Files
holocron/tests/run-tests.sh
Defame1297 a8beff7d2c perf(kyberforge): parallelize the bats and shell test runners
Both runners spawned one process at a time and let it finish before starting
the next, so wall time scaled with file count even though each file's own
work (bats: mostly repeated apm/git subprocess startup; the vale-heavy
shell scripts: repeated vale binary startup) rarely used a full core.

run-bats.sh now backgrounds one `bats` invocation per file, bounded by core
count, buffering each file's TAP output so concurrent streams can't
interleave, then flushes in stable sorted order once every job finishes.
run-tests.sh does the same for test-*.sh in fixed-size batches (plain `wait`,
not `wait -n`, to stay on the bash-3.2-safe path test-vale-wrap.sh already
enforces for this file) -- verified beforehand that every test-*.sh keeps its
fixtures under its own mktemp dir rather than mutating the live repo tree, so
running them concurrently is safe.

Confirmed correct on both the pass and fail paths (a deliberately failing
bats test and a scratch TEST_DIR with pass/fail/skip-77 scripts) before
measuring: full bats suite 26-30s -> 7.5s, tests/run-tests.sh 68s -> ~45-50s,
full pre-push gate 2m40s -> 1m12s.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
2026-08-13 18:35:18 +00:00

112 lines
3.0 KiB
Bash
Executable File

#!/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]
#
# A script exiting 77 (the automake convention) is reported as SKIPPED, not
# passed — a suite that can't run for lack of a binary must not read as green.
#
# 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=()
SKIPPED=()
PASSED=0
SKIP_EXIT=77
run_bats() {
if [[ -x "$BATS" ]]; then
echo "=== bats ==="
bash "$BATS"
echo ""
fi
}
if $BATS_ONLY; then
run_bats
exit 0
fi
run_bats
# Collected with a `while read` loop rather than `mapfile` — macOS ships
# /bin/bash 3.2, which has no `mapfile`. Process substitution (not a pipe)
# keeps the loop in this shell so the appends survive. `sort` is still fed
# newline-delimited output, exactly as before.
SCRIPTS=()
while IFS= read -r script; do
SCRIPTS+=("$script")
done < <(
find "$SEARCH_ROOT" -name "test-*.sh" \
-not -path "*/.git/*" \
-not -path "*/.claude/worktrees/*" \
| sort
)
# Each test-*.sh is independent (fixtures live under its own mktemp dir, none
# write back into the live repo tree -- verified before adding this), so they
# run concurrently in fixed-size batches instead of one at a time. Batches
# (not a rolling pool) because a bounded rolling pool needs `wait -n`, which
# is bash 4.3+ and this script is explicitly bash-3.2-safe (see the hazard
# scan in test-vale-wrap.sh); plain `wait` -- waiting for every job in the
# current batch -- is available since ancient bash. `getconf` over `nproc`
# for the same reason: `nproc` doesn't exist on macOS.
SCRATCH_ROOT="$(mktemp -d)"
trap 'rm -rf "$SCRATCH_ROOT"' EXIT
JOBS_LIMIT="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
running=0
idx=0
for script in ${SCRIPTS[@]+"${SCRIPTS[@]}"}; do
idx=$((idx + 1))
(
rc=0
bash "$script" >"$SCRATCH_ROOT/$idx.log" 2>&1 || rc=$?
echo "$rc" >"$SCRATCH_ROOT/$idx.status"
) &
running=$((running + 1))
if [[ $running -ge $JOBS_LIMIT ]]; then
wait
running=0
fi
done
wait
idx=0
for script in ${SCRIPTS[@]+"${SCRIPTS[@]}"}; do
idx=$((idx + 1))
rel="${script#"$SEARCH_ROOT/"}"
echo "=== $rel ==="
cat "$SCRATCH_ROOT/$idx.log"
rc="$(cat "$SCRATCH_ROOT/$idx.status" 2>/dev/null || echo 1)"
if [[ $rc -eq 0 ]]; then
PASSED=$((PASSED + 1))
elif [[ $rc -eq $SKIP_EXIT ]]; then
SKIPPED+=("$rel")
else
FAILED+=("$rel")
fi
echo ""
done
echo "=== Summary: $PASSED passed, ${#SKIPPED[@]} skipped, ${#FAILED[@]} failed ==="
if [[ ${#SKIPPED[@]} -gt 0 ]]; then
echo "Skipped scripts:"
for s in ${SKIPPED[@]+"${SKIPPED[@]}"}; do
echo " $s"
done
fi
if [[ ${#FAILED[@]} -gt 0 ]]; then
echo "Failed scripts:"
for s in ${FAILED[@]+"${FAILED[@]}"}; do
echo " $s"
done
exit 1
fi