scripts/voice-packs.env carries the decisions worth a diff and nothing else: the pack list, each voice's source file, crop offset, crop length and pinned source digest, the extractor path with its engine repo and commit, and the artifact arithmetic. Paths and numbers only — the pack directories themselves stay host state under /home/dank/voices. tests/voice_pack_unit.bats pins behaviour, from inputs to observable output: a crop inside the first ten seconds exits nonzero naming the invariant and creates nothing, a legal one builds the whole pack, the reference comes out 24 kHz mono inside 18-25 s (ffprobe, on a locally generated tone), the manifest round-trips through jq with offsets and a sha256 that matches the source bytes, a re-run without --force leaves every byte untouched, --force retires rather than deletes, --dry-run needs no extractor at all, and a stub extractor that changed its artifact size cannot publish a voice. No engine binary, no GPU, no network, no fleet host: qwen-codec is stood in by a stub with the real tool's I/O shape, the way frank_tts_unit.bats drives its backends. test-changed.sh routes both new paths to that seam instead of letting them land on the generic scripts/* syntax fallback.
326 lines
13 KiB
Bash
Executable File
326 lines
13 KiB
Bash
Executable File
#!/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
|
|
;;
|
|
tests/e2e/*)
|
|
add_test tests/e2e_scaffold.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
|
|
;;
|
|
# The five observability containers left compose for Quadlet units on
|
|
# 2026-09-11. Their seam pins boot persistence and the loopback-only
|
|
# publish posture that keeps them off every host interface. This case
|
|
# precedes the dankclaw/*/quadlet/*.container fallback below, which
|
|
# would otherwise swallow it into unrelated seams.
|
|
dankclaw/observability/*)
|
|
add_test tests/observability_quadlet.bats
|
|
add_test tests/caddyfile.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/docs_completeness.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
|
|
;;
|
|
# open-notebook is the fleet's podcast generator: an app quadlet, the
|
|
# SurrealDB quadlet behind it, the private network between them, and the
|
|
# one env file both units read. Like the caddy block above, this case
|
|
# has to precede the dankclaw/*.container glob, whose `*` crosses `/`
|
|
# and would otherwise swallow dankclaw/open-notebook/quadlet/*.
|
|
dankclaw/open-notebook/*|scripts/open-notebook.env)
|
|
add_test tests/open_notebook.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
|
|
;;
|
|
# frank-dragoon is a rootless quadlet again since 2026-09-16 (the
|
|
# halogen-flash-server restore). Its unit carries the fleet's GPU and
|
|
# podman posture, so it gets its own seam rather than the generic
|
|
# fallback below.
|
|
dankclaw/frank-dragoon/*)
|
|
add_test tests/frank_dragoon_unit.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/docs_completeness.bats
|
|
;;
|
|
# Quadlets live under dankclaw/<svc>/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
|
|
;;
|
|
# Archive material: a path under any .AGENTTRASH/ directory is
|
|
# retired-by-policy (deploy-plan.py plans nothing from it), so it
|
|
# selects no focused test. It still needs its own case: without one
|
|
# the wrapper refuses retirement commits, whose trash moves show up
|
|
# as additions, with "no focused-test mapping".
|
|
.AGENTTRASH/*|*/.AGENTTRASH/*)
|
|
;;
|
|
# frank-archon is llama.cpp (the unsloth b11030-mix prebuilt) on the
|
|
# two R9700s as a ROOT unit — the 2026-09-26 swap that replaced the
|
|
# Lucebox DS4.1 quadlet on :7770. Its env, unit and serve script all
|
|
# live in scripts/, which is why this case has to precede the generic
|
|
# scripts/*.service and scripts/* fallbacks below.
|
|
scripts/frank-archon.*|scripts/serve-frank-archon.sh)
|
|
add_test tests/frank_archon_unit.bats
|
|
add_test tests/llm_units.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
# frank-dragoon's chat template lives in scripts/ (moved there on
|
|
# 2026-09-26 when its old home was retired): a template edit changes
|
|
# what the live endpoint renders, so it selects the large unit seam.
|
|
scripts/qwen-sharp-v22.4.0.jinja)
|
|
add_test tests/frank_dragoon_unit.bats
|
|
;;
|
|
# frank-zealot is llama.cpp on the 3090 as a ROOT unit: a native CUDA
|
|
# binary, so no generated quadlet is involved. Its env, unit and serve
|
|
# script all live in scripts/, which is why this case has to precede
|
|
# the generic scripts/*.service and scripts/* fallbacks below.
|
|
scripts/frank-zealot.*|scripts/serve-frank-zealot.sh)
|
|
add_test tests/frank_zealot_unit.bats
|
|
add_test tests/llm_units.bats
|
|
add_test tests/deployment_scoping.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
|
|
;;
|
|
# The voice-pack builder is the only thing that enforces the 10-second
|
|
# source-material invariant and the artifact sizes the packs are pinned
|
|
# to, so it selects its own seam rather than landing on the generic
|
|
# scripts/* syntax fallback. This case has to precede that fallback.
|
|
scripts/build-voice-pack.sh|scripts/voice-packs.env)
|
|
add_test tests/voice_pack_unit.bats
|
|
add_test tests/scripts_syntax.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
|
|
;;
|
|
# loki and alloy images are built from this repo's Containerfiles; the
|
|
# loki one carries the healthcheck command (see its comment), so both
|
|
# seams read it.
|
|
dankclaw/loki/*|dankclaw/alloy/*)
|
|
add_test tests/observability_quadlet.bats
|
|
add_test tests/caddyfile.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
|
|
;;
|
|
# Cutover-era dankserver quadlet units (romm trio, jellyfin, calibre-web,
|
|
# copyparty, plex, tautulli): the seam checks boot-persistence +
|
|
# v1-generator contract; deploy-flow seams guard the deployer routing.
|
|
dankserver/romm/*|dankserver/jellyfin/*|dankserver/calibre-web/*|dankserver/copyparty/*|dankserver/plex/*|dankserver/tautulli/*)
|
|
add_test tests/dankserver_quadlet.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# FrankBook-Pro units live inside the podman machine VM; the seam
|
|
# pins boot persistence, the gluetun netns invariant, and the
|
|
# plan-locked volume layout. Deployer changes route here too.
|
|
frankbook-pro/*)
|
|
add_test tests/frankbook_quadlet.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# Seerr unit on dankclaw: boot persistence, loopback-only port
|
|
# posture, and the deployer routing that installs it.
|
|
dankclaw/seerr/*)
|
|
add_test tests/seerr_quadlet.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.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
|
|
|
|
# Mapped-but-untestable paths (udev rules, trash moves) legitimately produce an
|
|
# empty selection; that is a pass, not a bats usage error.
|
|
[ "${#TESTS[@]}" -gt 0 ] || exit 0
|
|
|
|
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[@]}"
|