#!/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" ;; .githooks/*) add_test tests/changed_tests.bats ;; dankserver/compose.yaml|dankclaw/compose.yaml) add_test tests/compose_validity.bats add_test tests/env_consistency.bats add_test tests/deployment_scoping.bats ;; dankserver/Caddyfile|dankclaw/Caddyfile) add_test tests/caddyfile.bats add_test tests/deployment_scoping.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 add_test tests/deployment_scoping.bats ;; shared/searxng/*) add_test tests/searxng_limiter.bats add_test tests/searxng_quadlet.bats add_test tests/deployment_scoping.bats ;; # Caddy left compose.yaml on 2026-08-26 for a quadlet; caddyfile.bats # now asserts against the quadlet itself. Route every change under # dankclaw/caddy/ to both seams (this block must precede the # dankclaw/*.container glob, which would otherwise swallow the unit). dankclaw/caddy/*) add_test tests/caddy_quadlet.bats add_test tests/caddyfile.bats add_test tests/docs_completeness.bats add_test tests/deployment_scoping.bats ;; dankclaw/*.container|dankclaw/*.network) add_test tests/open_webui_quadlet.bats add_test tests/pihole.bats add_test tests/searxng_quadlet.bats add_test tests/deployment_scoping.bats ;; # Anubis is a quadlet + Caddyfile pair: the chain (public listener -> # anubis:8923 -> internal :8016 listener) spans both, so any change # under the service dir must re-run the caddyfile seam too. This case # precedes the dankclaw/*/quadlet/*.container fallback below, which # would otherwise swallow it into unrelated seams. dankclaw/anubis/*) add_test tests/anubis.bats add_test tests/caddyfile.bats add_test tests/homepage_caddy.bats add_test tests/deployment_scoping.bats ;; # Quadlets live under dankclaw//quadlet/ (reorganized out of the # top-level dankclaw/*.container globs above). Map the whole subtree so # changes to the pihole, open-webui, searxng, and gitea quadlets hit # their seam tests. The pihole-specific block must come first: shell # case matches the first pattern, and dankclaw/pihole/quadlet/*.container # also satisfies the dankclaw/*/quadlet/*.container fallback. dankclaw/pihole/*) add_test tests/pihole.bats add_test tests/docs_completeness.bats add_test tests/deployment_scoping.bats ;; dankclaw/*/quadlet/*.container|dankclaw/*/quadlet/*.network) add_test tests/open_webui_quadlet.bats add_test tests/pihole.bats add_test tests/searxng_quadlet.bats add_test tests/docs_completeness.bats add_test tests/deployment_scoping.bats ;; scripts/deploy.sh|scripts/deploy-remote.sh|scripts/deploy-plan.py|scripts/deploy-*-quadlet.sh) add_test tests/deployment_flow.bats add_test tests/deployment_scoping.bats add_test tests/scripts_syntax.bats ;; scripts/mount-entropy.sh) add_test tests/mount_entropy.bats ;; scripts/frank-default-large.env|scripts/serve-frank-large.sh) add_test tests/llm_units.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/llm_units.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 add_test tests/deployment_scoping.bats ;; scripts/*) add_test tests/scripts_syntax.bats ;; serverAppConfigs-docs/*) add_test tests/docs_completeness.bats add_test tests/deployment_scoping.bats ;; dankclaw/hermes/*) add_test tests/docs_completeness.bats ;; README.md|AGENTS.md) # Project guidance changes can affect the documented deployment # contract; validate the docs vault and scoped deployment seams. add_test tests/docs_completeness.bats add_test tests/deployment_scoping.bats ;; dankclaw/comfyui/*) add_test tests/docs_completeness.bats add_test tests/deployment_scoping.bats ;; dankclaw/udev/*) # Host-level udev rules are applied by manual sudo install + udevadm # trigger, not dispatched as a service; nothing here is bats-testable. ;; .AGENTTRASH/*) # Trash moves are not testable; skip. ;; .gitignore|.env.example) # Ignore/ignore-list changes change nothing at runtime; not testable. ;; old/scripts/*|old/scripts/**) add_test tests/scripts_syntax.bats ;; report/*) # Frozen audit reports are point-in-time artifacts; not testable. ;; *) 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[@]}"