From 63ff1b6b43ae02e93f722af759fae08a51dae951 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 Aug 2026 16:05:59 -0700 Subject: [PATCH] Add focused changed-path test wrapper --- docs/Architecture/Testing Policy.md | 15 +++ scripts/test-changed.sh | 142 ++++++++++++++++++++++++++++ tests/changed_tests.bats | 27 ++++++ 3 files changed, 184 insertions(+) create mode 100755 scripts/test-changed.sh create mode 100644 tests/changed_tests.bats diff --git a/docs/Architecture/Testing Policy.md b/docs/Architecture/Testing Policy.md index 303e1b8..9b34e01 100644 --- a/docs/Architecture/Testing Policy.md +++ b/docs/Architecture/Testing Policy.md @@ -13,6 +13,21 @@ This repository tests its configuration at defined seams. The tests run with `ba | Script syntax | `tests/scripts_syntax.bats` | `bash -n` clean, executable bits, shellcheck when available | | Deployment flow | `tests/deployment_flow.bats` | deploy.sh exists, targets known, SSH reachable (skips offline) | +## Focused pre-commit testing + +For normal commits, use the focused wrapper: + +```bash +./scripts/test-changed.sh +``` + +It reads staged paths with `git diff --cached` and runs only the mapped Bats +files. Use `--list` to inspect the selection without running tests, or +`--paths FILE ...` to preview a mapping. Shared test infrastructure and +unmapped paths fail closed rather than silently skipping coverage. Run the +full suite with `./tests/run.sh` when changing shared test helpers, the test +runner, or when focused coverage is not sufficient. + ## Red-Green Tests are written first. A failing test means the seam is not satisfied; the config change that makes it pass is the only acceptable fix. No `--no-verify`, no deleting tests without approval. diff --git a/scripts/test-changed.sh b/scripts/test-changed.sh new file mode 100755 index 0000000..31406a0 --- /dev/null +++ b/scripts/test-changed.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# Run only the Bats tests affected by the staged paths. +# +# Usage: +# scripts/test-changed.sh # inspect the staged commit +# scripts/test-changed.sh --list # print selected test files +# scripts/test-changed.sh --paths FILE ... +# +# --paths is intended for tests and local experimentation. The normal mode is +# deliberately staged-only so a pre-commit hook cannot accidentally validate +# an unrelated dirty working-tree file. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +LIST_ONLY=0 +USE_PATHS=0 +declare -a CHANGED=() + +die() { + printf 'ERROR: %s\n' "$*" >&2 + exit 2 +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --list) + LIST_ONLY=1 + shift + ;; + --paths) + USE_PATHS=1 + shift + while [ "$#" -gt 0 ] && [[ "$1" != --* ]]; do + CHANGED+=("$1") + shift + done + ;; + --) + shift + CHANGED+=("$@") + break + ;; + *) + die "unknown option '$1' (use --list or --paths FILE ...)" + ;; + esac +done + +cd "$REPO_ROOT" +if [ "$USE_PATHS" -eq 0 ]; then + while IFS= read -r -d '' path; do + CHANGED+=("$path") + done < <(git diff --cached --name-only -z --diff-filter=ACMRTUXB) +fi + +[ "${#CHANGED[@]}" -gt 0 ] || { + printf 'No staged paths; no focused tests to run.\n' + exit 0 +} + +declare -a TESTS=() +add_test() { + local test="$1" + local existing + for existing in "${TESTS[@]:-}"; do + [ "$existing" != "$test" ] || return 0 + done + TESTS+=("$test") +} + +for path in "${CHANGED[@]}"; do + case "$path" in + tests/*.bats) + add_test "$path" + ;; + tests/helpers.bash|tests/run.sh) + die "$path is shared test infrastructure; run ./tests/run.sh explicitly" + ;; + dankserver/compose.yaml|dankclaw/compose.yaml) + add_test tests/compose_validity.bats + add_test tests/env_consistency.bats + ;; + dankserver/Caddyfile|dankclaw/Caddyfile) + add_test tests/caddyfile.bats + ;; + dankserver/.env.example|dankclaw/.env.example) + add_test tests/env_consistency.bats + ;; + shared/homepage/*) + add_test tests/homepage_caddy.bats + add_test tests/homepage_config.bats + add_test tests/homepage_deploy.bats + add_test tests/homepage_pages.bats + add_test tests/homepage_checker.bats + ;; + shared/searxng/*) + add_test tests/searxng_limiter.bats + add_test tests/searxng_quadlet.bats + ;; + dankclaw/*.container|dankclaw/*.network) + add_test tests/open_webui_quadlet.bats + add_test tests/pihole.bats + add_test tests/searxng_quadlet.bats + ;; + scripts/mount-entropy.sh) + add_test tests/mount_entropy.bats + ;; + scripts/frank-default-large.env|scripts/serve-frank-large.sh) + add_test tests/frank_large_config.bats + add_test tests/llm_secrets.bats + add_test tests/scripts_syntax.bats + ;; + scripts/frank-default-small.env|scripts/serve-frank-small.sh) + add_test tests/frank_small_config.bats + add_test tests/llm_secrets.bats + add_test tests/scripts_syntax.bats + ;; + scripts/*.service) + add_test tests/llm_units.bats + add_test tests/scripts_syntax.bats + ;; + scripts/*) + add_test tests/scripts_syntax.bats + ;; + docs/*) + add_test tests/docs_completeness.bats + ;; + *) + die "no focused-test mapping for '$path'; add one before committing" + ;; + esac +done + +if [ "$LIST_ONLY" -eq 1 ]; then + printf '%s\n' "${TESTS[@]}" + exit 0 +fi + +command -v bats >/dev/null 2>&1 || die 'bats is not installed' +printf 'Running focused tests for %d changed path(s):\n' "${#CHANGED[@]}" +printf ' %s\n' "${TESTS[@]}" +bats --print-output-on-failure "${TESTS[@]}" diff --git a/tests/changed_tests.bats b/tests/changed_tests.bats new file mode 100644 index 0000000..0fc31e7 --- /dev/null +++ b/tests/changed_tests.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats +# Seam: the changed-test wrapper selects only the tests associated with +# staged paths and refuses to silently run the entire suite. + +load helpers + +@test "changed-test wrapper is executable and uses staged diff by default" { + [ -x "$REPO_ROOT/scripts/test-changed.sh" ] + run grep -F 'git diff --cached' "$REPO_ROOT/scripts/test-changed.sh" + [ "$status" -eq 0 ] +} + +@test "changed-test wrapper maps a compose change to compose and environment tests" { + run bash "$REPO_ROOT/scripts/test-changed.sh" --list --paths dankserver/compose.yaml + [ "$status" -eq 0 ] + [[ "$output" == *"tests/compose_validity.bats"* ]] + [[ "$output" == *"tests/env_consistency.bats"* ]] + [[ "$output" != *"tests/homepage_pages.bats"* ]] +} + +@test "changed-test wrapper maps a service script to its focused config tests" { + run bash "$REPO_ROOT/scripts/test-changed.sh" --list --paths scripts/serve-frank-small.sh + [ "$status" -eq 0 ] + [[ "$output" == *"tests/scripts_syntax.bats"* ]] + [[ "$output" == *"tests/frank_small_config.bats"* ]] + [[ "$output" != *"tests/frank_large_config.bats"* ]] +}