From 32cd2e3128e7c4a4a78e0bb72dba812db5386a5d Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Thu, 25 Jun 2026 19:47:20 +0000 Subject: [PATCH] test: add run-tests.sh, fix stale tests for marketplace model - Add tests/run-tests.sh: discovers and runs all test-*.sh (including plugin subdirs) and the bats suite; replaces per-script pre-push calls - Add tests/run-tests.bats: TDD coverage for run-tests.sh behaviours - Update setup-hooks.sh: pre-push block now calls run-tests.sh - Fix test-install.sh: remove provider adapter symlink tests (adapter removed in marketplace migration), guard skills loops on dir existence - Fix test-instructions-and-docs.sh: content index checks now point to core/AGENTS.md (where it lives), remove ard/bug dir assertions - Fix test-setup-hooks.sh: assert pre-push hook calls run-tests.sh Co-Authored-By: Claude Sonnet 4.6 --- scripts/setup-hooks.sh | 6 +- tests/run-tests.bats | 66 ++++++++++++ tests/run-tests.sh | 57 ++++++++++ tests/test-install.sh | 158 +++++++++++----------------- tests/test-instructions-and-docs.sh | 27 ++--- tests/test-setup-hooks.sh | 5 + 6 files changed, 207 insertions(+), 112 deletions(-) create mode 100644 tests/run-tests.bats create mode 100755 tests/run-tests.sh diff --git a/scripts/setup-hooks.sh b/scripts/setup-hooks.sh index f236a05..665eefd 100755 --- a/scripts/setup-hooks.sh +++ b/scripts/setup-hooks.sh @@ -197,11 +197,7 @@ pre_push_block() { HOOKS_SCRIPT_DIR="$script_dir" REPO_ROOT="\$(git rev-parse --show-toplevel)" -echo "Running test suite..." -bash "\$REPO_ROOT/tests/test-install.sh" -bash "\$REPO_ROOT/tests/test-governance-layer.sh" -bash "\$REPO_ROOT/tests/test-check-manifests.sh" -bash "\$REPO_ROOT/tests/test-setup-hooks.sh" +bash "\$REPO_ROOT/tests/run-tests.sh" echo "Checking manifests..." bash "\$HOOKS_SCRIPT_DIR/check-manifests.sh" "\$REPO_ROOT" diff --git a/tests/run-tests.bats b/tests/run-tests.bats new file mode 100644 index 0000000..ac04b3d --- /dev/null +++ b/tests/run-tests.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats + +REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)" +RUN_TESTS="$REPO_ROOT/tests/run-tests.sh" + +setup() { + SCRATCH="$(mktemp -d)" +} + +teardown() { + rm -rf "$SCRATCH" +} + +# --- Tracer bullet --- + +@test "exits 0 when all test scripts pass" { + mkdir -p "$SCRATCH/tests" + printf '#!/usr/bin/env bash\necho "ok"\n' > "$SCRATCH/tests/test-one.sh" + chmod +x "$SCRATCH/tests/test-one.sh" + TEST_DIR="$SCRATCH" run bash "$RUN_TESTS" + [ "$status" -eq 0 ] +} + +# --- Failure detection --- + +@test "exits non-zero when a test script fails" { + mkdir -p "$SCRATCH/tests" + printf '#!/usr/bin/env bash\nexit 1\n' > "$SCRATCH/tests/test-bad.sh" + chmod +x "$SCRATCH/tests/test-bad.sh" + TEST_DIR="$SCRATCH" run bash "$RUN_TESTS" + [ "$status" -ne 0 ] +} + +@test "reports the name of the failing script" { + mkdir -p "$SCRATCH/tests" + printf '#!/usr/bin/env bash\nexit 1\n' > "$SCRATCH/tests/test-bad.sh" + chmod +x "$SCRATCH/tests/test-bad.sh" + TEST_DIR="$SCRATCH" run bash "$RUN_TESTS" + [[ "$output" == *"test-bad.sh"* ]] +} + +# --- Subdirectory discovery --- + +@test "discovers test scripts in subdirectories" { + mkdir -p "$SCRATCH/plugins/myplugin" + printf '#!/usr/bin/env bash\necho "plugin test ok"\n' > "$SCRATCH/plugins/myplugin/test-plugin.sh" + chmod +x "$SCRATCH/plugins/myplugin/test-plugin.sh" + TEST_DIR="$SCRATCH" run bash "$RUN_TESTS" + [ "$status" -eq 0 ] + [[ "$output" == *"test-plugin.sh"* ]] +} + +@test "does not pick up scripts not named test-*.sh" { + mkdir -p "$SCRATCH/tests" + printf '#!/usr/bin/env bash\nexit 1\n' > "$SCRATCH/tests/run-bats.sh" + chmod +x "$SCRATCH/tests/run-bats.sh" + TEST_DIR="$SCRATCH" run bash "$RUN_TESTS" + [ "$status" -eq 0 ] +} + +# --- Bats integration --- + +@test "runs the bats suite when no TEST_DIR override is set" { + run bash "$RUN_TESTS" --bats-only + [ "$status" -eq 0 ] +} diff --git a/tests/run-tests.sh b/tests/run-tests.sh new file mode 100755 index 0000000..dcfea34 --- /dev/null +++ b/tests/run-tests.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Run all test-*.sh files in the repo (including plugins) and the bats suite. +# Usage: bash tests/run-tests.sh [--bats-only] +# +# TEST_DIR — override root to search for test-*.sh (default: REPO_ROOT); used by tests. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BATS="$REPO_ROOT/tests/run-bats.sh" +BATS_ONLY=false +[[ "${1:-}" == "--bats-only" ]] && BATS_ONLY=true + +SEARCH_ROOT="${TEST_DIR:-$REPO_ROOT}" + +FAILED=() +PASSED=0 + +run_bats() { + if [[ -x "$BATS" ]]; then + echo "=== bats ===" + bash "$BATS" + echo "" + fi +} + +if $BATS_ONLY; then + run_bats + exit 0 +fi + +run_bats + +mapfile -t SCRIPTS < <( + find "$SEARCH_ROOT" -name "test-*.sh" \ + -not -path "*/.git/*" \ + | sort +) + +for script in "${SCRIPTS[@]}"; do + rel="${script#"$SEARCH_ROOT/"}" + echo "=== $rel ===" + if bash "$script"; then + PASSED=$((PASSED + 1)) + else + FAILED+=("$rel") + fi + echo "" +done + +echo "=== Summary: $PASSED passed, ${#FAILED[@]} failed ===" +if [[ ${#FAILED[@]} -gt 0 ]]; then + echo "Failed scripts:" + for s in "${FAILED[@]}"; do + echo " $s" + done + exit 1 +fi diff --git a/tests/test-install.sh b/tests/test-install.sh index d7445fa..aa12082 100755 --- a/tests/test-install.sh +++ b/tests/test-install.sh @@ -18,7 +18,6 @@ echo "" echo "--- providers/claude-code/ → ~/.claude/ ---" while IFS= read -r -d '' src; do rel="${src#"$REPO_ROOT/providers/claude-code/"}" - # provider-manifest.sh is sourced by install.sh, not deployed to ~/.claude/ [[ "$rel" == "provider-manifest.sh" ]] && continue dest="$TEMP_HOME/.claude/$rel" if diff -q "$src" "$dest" > /dev/null 2>&1; then @@ -57,56 +56,46 @@ echo "--- core/AGENTS.md → ~/.agents/AGENTS.md (0015) ---" if diff -q "$REPO_ROOT/core/AGENTS.md" "$TEMP_HOME/.agents/AGENTS.md" > /dev/null 2>&1; then pass "core/AGENTS.md deployed and matches source" else - fail "~/.agents/AGENTS.md — missing or differs from source" + fail "\$HOME/.agents/AGENTS.md — missing or differs from source" fi -echo "" -echo "--- skills deployed to ~/.agents/skills/ ---" -while IFS= read -r -d '' src_skill; do - skill_name="$(basename "$src_skill")" - dest_skill="$TEMP_HOME/.agents/skills/$skill_name" - if [[ -d "$dest_skill" ]]; then - pass "$skill_name deployed to ~/.agents/skills/" - else - fail "$skill_name missing from ~/.agents/skills/" - fi -done < <(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d -print0) +if [[ -d "$REPO_ROOT/.agents/skills" ]]; then + echo "" + echo "--- skills deployed to ~/.agents/skills/ ---" + while IFS= read -r -d '' src_skill; do + skill_name="$(basename "$src_skill")" + dest_skill="$TEMP_HOME/.agents/skills/$skill_name" + if [[ -d "$dest_skill" ]]; then + pass "$skill_name deployed to ~/.agents/skills/" + else + fail "$skill_name missing from ~/.agents/skills/" + fi + done < <(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d -print0) -echo "" -echo "--- skill files match source ---" -while IFS= read -r -d '' src; do - rel="${src#"$REPO_ROOT/.agents/skills/"}" - dest="$TEMP_HOME/.agents/skills/$rel" - if diff -q "$src" "$dest" > /dev/null 2>&1; then - pass "skills/$rel matches source" - else - fail "skills/$rel — missing or differs from source" - fi -done < <(find "$REPO_ROOT/.agents/skills" -type f -print0) + echo "" + echo "--- skill files match source ---" + while IFS= read -r -d '' src; do + rel="${src#"$REPO_ROOT/.agents/skills/"}" + dest="$TEMP_HOME/.agents/skills/$rel" + if diff -q "$src" "$dest" > /dev/null 2>&1; then + pass "skills/$rel matches source" + else + fail "skills/$rel — missing or differs from source" + fi + done < <(find "$REPO_ROOT/.agents/skills" -type f -print0) -echo "" -echo "--- provider adapter: ~/.claude/skills/ is a symlink to ~/.agents/skills/ ---" -adapter="$TEMP_HOME/.claude/skills" -skills_canonical="$TEMP_HOME/.agents/skills" -if [[ -L "$adapter" ]]; then - resolved="$(readlink "$adapter")" - if [[ "$resolved" == "$skills_canonical" ]]; then - pass "~/.claude/skills → ~/.agents/skills (correct target)" - else - fail "~/.claude/skills symlink points to wrong target: $resolved" - fi -else - fail "~/.claude/skills is not a symlink" -fi - -echo "" -echo "--- skills correctly replaced on second install (no double-nesting) ---" -skill_name="$(basename "$(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d | head -1)")" -nested="$TEMP_HOME/.agents/skills/$skill_name/$skill_name" -if [[ -d "$nested" ]]; then - fail "$skill_name/$skill_name exists — skill was nested instead of replaced" -else - pass "$skill_name not double-nested after second install" + echo "" + echo "--- skills correctly replaced on second install (no double-nesting) ---" + first_skill="$(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d | head -1)" + if [[ -n "$first_skill" ]]; then + skill_name="$(basename "$first_skill")" + nested="$TEMP_HOME/.agents/skills/$skill_name/$skill_name" + if [[ -d "$nested" ]]; then + fail "$skill_name/$skill_name exists — skill was nested instead of replaced" + else + pass "$skill_name not double-nested after second install" + fi + fi fi echo "" @@ -123,57 +112,36 @@ else fail "pre-existing user skill was wiped by install" fi -echo "" -echo "--- warning emitted when adapter target is a real directory ---" -TEMP_HOME3="$(mktemp -d)" -trap 'rm -rf "$TEMP_HOME3"' EXIT -mkdir -p "$TEMP_HOME3/.claude/skills/some-user-skill" -output="$(HOME="$TEMP_HOME3" bash "$REPO_ROOT/scripts/install.sh" 2>&1)" -if echo "$output" | grep -q "Warning"; then - pass "warning emitted when ~/.claude/skills exists as real directory" -else - fail "no warning when ~/.claude/skills is a real directory" -fi -if [[ -d "$TEMP_HOME3/.claude/skills/some-user-skill" ]]; then - pass "real directory left intact when warning emitted" -else - fail "real directory was destroyed despite warning" -fi - echo "" echo "--- idempotency: second run state is correct ---" -HOME="$TEMP_HOME" bash "$REPO_ROOT/scripts/install.sh" > /dev/null 2>&1 \ - && pass "second run exits zero" \ - || fail "second run failed" - -# skill files still match source after second run -while IFS= read -r -d '' src; do - rel="${src#"$REPO_ROOT/.agents/skills/"}" - dest="$TEMP_HOME/.agents/skills/$rel" - if diff -q "$src" "$dest" > /dev/null 2>&1; then - pass "idempotent: skills/$rel correct after second install" - else - fail "idempotent: skills/$rel corrupted after second install" - fi -done < <(find "$REPO_ROOT/.agents/skills" -type f -print0) - -# no double-nesting after second run -while IFS= read -r -d '' skill_dir; do - skill_name="$(basename "$skill_dir")" - nested="$TEMP_HOME/.agents/skills/$skill_name/$skill_name" - if [[ -d "$nested" ]]; then - fail "idempotent: $skill_name double-nested after second install" - else - pass "idempotent: $skill_name not double-nested after second install" - fi -done < <(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d -print0) - -# symlink still correct after second run -adapter="$TEMP_HOME/.claude/skills" -if [[ -L "$adapter" ]] && [[ "$(readlink "$adapter")" == "$TEMP_HOME/.agents/skills" ]]; then - pass "idempotent: ~/.claude/skills symlink intact after second install" +if HOME="$TEMP_HOME" bash "$REPO_ROOT/scripts/install.sh" > /dev/null 2>&1; then + pass "second run exits zero" else - fail "idempotent: ~/.claude/skills symlink broken after second install" + fail "second run failed" +fi + +if [[ -d "$REPO_ROOT/.agents/skills" ]]; then + # skill files still match source after second run + while IFS= read -r -d '' src; do + rel="${src#"$REPO_ROOT/.agents/skills/"}" + dest="$TEMP_HOME/.agents/skills/$rel" + if diff -q "$src" "$dest" > /dev/null 2>&1; then + pass "idempotent: skills/$rel correct after second install" + else + fail "idempotent: skills/$rel corrupted after second install" + fi + done < <(find "$REPO_ROOT/.agents/skills" -type f -print0) + + # no double-nesting after second run + while IFS= read -r -d '' skill_dir; do + skill_name="$(basename "$skill_dir")" + nested="$TEMP_HOME/.agents/skills/$skill_name/$skill_name" + if [[ -d "$nested" ]]; then + fail "idempotent: $skill_name double-nested after second install" + else + pass "idempotent: $skill_name not double-nested after second install" + fi + done < <(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d -print0) fi # AGENTS.md still correct after second run diff --git a/tests/test-instructions-and-docs.sh b/tests/test-instructions-and-docs.sh index b4b397a..1418e38 100755 --- a/tests/test-instructions-and-docs.sh +++ b/tests/test-instructions-and-docs.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC2015 # pass()/fail() always exit 0; A && pass || fail is safe here set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" @@ -35,18 +36,19 @@ CLAUDE="$REPO_ROOT/providers/claude-code/CLAUDE.md" && pass "behavior: rules not duplicated in CLAUDE.md (moved to core/AGENTS.md)" \ || fail "behavior: rules still inline in CLAUDE.md — 0015 refactor incomplete" -# Content index — must reference each on-demand file -contains "coding" "$CLAUDE" \ - && pass "content index: coding conventions trigger present" \ - || fail "content index: coding conventions trigger missing" +# Content index lives in core/AGENTS.md (deployed as ~/.agents/AGENTS.md), not in CLAUDE.md +CORE_AGENTS_FOR_0004="$REPO_ROOT/core/AGENTS.md" +contains "coding" "$CORE_AGENTS_FOR_0004" \ + && pass "content index: coding conventions trigger present (core/AGENTS.md)" \ + || fail "content index: coding conventions trigger missing from core/AGENTS.md" -contains "git" "$CLAUDE" \ - && pass "content index: git conventions trigger present" \ - || fail "content index: git conventions trigger missing" +contains "git" "$CORE_AGENTS_FOR_0004" \ + && pass "content index: git conventions trigger present (core/AGENTS.md)" \ + || fail "content index: git conventions trigger missing from core/AGENTS.md" -contains "testing" "$CLAUDE" \ - && pass "content index: testing conventions trigger present" \ - || fail "content index: testing conventions trigger missing" +contains "testing" "$CORE_AGENTS_FOR_0004" \ + && pass "content index: testing conventions trigger present (core/AGENTS.md)" \ + || fail "content index: testing conventions trigger missing from core/AGENTS.md" # global.md must be retired — no longer referenced in content index ! contains "global\.md" "$CLAUDE" \ @@ -156,7 +158,7 @@ echo "" echo "--- 0008: docs/ restructure ---" -for dir in prd ard bug notes adr; do +for dir in prd notes adr; do [[ -d "$REPO_ROOT/docs/$dir" ]] \ && pass "docs/$dir/ exists" \ || fail "docs/$dir/ missing" @@ -285,7 +287,8 @@ contains "core/AGENTS\.md" "$ARCH" \ && pass "architecture.md: core/AGENTS.md entry present" \ || fail "architecture.md: core/AGENTS.md entry missing" -contains "~/\.agents/AGENTS\.md" "$ARCH" \ +# shellcheck disable=SC2088 # tilde is a literal search string, not a path +grep -qF '~/.agents/AGENTS.md' "$ARCH" \ && pass "architecture.md: ~/.agents/AGENTS.md deployment path present" \ || fail "architecture.md: ~/.agents/AGENTS.md deployment path missing" diff --git a/tests/test-setup-hooks.sh b/tests/test-setup-hooks.sh index 8adce9c..372047f 100644 --- a/tests/test-setup-hooks.sh +++ b/tests/test-setup-hooks.sh @@ -140,6 +140,11 @@ if grep -q "check-manifests" "$PUSH_HOOK"; then else fail "pre-push hook missing check-manifests.sh call" fi +if grep -q "run-tests.sh" "$PUSH_HOOK"; then + pass "pre-push hook calls run-tests.sh" +else + fail "pre-push hook missing run-tests.sh call" +fi # --- 6. Idempotent: second run replaces each block exactly once --- echo ""