gabriel / musehub public
smoke_muse.sh bash
244 lines 9.6 KB
Raw
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