You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Drop the throwaway $() subshell that bashunit::helper::find_total_tests uses to source every test file just to print "Running N tests" in the header. Compute the header total with a lightweight static scan × the already-cached provider multipliers, so each file is parsed once (at run time) instead of twice (pre-count + run).
📍 Where
bashunit::helper::find_total_tests — src/helpers.sh:533-575. It builds the provider map in the main shell first (so the counting subshell + later runner both hit the cache — the provider-map double-build was already fixed in perf(runner): build the provider map once per file, not twice #817), then enters a subshell that sources each file to discover/count test functions and expand provider counts.
The runner then sources each file again in the main shell at run time (.claude/rules/architecture-map.md → "source "$test_file" in the MAIN shell").
Header consumer: bashunit::console_header::print_header (reads the total via the return slot written by find_total_tests, src/helpers.sh:529).
💡 Rationale
Sourcing is parse-time-dominated (perf-fork-budget.md: "parse time dominates, file opens don't"), so parsing all N files an extra time is the cost. Test discovery does not need the file executed — it needs the test_* function names, which a static scan can extract without a source, and the provider multipliers, which already come from the cached provider map (one awk pass per file, bashunit::helper::build_provider_map, src/helpers.sh:406).
🔧 Approach
Replace the sourcing subshell with a static per-file scan of test-function declarations, e.g. match ^[[:space:]]*(function[[:space:]]+)?test_[A-Za-z0-9_]+[[:space:]]*\(\). Prefer a single pass (the existing awk/grep file-scan machinery already used for duplicate-check and provider-map — reuse it rather than adding a new fork class).
Multiply each discovered test_* by its provider row count from the cached provider map (bashunit::helper::provider_for_function / the map globals). Functions with no provider count as 1.
Write the total into the existing return slot (_BASHUNIT_HELPER_TOTAL_TESTS_OUT) — keep the public contract identical.
✅ Acceptance criteria
Header total matches the current behaviour for: plain tests, data-provider tests (single + multi-row), filtered runs (--filter/--tag/--exclude-tag), and empty files.
Each test file is parsed once per run (the pre-count subshell that sources files is gone).
No new per-file fork class beyond the scan already used for discovery.
🧪 TDD / measurement
RED: add a tests/unit/helpers_test.sh case pinning find_total_tests output for a fixture with N plain tests + a provider test with K rows → expects N + K; assert it drives the header count. Then a fork/parse census showing the pre-count no longer sources files.
Guard the known trade-off: dynamically-defined tests (a test_* created at source time by a loop/eval rather than a literal function test_…()) will not be counted by a static scan. Confirm the repo's own suite has none, document the limitation in the function header, and if any fixture relies on it, keep a source-based fallback gated behind detection of non-literal definitions.
Full gate: ./bashunit tests/, ./bashunit --parallel --simple --strict tests/, make sa, make lint. Regenerate any header snapshot if counts render identically (they should).
⛓️ Constraints
Bash 3.0+ (.claude/rules/bash-style.md). Reuse pinned $GREP/$AWK; don't add a while read loop over big files (measured slower than one awk in perf-fork-budget.md).
Update perf-fork-budget.md cold-start notes if the per-run parse count drops.
📈 Impact
Removes N extra file parses per run (N = file count). Cold-ish path (once per run), most visible on large suites (e.g. bashunit's own 63-file unit suite).
⚠️ Note
Medium confidence / medium risk because it changes the counting mechanism from dynamic (source) to static (scan). Land behind tests that pin provider-multiplier and filter behaviour. If static discovery proves lossy for any supported pattern, close as wontfix rather than shipping a wrong header count.
🎯 Goal
Drop the throwaway
$()subshell thatbashunit::helper::find_total_testsuses to source every test file just to print "Running N tests" in the header. Compute the header total with a lightweight static scan × the already-cached provider multipliers, so each file is parsed once (at run time) instead of twice (pre-count + run).📍 Where
bashunit::helper::find_total_tests—src/helpers.sh:533-575. It builds the provider map in the main shell first (so the counting subshell + later runner both hit the cache — the provider-map double-build was already fixed in perf(runner): build the provider map once per file, not twice #817), then enters a subshell that sources each file to discover/count test functions and expand provider counts..claude/rules/architecture-map.md→ "source "$test_file" in the MAIN shell").bashunit::console_header::print_header(reads the total via the return slot written byfind_total_tests,src/helpers.sh:529).💡 Rationale
Sourcing is parse-time-dominated (
perf-fork-budget.md: "parse time dominates, file opens don't"), so parsing all N files an extra time is the cost. Test discovery does not need the file executed — it needs thetest_*function names, which a static scan can extract without a source, and the provider multipliers, which already come from the cached provider map (oneawkpass per file,bashunit::helper::build_provider_map,src/helpers.sh:406).🔧 Approach
^[[:space:]]*(function[[:space:]]+)?test_[A-Za-z0-9_]+[[:space:]]*\(\). Prefer a single pass (the existingawk/grepfile-scan machinery already used for duplicate-check and provider-map — reuse it rather than adding a new fork class).test_*by its provider row count from the cached provider map (bashunit::helper::provider_for_function/ the map globals). Functions with no provider count as 1._BASHUNIT_HELPER_TOTAL_TESTS_OUT) — keep the public contract identical.✅ Acceptance criteria
--filter/--tag/--exclude-tag), and empty files.🧪 TDD / measurement
tests/unit/helpers_test.shcase pinningfind_total_testsoutput for a fixture with N plain tests + a provider test with K rows → expectsN + K; assert it drives the header count. Then a fork/parse census showing the pre-count no longer sources files.test_*created at source time by a loop/eval rather than a literalfunction test_…()) will not be counted by a static scan. Confirm the repo's own suite has none, document the limitation in the function header, and if any fixture relies on it, keep a source-based fallback gated behind detection of non-literal definitions../bashunit tests/,./bashunit --parallel --simple --strict tests/,make sa,make lint. Regenerate any header snapshot if counts render identically (they should).⛓️ Constraints
.claude/rules/bash-style.md). Reuse pinned$GREP/$AWK; don't add awhile readloop over big files (measured slower than oneawkinperf-fork-budget.md).perf-fork-budget.mdcold-start notes if the per-run parse count drops.📈 Impact
Removes N extra file parses per run (N = file count). Cold-ish path (once per run), most visible on large suites (e.g. bashunit's own 63-file unit suite).
Medium confidence / medium risk because it changes the counting mechanism from dynamic (source) to static (scan). Land behind tests that pin provider-multiplier and filter behaviour. If static discovery proves lossy for any supported pattern, close as
wontfixrather than shipping a wrong header count.