Skip to content

Commit a3a670d

Browse files
authored
perf(runner): build the provider map once per file, not twice (#817)
1 parent a3c82ce commit a3a670d

4 files changed

Lines changed: 22 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- `--jobs auto` / `-j auto` caps parallel concurrency at the CPU core count (portable across Linux/macOS/BSD); the default stays unlimited (#766)
1616

1717
### Changed
18+
- Faster test runs: the data-provider map is built once per file in the main shell (the header's test count reads a return slot instead of a `$(...)` capture, so the cache survives for the runner) — one awk scan per file instead of two
1819
- Faster test runs: stripping ANSI codes from short colored strings is pure bash now, so aligning the per-test execution time (shown on systems with a fork-free clock, e.g. Linux) no longer forks `sed` once per passing test
1920
- Faster parallel runs: publishing each test's result file no longer forks `basename`, `mkdir` and an `echo | tr | sed` pipeline per test — the suite dir name is parameter expansion, the dir is pre-created once per file before workers spawn, and arg sanitizing is skipped without provider args (a 10-test parallel run dropped from 61 to 21 forks). No behaviour change
2021
- Faster test runs: listing all defined functions uses the `compgen -A function` builtin instead of forking `declare -F | awk` (three call sites; 5 -> 3 `awk` forks per test file). No behaviour change

src/console_header.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ function bashunit::console_header::print_version() {
3939
# Skip counting in parallel+simple mode for faster startup
4040
total_tests=0
4141
else
42-
total_tests=$(bashunit::helper::find_total_tests "$filter" "$@")
42+
# Read via the return slot, not $(...): the capture subshell would discard
43+
# the provider-map cache find_total_tests builds per file, forcing the
44+
# runner to re-scan each file with a second awk fork.
45+
bashunit::helper::find_total_tests "$filter" "$@" >/dev/null
46+
total_tests=$_BASHUNIT_HELPER_TOTAL_TESTS_OUT
4347
fi
4448

4549
if bashunit::env::is_header_ascii_art_enabled; then

src/helpers.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,15 @@ function bashunit::helper::get_latest_tag() {
526526
head -n 1
527527
}
528528

529+
# Also written by find_total_tests so a main-shell caller can read the count
530+
# without a $() capture (which would discard the provider-map cache built here).
531+
_BASHUNIT_HELPER_TOTAL_TESTS_OUT=0
532+
529533
function bashunit::helper::find_total_tests() {
530534
local filter=${1:-}
531535
shift || true
532536

537+
_BASHUNIT_HELPER_TOTAL_TESTS_OUT=0
533538
if [ $# -eq 0 ]; then
534539
echo 0
535540
return
@@ -543,6 +548,12 @@ function bashunit::helper::find_total_tests() {
543548
continue
544549
fi
545550

551+
# Build the provider map in THIS shell before the counting subshell: the
552+
# subshell inherits it (its own build call becomes a cache hit), and when
553+
# the caller runs in the main shell the runner's later build for the same
554+
# file is a cache hit too — one awk scan per file instead of two.
555+
bashunit::helper::build_provider_map "$file"
556+
546557
local file_count
547558
file_count=$( (
548559
# shellcheck source=/dev/null
@@ -590,6 +601,7 @@ function bashunit::helper::find_total_tests() {
590601
total_count=$((total_count + file_count))
591602
done
592603

604+
_BASHUNIT_HELPER_TOTAL_TESTS_OUT=$total_count
593605
echo "$total_count"
594606
}
595607

tests/acceptance/bashunit_run_forks_test.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ function test_running_a_test_file_does_not_fork_sort() {
126126

127127
# Regression guard: listing all defined functions must use the `compgen -A
128128
# function` builtin, not a `declare -F | awk` fork. The remaining awk budget of
129-
# a plain run is the per-file file scans (data-provider map, which runs in both
130-
# the counting subshell and the runner, plus the duplicate-name check).
129+
# a single-file run is one data-provider scan (built once in the main shell so
130+
# the header-count subshell and the runner both hit the cache) plus the
131+
# duplicate-name check.
131132
function test_running_a_test_file_stays_within_the_awk_fork_budget() {
132133
if bashunit::check_os::is_windows; then
133134
bashunit::skip "PATH shims are unreliable under Git Bash" && return
@@ -155,7 +156,7 @@ function test_running_a_test_file_stays_within_the_awk_fork_budget() {
155156
awk_forks="$(grep -c . "$count_file" || true)"
156157
fi
157158

158-
assert_less_or_equal_than 3 "$awk_forks"
159+
assert_less_or_equal_than 2 "$awk_forks"
159160
}
160161

161162
# Regression guard: a run must remove its run-output scratch directory on exit.

0 commit comments

Comments
 (0)