Problem
Most core assertions join their haystack args with a command-substitution fork just to build the compared string:
actual=$(printf '%s\n' "${actual_arr[@]}")
This forks a subshell on every assertion call. Micro-benchmark (20 000 iterations, Bash 3.2):
$(printf '%s\n' ...) : 10.44s
IFS join (fork-free) : 0.09s # ~100x faster
On assertion-heavy suites this is the dominant per-assertion cost. assert_contains already matches with a native case; only the join in front of it still forks.
Fix
Replace the fork with a Bash-3.0-safe, fork-free IFS join:
local IFS=$'\n'
actual="${actual_arr[*]}"
Affected sites (src/assert.sh)
All actual=$(printf '%s\n' "${actual_arr[@]}") occurrences — currently ~8, including assert_contains, assert_not_contains, assert_matches, assert_not_matches, and the array-shaped assertions around lines 277, 330, 353, 379, 656, 701, 726. grep -nE '=\$\(printf .%s.n. "\$\{actual_arr' src/assert.sh lists them.
IMPORTANT — per-site semantic check (do NOT blanket-sed)
The IFS join differs from printf '%s\n' in two ways:
- No trailing newline after the last element.
- Elements are joined between only.
- Safe where
actual feeds a substring case, a grep/regex match, or is only shown on failure (trailing-newline irrelevant): assert_contains, assert_not_contains, assert_matches, assert_not_matches.
- Verify carefully any site where
actual is exact-compared or its exact byte form is asserted in a snapshot — if a test pins the trailing newline, either keep that site or adjust deliberately.
Convert only the sites you can prove are behavior-identical. Leave the rest and note them.
Bash 3.0+ constraint
Hard floor is Bash 3.0 (CI runs a 3.0 job). printf -v is 3.1+ — do NOT use it. The IFS "${arr[*]}" join is the only 3.0-safe fork-free approach. No [[ ]], ${var,,}, declare -A, ${arr[-1]}, &>>.
TDD
- RED: existing assertion tests already cover behavior; add a targeted test if any converted assertion lacks trailing-newline / multi-arg coverage.
- GREEN: convert one site, keep suite green.
- Repeat per site.
Acceptance criteria
Problem
Most core assertions join their haystack args with a command-substitution fork just to build the compared string:
actual=$(printf '%s\n' "${actual_arr[@]}")This forks a subshell on every assertion call. Micro-benchmark (20 000 iterations, Bash 3.2):
On assertion-heavy suites this is the dominant per-assertion cost.
assert_containsalready matches with a nativecase; only the join in front of it still forks.Fix
Replace the fork with a Bash-3.0-safe, fork-free IFS join:
Affected sites (
src/assert.sh)All
actual=$(printf '%s\n' "${actual_arr[@]}")occurrences — currently ~8, includingassert_contains,assert_not_contains,assert_matches,assert_not_matches, and the array-shaped assertions around lines 277, 330, 353, 379, 656, 701, 726.grep -nE '=\$\(printf .%s.n. "\$\{actual_arr' src/assert.shlists them.IMPORTANT — per-site semantic check (do NOT blanket-sed)
The IFS join differs from
printf '%s\n'in two ways:actualfeeds a substringcase, agrep/regex match, or is only shown on failure (trailing-newline irrelevant):assert_contains,assert_not_contains,assert_matches,assert_not_matches.actualis exact-compared or its exact byte form is asserted in a snapshot — if a test pins the trailing newline, either keep that site or adjust deliberately.Convert only the sites you can prove are behavior-identical. Leave the rest and note them.
Bash 3.0+ constraint
Hard floor is Bash 3.0 (CI runs a 3.0 job).
printf -vis 3.1+ — do NOT use it. The IFS"${arr[*]}"join is the only 3.0-safe fork-free approach. No[[ ]],${var,,},declare -A,${arr[-1]},&>>.TDD
Acceptance criteria
./bashunit tests/and./bashunit --parallel --simple --strict tests/green.make sa,make lint,./build.sh --verifygreen.assert_containscalls, wall-time delta).