Skip to content

test: add a suite runner, gate it in CI, fix 8 failing tests - #60

Draft
yulonglin wants to merge 1 commit into
mainfrom
claude/test-coverage-analysis-67dcrk
Draft

test: add a suite runner, gate it in CI, fix 8 failing tests#60
yulonglin wants to merge 1 commit into
mainfrom
claude/test-coverage-analysis-67dcrk

Conversation

@yulonglin

Copy link
Copy Markdown
Owner

Follow-up to a test-coverage analysis of the repo. The headline finding was not that coverage is thin (though it is) but that nothing ran the tests — so eight suites were failing on main at once with nobody noticing.

What was wrong

  • .gitlab-ci.yml ran bash -n install.sh + bash -n deploy.sh. Both files are #!/usr/bin/env zsh, so this used the wrong parser — bash -n deploy.sh fails outright on line 768's zsh glob qualifiers ("$ctx_src"/*.json(N)), reporting valid zsh as a syntax error.
  • .github/workflows/build-claude-tools.yml compiles 4,465 lines of Rust, commits the binary into custom_bins/, and publishes a release — with no test step, and only on main, so PRs touching it were ungated.
  • No Makefile, justfile, pytest config, or runner script. Suites were invoked by hand.

Changes

Runnertests/run-all.sh

Discovers suites by naming convention (tests/test_*.{sh,zsh,py}, claude/hooks/test_*.sh, scripts/tests/test-*.sh), so a new suite needs no registration. Resolves each suite's interpreter rather than assuming one, and reports a missing interpreter as SKIP instead of silently dropping it. --strict turns SKIP into failure, so a runner without zsh degrades into a red build rather than invisible coverage.

CI.github/workflows/tests.yml

Runs tests/run-all.sh --strict on push and PR. Checkout uses fetch-depth: 0 because the memory-tier suite resolves a tag. A second job adds the missing cargo test gate for tools/claude-tools; it is green-on-zero-tests today by design — the point is that the gate exists before the tests do.

.gitlab-ci.yml's syntax check is replaced by tests/test_shell_syntax.sh, which dispatches on each file's shebang and covers 191 tracked scripts instead of 2 — including the 46 shebanged executables in custom_bins/, which had no syntax gate at all.

Fixes

Suite Defect
test_memory_tier_budget AGGREGATE_CEILING was the literal sum of the per-file ceilings while safety-and-git.md was exempt from its own — double-counting the exemption, making the aggregate unsatisfiable by construction. Now derived from the exemption; the exempt file's protected floor and prose allowance are each asserted separately.
test_memory_tier_budget git show <tag>:path raised CalledProcessError on clones without tags — infrastructure failure wearing the costume of a content regression. Now fetches the one tag it needs, and skips legibly if it cannot.
test_vault_sync_gate Two tests that pass the gate reached the real resolve_ob() and died on obsidian-headless not being installed. Stubbed explicitly rather than via an autouse fixture, so test_filters_never_invokes_ob_when_blocked — which proves the stop precedes the irreversible call — keeps working.
test_simplify_reuse Asserted on the string quality pass, wording simplify_nudge.sh no longer emits. Now keys on the dirty-signal sentence, which is also the only phrase distinguishing the two message parts.

Verification

tests/run-all.sh --strict — 19 suites, 0 failed, 0 skipped (with zsh installed).

Each fix was mutation-tested to confirm it still asserts something:

Mutation Result
Neuter exclusion_blocker() 23 vault tests red
Reword the simplify nudge 2 tests red
+600 bytes to a memory file aggregate + per-file red
Stray if in a hook syntax suite red

Not in scope

The analysis flagged these as the next priorities, none addressed here: tools/claude-tools has zero tests; 38 of the 49 hooks wired in settings.json are untested (12 of them return deny); scripts/shared/helpers.sh has ~55 untested functions including safe_symlink, merge_json_settings and sync_authorized_keys_union. claude/hooks/README.md now names the specific hooks worth writing next.


Generated by Claude Code

The suites in tests/, claude/hooks/ and scripts/tests/ were only ever
invoked by hand — no CI ran them. Eight were failing on main at once
with nobody noticing.

Runner:
- tests/run-all.sh discovers suites by naming convention, resolves each
  one's interpreter, and reports a missing interpreter as SKIP rather
  than dropping it. --strict makes SKIP a failure, which is what CI runs.

CI:
- .github/workflows/tests.yml runs the suite on push and PR. Checkout
  uses fetch-depth: 0 because the memory-tier test resolves a tag.
- Adds a cargo test gate for tools/claude-tools: 4.4k lines of Rust that
  ships as a committed binary, previously compiled and released by CI
  with no test step and no PR gate.
- .gitlab-ci.yml checked deploy.sh and install.sh with `bash -n`, but
  both are zsh; `bash -n deploy.sh` fails on line 768's zsh glob
  qualifiers. Replaced with tests/test_shell_syntax.sh, which dispatches
  on each file's shebang and covers 191 tracked scripts instead of 2.

Fixes:
- test_memory_tier_budget: AGGREGATE_CEILING was the literal sum of the
  per-file ceilings while safety-and-git.md was exempt from its own —
  double-counting the exemption, so the aggregate was unsatisfiable by
  construction. It is now derived from the exemption, and the exempt
  file's protected floor and prose allowance are each asserted.
- test_memory_tier_budget: `git show <tag>:path` raised on clones without
  tags. Fetches the one tag it needs, skips legibly if it cannot.
- test_vault_sync_gate: two tests that pass the gate reached the real
  resolve_ob() and died on obsidian-headless not being installed. Stubbed
  explicitly, not via autouse, so the test that proves the stop precedes
  the irreversible call keeps working.
- test_simplify_reuse: asserted on "quality pass", wording the hook no
  longer emits. Now keys on the dirty-signal sentence.

Each fix was mutation-tested: neutering the vault gate reddens 23 tests,
rewording the nudge reddens 2, +600 bytes to a memory file reddens the
aggregate, and a stray `if` reddens the syntax suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Swn1T5hcQRUMmQfnepjQde
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants