Tests, licence, and the tooling to keep the repo consistent
A test suite with no dependencies beyond bash and the Python standard library, run by one command: scripts/test.sh (or: make test) scripts/test.sh --windows [host] adds the PowerShell suite over SSH - tests/static.sh: script syntax, JSON validity, VERSION against the widget manifest and CHANGELOG, providers.json against the kinds, probes and checks the code implements (and ids that would shadow a command), qmllint and shellcheck when installed. - tests/python: unit tests for cm-json.py - providers and the column contract bash depends on, the original three scaffolds byte for byte, every catalogue parser and the cache, session scan/dismiss/repair, rename, defaults, health, the cost guard. - tests/cli: the CLI in a sandbox with its own HOME, CM_ROOT, CLAUDE_CONFIG_DIR and a file-only vault, against fake Ollama, LM Studio, keyed-gateway and proxy servers - preflight, presets, models, doctor, a real switch, sessions, and the key helper under a rename race. - tests/windows: the same idea on a Windows host, with USERPROFILE pointed at a temp folder so the real install is never touched. Also: MIT licence, CHANGELOG.md reconstructed from history, .editorconfig and .gitattributes pinning LF everywhere (what the tree already is), and scripts/bump-version.sh, since the version lives in two files and drifted once before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+117
@@ -0,0 +1,117 @@
|
||||
# Shared by tests/cli/test_*.sh - source it first.
|
||||
#
|
||||
# Sourcing gives the test its own sandbox: a HOME, CM_ROOT and CLAUDE_CONFIG_DIR
|
||||
# of its own, the repository's scripts installed into it the way linux/install.sh
|
||||
# lays them out, and the vault forced to the plain-file backend. Nothing on the
|
||||
# machine running the tests is read or written - not the real keyring, not
|
||||
# ~/.claude-mode, not ~/.claude/settings.json - and a switch performed by a test
|
||||
# only rewrites the sandbox's settings.json.
|
||||
#
|
||||
# Assertions count rather than stop, so one run reports every failure; the
|
||||
# file ends with `finish`, whose status is the test's.
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
T_NAME="$(basename "$0" .sh)"
|
||||
T_PASS=0
|
||||
T_FAIL=0
|
||||
OUT=''
|
||||
RC=0
|
||||
FAKE_PID=''
|
||||
|
||||
SB="$(mktemp -d "${TMPDIR:-/tmp}/cm-test.XXXXXX")"
|
||||
mkdir -p "$SB/root/bin" "$SB/root/presets" "$SB/home/.claude/projects"
|
||||
cp "$REPO/linux/claude-mode" "$REPO/linux/cm-json.py" "$REPO/linux/cm-vault.sh" \
|
||||
"$REPO/linux/claude-key-helper.sh" "$SB/root/bin/"
|
||||
if [ -d "$REPO/linux/lib" ]; then cp -R "$REPO/linux/lib" "$SB/root/bin/"; fi
|
||||
chmod +x "$SB/root/bin/claude-mode" "$SB/root/bin/claude-key-helper.sh"
|
||||
cp "$REPO"/presets/*.json "$SB/root/presets/"
|
||||
cp "$REPO/providers.json" "$REPO/VERSION" "$SB/root/"
|
||||
|
||||
export HOME="$SB/home"
|
||||
export CM_ROOT="$SB/root"
|
||||
export CLAUDE_CONFIG_DIR="$SB/home/.claude"
|
||||
export CLAUDE_MODE_VAULT=file
|
||||
unset CM_IGNORE_AGE_DAYS CLAUDE_MODE_THEME
|
||||
|
||||
CM="$SB/root/bin/claude-mode"
|
||||
J="$SB/root/bin/cm-json.py"
|
||||
P="$SB/root/presets"
|
||||
|
||||
set_state() {
|
||||
printf '{"mode":"%s","preset":"%s","writtenEnvKeys":[]}\n' "$1" "${2:-}" > "$CM_ROOT/state.json"
|
||||
}
|
||||
set_state anthropic
|
||||
|
||||
t_cleanup() {
|
||||
if [ -n "$FAKE_PID" ]; then kill "$FAKE_PID" 2>/dev/null; fi
|
||||
rm -rf "$SB"
|
||||
}
|
||||
trap t_cleanup EXIT
|
||||
|
||||
# Starts tests/fake_server.py and sets OLLAMA_URL, LMSTUDIO_URL, KEYED_URL and
|
||||
# PROXY_URL from the ports it chose.
|
||||
start_fake() {
|
||||
python3 "$REPO/tests/fake_server.py" > "$SB/ports" 2>/dev/null &
|
||||
FAKE_PID=$!
|
||||
local i
|
||||
for i in $(seq 1 50); do
|
||||
[ -s "$SB/ports" ] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[ -s "$SB/ports" ] || { echo "$T_NAME: the fake server did not start" >&2; exit 1; }
|
||||
eval "$(python3 -c '
|
||||
import json, sys
|
||||
for kind, port in json.load(open(sys.argv[1])).items():
|
||||
print("%s_URL=http://127.0.0.1:%d" % (kind.upper(), port))
|
||||
' "$SB/ports")"
|
||||
}
|
||||
|
||||
ESC="$(printf '\033')"
|
||||
|
||||
# run_cm <args...>: the CLI's combined output, colour codes removed, in OUT;
|
||||
# its exit status in RC.
|
||||
run_cm() {
|
||||
OUT="$("$CM" "$@" 2>&1)"
|
||||
RC=$?
|
||||
OUT="$(printf '%s' "$OUT" | sed "s/${ESC}\[[0-9;]*m//g")"
|
||||
}
|
||||
|
||||
pass() { T_PASS=$((T_PASS + 1)); }
|
||||
|
||||
fail() {
|
||||
T_FAIL=$((T_FAIL + 1))
|
||||
printf ' FAIL %s: %s\n' "$T_NAME" "$*"
|
||||
if [ -n "$OUT" ]; then printf '%s\n' "$OUT" | tail -n 8 | sed 's/^/ | /'; fi
|
||||
}
|
||||
|
||||
expect_rc() { if [ "$RC" -eq "$1" ]; then pass; else fail "$2 (exit $RC, wanted $1)"; fi; }
|
||||
expect_out() { case "$OUT" in *"$1"*) pass ;; *) fail "$2 (output lacks: $1)" ;; esac; }
|
||||
expect_no_out() { case "$OUT" in *"$1"*) fail "$2 (output has: $1)" ;; *) pass ;; esac; }
|
||||
expect_eq() { if [ "$1" = "$2" ]; then pass; else fail "$3 (got '$1', wanted '$2')"; fi; }
|
||||
expect_file() { if [ -e "$1" ]; then pass; else fail "$2 (no $1)"; fi; }
|
||||
expect_no_file(){ if [ -e "$1" ]; then fail "$2 ($1 exists)"; else pass; fi; }
|
||||
|
||||
jget() { python3 "$J" get "$1" "$2"; }
|
||||
|
||||
# The preflight verdict for a mode (and preset) as "<code> <remedyKind>".
|
||||
preflight_code() {
|
||||
"$CM" preflight "$@" 2>/dev/null | python3 -c '
|
||||
import json, sys
|
||||
d = json.load(sys.stdin)
|
||||
print(d["code"], d["remedyKind"])'
|
||||
}
|
||||
|
||||
# Point a shipped preset at a fake server and mark it set up, so preflight
|
||||
# gets past "not set up yet" to the checks under test.
|
||||
ready_preset() {
|
||||
python3 "$J" set-flag "$P/$1.json" configured true
|
||||
[ -n "${2:-}" ] && python3 "$J" set-url "$P/$1.json" "$2" >/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
finish() {
|
||||
printf '%-22s %3d passed' "$T_NAME" "$T_PASS"
|
||||
if [ "$T_FAIL" -gt 0 ]; then printf ', %d FAILED' "$T_FAIL"; fi
|
||||
printf '\n'
|
||||
[ "$T_FAIL" -eq 0 ]
|
||||
}
|
||||
Reference in New Issue
Block a user