From 7697acb711c0ffa96511dcd7c444070686995e9a Mon Sep 17 00:00:00 2001 From: Tw93 Date: Sat, 14 Mar 2026 09:19:54 +0800 Subject: [PATCH] test: speed up bats execution --- scripts/test.sh | 24 +++++++++++++++++++----- tests/clean_system_caches.bats | 12 ++++++------ tests/cli.bats | 12 ++++++++++-- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 2cf6fab..6de57ba 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -98,12 +98,26 @@ if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then if [[ -t 1 && "${TERM:-}" != "dumb" ]]; then use_color=true fi + + # Enable parallel execution across test files when GNU parallel is available. + # Cap at 6 jobs to balance speed vs. system load during CI. + bats_opts=() + if command -v parallel > /dev/null 2>&1 && bats --help 2>&1 | grep -q -- "--jobs"; then + _ncpu="$(sysctl -n hw.logicalcpu 2>/dev/null || nproc 2>/dev/null || echo 4)" + _jobs="$(( _ncpu > 6 ? 6 : (_ncpu < 2 ? 2 : _ncpu) ))" + # --no-parallelize-within-files ensures each test file's tests run + # sequentially (they share a $HOME set by setup_file and are not safe + # to run concurrently). Parallelism is only across files. + bats_opts+=("--jobs" "$_jobs" "--no-parallelize-within-files") + unset _ncpu _jobs + fi + if bats --help 2>&1 | grep -q -- "--formatter"; then formatter="${BATS_FORMATTER:-pretty}" if [[ "$formatter" == "tap" ]]; then if $use_color; then esc=$'\033' - if bats --formatter tap "$@" | + if bats "${bats_opts[@]}" --formatter tap "$@" | sed -e "s/^ok /${esc}[32mok ${esc}[0m /" \ -e "s/^not ok /${esc}[31mnot ok ${esc}[0m /"; then report_unit_result 0 @@ -111,7 +125,7 @@ if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then report_unit_result 1 fi else - if bats --formatter tap "$@"; then + if bats "${bats_opts[@]}" --formatter tap "$@"; then report_unit_result 0 else report_unit_result 1 @@ -119,7 +133,7 @@ if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then fi else # Pretty format for local development - if bats --formatter "$formatter" "$@"; then + if bats "${bats_opts[@]}" --formatter "$formatter" "$@"; then report_unit_result 0 else report_unit_result 1 @@ -128,7 +142,7 @@ if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then else if $use_color; then esc=$'\033' - if bats --tap "$@" | + if bats "${bats_opts[@]}" --tap "$@" | sed -e "s/^ok /${esc}[32mok ${esc}[0m /" \ -e "s/^not ok /${esc}[31mnot ok ${esc}[0m /"; then report_unit_result 0 @@ -136,7 +150,7 @@ if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then report_unit_result 1 fi else - if bats --tap "$@"; then + if bats "${bats_opts[@]}" --tap "$@"; then report_unit_result 0 else report_unit_result 1 diff --git a/tests/clean_system_caches.bats b/tests/clean_system_caches.bats index bfd2930..fbd57e7 100644 --- a/tests/clean_system_caches.bats +++ b/tests/clean_system_caches.bats @@ -72,10 +72,10 @@ setup() { mkdir -p "$test_cache" run bash -c " - run_with_timeout() { shift; \"\$@\"; } - export -f run_with_timeout source '$PROJECT_ROOT/lib/core/common.sh' source '$PROJECT_ROOT/lib/clean/caches.sh' + run_with_timeout() { shift; \"\$@\"; } + export -f run_with_timeout clean_service_worker_cache 'TestBrowser' '$test_cache' " [ "$status" -eq 0 ] @@ -89,6 +89,10 @@ setup() { mkdir -p "$test_cache/def456_https_example.com_0" run bash -c " + export DRY_RUN=true + export PROTECTED_SW_DOMAINS=(capcut.com photopea.com) + source '$PROJECT_ROOT/lib/core/common.sh' + source '$PROJECT_ROOT/lib/clean/caches.sh' run_with_timeout() { local timeout=\"\$1\" shift @@ -105,10 +109,6 @@ setup() { \"\$@\" } export -f run_with_timeout - export DRY_RUN=true - export PROTECTED_SW_DOMAINS=(capcut.com photopea.com) - source '$PROJECT_ROOT/lib/core/common.sh' - source '$PROJECT_ROOT/lib/clean/caches.sh' clean_service_worker_cache 'TestBrowser' '$test_cache' " [ "$status" -eq 0 ] diff --git a/tests/cli.bats b/tests/cli.bats index 2da29d8..6afc025 100644 --- a/tests/cli.bats +++ b/tests/cli.bats @@ -7,20 +7,28 @@ setup_file() { ORIGINAL_HOME="${HOME:-}" export ORIGINAL_HOME + # Capture real GOCACHE before HOME is replaced with a temp dir. + # Without this, go build would use $HOME/Library/Caches/go-build inside the + # temp dir (empty), causing a full cold rebuild on every test run (~6s). + ORIGINAL_GOCACHE="$(go env GOCACHE 2>/dev/null || true)" + export ORIGINAL_GOCACHE + HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-cli-home.XXXXXX")" export HOME mkdir -p "$HOME" # Build Go binaries from current source for JSON tests. - # Point GOPATH/GOMODCACHE at the real home so go build doesn't write - # module caches into the fake HOME under tests/. + # Point GOPATH/GOMODCACHE/GOCACHE at the real home so go build can reuse + # the module and build caches rather than doing a cold rebuild every run. if command -v go > /dev/null 2>&1; then ANALYZE_BIN="$(mktemp "${TMPDIR:-/tmp}/analyze-go.XXXXXX")" STATUS_BIN="$(mktemp "${TMPDIR:-/tmp}/status-go.XXXXXX")" GOPATH="${ORIGINAL_HOME}/go" GOMODCACHE="${ORIGINAL_HOME}/go/pkg/mod" \ + GOCACHE="${ORIGINAL_GOCACHE}" \ go build -o "$ANALYZE_BIN" "$PROJECT_ROOT/cmd/analyze" 2>/dev/null GOPATH="${ORIGINAL_HOME}/go" GOMODCACHE="${ORIGINAL_HOME}/go/pkg/mod" \ + GOCACHE="${ORIGINAL_GOCACHE}" \ go build -o "$STATUS_BIN" "$PROJECT_ROOT/cmd/status" 2>/dev/null export ANALYZE_BIN STATUS_BIN fi