smoke_muse.sh
bash
sha256:5601f81903b6c70ddd11bd88a5a257ee6dfd38aa3b85b19746c100c030657f1e
chore: update smoke_muse.sh comment to reference rc9
Sonnet 4.6
minor
⚠ breaking
19 days ago
| 1 | #!/usr/bin/env bash |
| 2 | # Post-deploy smoke harness for the muse CLI. |
| 3 | # |
| 4 | # Installs the published tarball into a throwaway venv and runs 18 checks |
| 5 | # against the installed binary. The dev editable install is never touched. |
| 6 | # |
| 7 | # Usage: |
| 8 | # bash deploy/smoke_muse.sh # staging, auto version |
| 9 | # bash deploy/smoke_muse.sh --url https://musehub.ai # prod |
| 10 | # bash deploy/smoke_muse.sh --version 0.2.0rc9 # explicit version |
| 11 | # |
| 12 | # Exit codes: |
| 13 | # 0 All checks passed. |
| 14 | # 1 One or more checks failed. |
| 15 | # 2 Setup error (tarball not found, venv creation failed, etc.). |
| 16 | |
| 17 | set -euo pipefail |
| 18 | |
| 19 | MUSE_REPO="${MUSE_REPO:-$HOME/ecosystem/muse}" |
| 20 | |
| 21 | # ── Defaults ────────────────────────────────────────────────────────────────── |
| 22 | |
| 23 | HUB_URL="https://staging.musehub.ai" |
| 24 | MUSE_VERSION="" |
| 25 | |
| 26 | # ── Arg parse ───────────────────────────────────────────────────────────────── |
| 27 | |
| 28 | while [[ $# -gt 0 ]]; do |
| 29 | case "$1" in |
| 30 | --url) HUB_URL="$2"; shift 2 ;; |
| 31 | --version) MUSE_VERSION="$2"; shift 2 ;; |
| 32 | *) echo "Unknown argument: $1" >&2; exit 2 ;; |
| 33 | esac |
| 34 | done |
| 35 | |
| 36 | # ── Resolve version ─────────────────────────────────────────────────────────── |
| 37 | |
| 38 | if [[ -z "$MUSE_VERSION" ]]; then |
| 39 | MUSE_VERSION=$(python3 -c " |
| 40 | import re, pathlib |
| 41 | t = pathlib.Path('$MUSE_REPO/pyproject.toml').read_text() |
| 42 | m = re.search(r'^version\s*=\s*\"([^\"]+)\"', t, re.MULTILINE) |
| 43 | print(m.group(1)) |
| 44 | ") |
| 45 | fi |
| 46 | |
| 47 | TARBALL_URL="${HUB_URL}/releases/muse-${MUSE_VERSION}.tar.gz" |
| 48 | |
| 49 | # ── Colours ─────────────────────────────────────────────────────────────────── |
| 50 | |
| 51 | GREEN='\033[0;32m' |
| 52 | RED='\033[0;31m' |
| 53 | BOLD='\033[1m' |
| 54 | DIM='\033[2m' |
| 55 | RESET='\033[0m' |
| 56 | |
| 57 | pass() { printf " ${GREEN}✅${RESET} %-22s %s\n" "$1" "$2"; } |
| 58 | fail() { printf " ${RED}❌${RESET} %-22s %s\n" "$1" "$2"; } |
| 59 | info() { printf " ${DIM}%s${RESET}\n" "$1"; } |
| 60 | |
| 61 | # ── Shared state ────────────────────────────────────────────────────────────── |
| 62 | |
| 63 | PASSED=0 |
| 64 | FAILED=0 |
| 65 | SMOKE_VENV="" |
| 66 | SMOKE_REPO="" |
| 67 | SMOKE_TARBALL="" |
| 68 | |
| 69 | # ── Cleanup (always runs) ───────────────────────────────────────────────────── |
| 70 | |
| 71 | cleanup() { |
| 72 | [[ -n "$SMOKE_REPO" ]] && rm -rf "$SMOKE_REPO" |
| 73 | [[ -n "$SMOKE_TARBALL" ]] && rm -f "$SMOKE_TARBALL" |
| 74 | [[ -n "$SMOKE_VENV" ]] && rm -rf "$SMOKE_VENV" |
| 75 | } |
| 76 | trap cleanup EXIT |
| 77 | |
| 78 | # ── Check helpers ───────────────────────────────────────────────────────────── |
| 79 | |
| 80 | _ts() { python3 -c "import time; print(time.monotonic())"; } |
| 81 | _ms() { python3 -c "print(f'{($2-$1)*1000:.0f}ms')"; } |
| 82 | |
| 83 | check_exit() { |
| 84 | local name="$1"; shift |
| 85 | local t0 t1 ms rc _out _err _tmp_o _tmp_e |
| 86 | _tmp_o=$(mktemp); _tmp_e=$(mktemp) |
| 87 | t0=$(_ts) |
| 88 | "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? |
| 89 | t1=$(_ts); ms=$(_ms "$t0" "$t1") |
| 90 | _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") |
| 91 | rm -f "$_tmp_o" "$_tmp_e" |
| 92 | if [[ $rc -eq 0 ]]; then |
| 93 | pass "$name" "$ms"; (( PASSED++ )) || true |
| 94 | else |
| 95 | fail "$name" "exit $rc — $ms — ${_err:0:120}${_out:0:80}" |
| 96 | (( FAILED++ )) || true |
| 97 | fi |
| 98 | } |
| 99 | |
| 100 | check_json() { |
| 101 | local name="$1" expr="$2" expected="$3"; shift 3 |
| 102 | local t0 t1 ms rc actual _out _err _tmp_o _tmp_e |
| 103 | _tmp_o=$(mktemp); _tmp_e=$(mktemp) |
| 104 | t0=$(_ts) |
| 105 | "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? |
| 106 | t1=$(_ts); ms=$(_ms "$t0" "$t1") |
| 107 | _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") |
| 108 | rm -f "$_tmp_o" "$_tmp_e" |
| 109 | if [[ $rc -ne 0 ]]; then |
| 110 | fail "$name" "exit $rc — $ms — ${_err:0:120}" |
| 111 | (( FAILED++ )) || true; return |
| 112 | fi |
| 113 | actual=$(echo "$_out" | python3 -c " |
| 114 | import sys, json |
| 115 | d = json.load(sys.stdin) |
| 116 | result = $expr |
| 117 | print(str(result).lower() if isinstance(result, bool) else result) |
| 118 | " 2>&1) && true |
| 119 | if [[ "$actual" == "$expected" ]]; then |
| 120 | pass "$name" "$ms"; (( PASSED++ )) || true |
| 121 | else |
| 122 | fail "$name" "expected '$expected' got '${actual:0:100}' — $ms" |
| 123 | (( FAILED++ )) || true |
| 124 | fi |
| 125 | } |
| 126 | |
| 127 | check_contains() { |
| 128 | local name="$1" needle="$2"; shift 2 |
| 129 | local t0 t1 ms rc _out _err _tmp_o _tmp_e |
| 130 | _tmp_o=$(mktemp); _tmp_e=$(mktemp) |
| 131 | t0=$(_ts) |
| 132 | "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? |
| 133 | t1=$(_ts); ms=$(_ms "$t0" "$t1") |
| 134 | _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") |
| 135 | rm -f "$_tmp_o" "$_tmp_e" |
| 136 | if [[ $rc -ne 0 ]]; then |
| 137 | fail "$name" "exit $rc — $ms — ${_err:0:120}" |
| 138 | (( FAILED++ )) || true; return |
| 139 | fi |
| 140 | if echo "$_out" | grep -qF "$needle"; then |
| 141 | pass "$name" "$ms"; (( PASSED++ )) || true |
| 142 | else |
| 143 | fail "$name" "needle '$needle' not found — $ms" |
| 144 | (( FAILED++ )) || true |
| 145 | fi |
| 146 | } |
| 147 | |
| 148 | check_json_contains() { |
| 149 | local name="$1" key="$2" needle="$3"; shift 3 |
| 150 | local t0 t1 ms rc actual _out _err _tmp_o _tmp_e |
| 151 | _tmp_o=$(mktemp); _tmp_e=$(mktemp) |
| 152 | t0=$(_ts) |
| 153 | "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? |
| 154 | t1=$(_ts); ms=$(_ms "$t0" "$t1") |
| 155 | _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") |
| 156 | rm -f "$_tmp_o" "$_tmp_e" |
| 157 | if [[ $rc -ne 0 ]]; then |
| 158 | fail "$name" "exit $rc — $ms — ${_err:0:120}" |
| 159 | (( FAILED++ )) || true; return |
| 160 | fi |
| 161 | actual=$(echo "$_out" | python3 -c " |
| 162 | import sys, json |
| 163 | d = json.load(sys.stdin) |
| 164 | items = d.get('$key', d) if isinstance(d, dict) else d |
| 165 | needle = '$needle' |
| 166 | found = any(needle in str(item) for item in (items if isinstance(items, list) else [items])) |
| 167 | print('true' if found else 'false') |
| 168 | " 2>&1) |
| 169 | if [[ "$actual" == "true" ]]; then |
| 170 | pass "$name" "$ms"; (( PASSED++ )) || true |
| 171 | else |
| 172 | fail "$name" "needle '$needle' not found in .$key — $ms" |
| 173 | (( FAILED++ )) || true |
| 174 | fi |
| 175 | } |
| 176 | |
| 177 | # ── Setup ───────────────────────────────────────────────────────────────────── |
| 178 | |
| 179 | printf "\n${BOLD}[smoke] muse ${MUSE_VERSION} url=${HUB_URL}${RESET}\n\n" |
| 180 | |
| 181 | printf " Downloading %s …\n" "$TARBALL_URL" |
| 182 | SMOKE_TARBALL=$(mktemp /tmp/muse-smoke-XXXXXX.tar.gz) |
| 183 | HTTP=$(curl -fsSL --write-out "%{http_code}" -o "$SMOKE_TARBALL" "$TARBALL_URL" 2>&1) && true |
| 184 | if [[ "$HTTP" != "200" ]]; then |
| 185 | printf "${RED}ERROR:${RESET} tarball download failed (HTTP %s) — %s\n" "$HTTP" "$TARBALL_URL" >&2 |
| 186 | exit 2 |
| 187 | fi |
| 188 | printf " Downloaded %s\n" "$(du -sh "$SMOKE_TARBALL" | cut -f1)" |
| 189 | |
| 190 | printf " Installing into throwaway venv …\n" |
| 191 | SMOKE_VENV=$(mktemp -d /tmp/muse-smoke-venv-XXXXXX) |
| 192 | python3 -m venv "$SMOKE_VENV" |
| 193 | "$SMOKE_VENV/bin/pip" install --quiet --disable-pip-version-check "$SMOKE_TARBALL" 2>/dev/null |
| 194 | MUSE="$SMOKE_VENV/bin/muse" |
| 195 | |
| 196 | SMOKE_REPO=$(mktemp -d /tmp/muse-smoke-repo-XXXXXX) |
| 197 | |
| 198 | printf "\n" |
| 199 | |
| 200 | # ── Check corpus ────────────────────────────────────────────────────────────── |
| 201 | |
| 202 | printf "${BOLD}local CLI checks${RESET}\n\n" |
| 203 | |
| 204 | TOTAL_START=$(_ts) |
| 205 | |
| 206 | check_contains "version" "$MUSE_VERSION" "$MUSE" --version |
| 207 | check_exit "init" "$MUSE" init --json "$SMOKE_REPO" |
| 208 | |
| 209 | M() { "$MUSE" -C "$SMOKE_REPO" "$@"; } |
| 210 | |
| 211 | check_exit "add_empty" M code add . |
| 212 | |
| 213 | echo "muse smoke test file — $(date -u)" > "$SMOKE_REPO/smoke.txt" |
| 214 | |
| 215 | check_exit "add_file" M code add smoke.txt |
| 216 | check_exit "commit" M commit -m "smoke" --agent-id smoke --model-id smoke |
| 217 | check_json "status_clean" 'd["clean"]' "true" M status --json |
| 218 | check_json "log_count" 'len(d["commits"])' "1" M log --json |
| 219 | check_json "read_message" 'd["message"]' "smoke" M read --json |
| 220 | check_json_contains "ls_files" "files" "smoke.txt" M ls-files --json |
| 221 | check_json "branch_count" 'len(d)' "1" M branch --json |
| 222 | check_json "branch_current" 'd[0]["current"]' "true" M branch --json |
| 223 | check_contains "diff_clean" "" M diff |
| 224 | check_exit "checkout_b" M checkout -b smoke/branch |
| 225 | check_exit "checkout_main" M checkout main |
| 226 | check_exit "branch_delete" M branch -d smoke/branch |
| 227 | check_exit "tag_add" M tag add "smoke:v1" |
| 228 | check_json_contains "tag_list" "tags" "smoke:v1" M tag list --json |
| 229 | check_json "verify" 'd["all_ok"]' "true" M verify --json |
| 230 | |
| 231 | # ── Summary ─────────────────────────────────────────────────────────────────── |
| 232 | |
| 233 | TOTAL_END=$(_ts) |
| 234 | TOTAL_MS=$(_ms "$TOTAL_START" "$TOTAL_END") |
| 235 | TOTAL=$(( PASSED + FAILED )) |
| 236 | |
| 237 | printf "\n" |
| 238 | if [[ $FAILED -eq 0 ]]; then |
| 239 | printf "${GREEN}${BOLD}[smoke] PASSED %d/%d total=%s${RESET}\n\n" "$PASSED" "$TOTAL" "$TOTAL_MS" |
| 240 | exit 0 |
| 241 | else |
| 242 | printf "${RED}${BOLD}[smoke] FAILED %d/%d total=%s${RESET}\n\n" "$PASSED" "$TOTAL" "$TOTAL_MS" |
| 243 | exit 1 |
| 244 | fi |
File History
1 commit
sha256:5601f81903b6c70ddd11bd88a5a257ee6dfd38aa3b85b19746c100c030657f1e
chore: update smoke_muse.sh comment to reference rc9
Sonnet 4.6
minor
⚠
19 days ago