Part of #761.
Problem
bashunit::runner::run_test reads the clock twice per test (src/runner.sh:1070 and src/runner.sh:1097). On shells without EPOCHREALTIME (bash < 5, i.e. macOS default 3.2) and without GNU date +%s%N (macOS/Alpine are explicitly excluded at src/clock.sh:22), bashunit::clock::now falls through to forking a perl interpreter per read (src/clock.sh:88-90). Measured: ~5-8ms per perl fork, so ~10-16ms per test, ~2400 perl processes and ~15-20s per full suite run on a stock Mac — plus the same tax inside every one of the 258 nested ./bashunit acceptance invocations.
The same two-reads-per-hook pattern exists for set_up_before_script/tear_down_after_script (src/runner.sh:1761,1778 and src/runner.sh:1900,1917) — per file, so much cheaper, but it rides the same code path.
Proposed behavior (needs ADR)
Per-test wall-clock timing becomes best-effort based on clock cost:
- Add
bashunit::clock::is_expensive (or similar): true when the chosen impl forks an interpreter (perl, python, node, powershell); false for shell (EPOCHREALTIME) and date/date-seconds.
- Introduce an
auto default for per-test timing: when the clock is expensive AND nothing needs per-test durations, skip BOTH clock::now reads in run_test. Per-test durations are consumed by:
- execution-time display (
bashunit::env::is_show_execution_time_enabled, src/env.sh:250, default true via _BASHUNIT_DEFAULT_SHOW_EXECUTION_TIME src/env.sh:103),
--profile (src/runner.sh:1101), --verbose (src/runner.sh:1105), and report writers (src/reports.sh).
Suggested mechanics: change the default of BASHUNIT_SHOW_EXECUTION_TIME from "true" to "auto"; auto resolves to false only when the clock is expensive. An explicit BASHUNIT_SHOW_EXECUTION_TIME=true, --profile, --verbose, or an active report keeps precise timing (and its perl cost). Explicit false already skips display but currently NOT the clock reads — fix that too: when duration is not consumed, do not read the clock at all.
- When timing is skipped, the per-test duration renders as empty (verify
print_successful_test / report writers handle an empty or 0 duration gracefully — decide and test one representation).
- Total suite runtime (
bashunit::clock::total_runtime_in_milliseconds, 2 reads per run) stays as-is.
- Write an ADR in
adrs/ documenting the decision and the alternative considered and rejected: a persistent co-process timestamp server over FIFOs (one fork per suite). Rejected because concurrent readers in --parallel mode would race on the FIFO without a portable lock (no flock on stock macOS), plus lifecycle/cleanup complexity.
If during implementation a materially better design emerges, propose it in the issue before building.
Tests first (TDD)
- Unit tests for the impl-classification function (force
_BASHUNIT_CLOCK_NOW_IMPL values).
- Acceptance: on an expensive-clock system, default run shows no per-test times but correct totals;
BASHUNIT_SHOW_EXECUTION_TIME=true restores them; --profile/report runs still produce durations. Use BASHUNIT_TEST_BASH_VERSION-style seams or env overrides rather than depending on the host — check how existing clock tests (tests/unit/clock_test.sh if present) fake impls.
- Existing display/report tests must pass on Linux CI (bash 5, cheap clock: behavior unchanged there).
Constraints
- Bash 3.0+ only: no
declare -A, no [[ ]], no ${var,,}, no negative array indexing, no &>>.
- No behavior change on fork-free clock systems (Linux CI, bash 5).
- User-visible change (per-test times disappear by default on old-bash Macs): CHANGELOG.md entry + docs update required.
Acceptance criteria
Line references valid at commit 4d80e7c.
Part of #761.
Problem
bashunit::runner::run_testreads the clock twice per test (src/runner.sh:1070andsrc/runner.sh:1097). On shells withoutEPOCHREALTIME(bash < 5, i.e. macOS default 3.2) and without GNUdate +%s%N(macOS/Alpine are explicitly excluded atsrc/clock.sh:22),bashunit::clock::nowfalls through to forking a perl interpreter per read (src/clock.sh:88-90). Measured: ~5-8ms per perl fork, so ~10-16ms per test, ~2400 perl processes and ~15-20s per full suite run on a stock Mac — plus the same tax inside every one of the 258 nested./bashunitacceptance invocations.The same two-reads-per-hook pattern exists for
set_up_before_script/tear_down_after_script(src/runner.sh:1761,1778andsrc/runner.sh:1900,1917) — per file, so much cheaper, but it rides the same code path.Proposed behavior (needs ADR)
Per-test wall-clock timing becomes best-effort based on clock cost:
bashunit::clock::is_expensive(or similar): true when the chosen impl forks an interpreter (perl,python,node,powershell); false forshell(EPOCHREALTIME) anddate/date-seconds.autodefault for per-test timing: when the clock is expensive AND nothing needs per-test durations, skip BOTHclock::nowreads inrun_test. Per-test durations are consumed by:bashunit::env::is_show_execution_time_enabled,src/env.sh:250, default true via_BASHUNIT_DEFAULT_SHOW_EXECUTION_TIMEsrc/env.sh:103),--profile(src/runner.sh:1101),--verbose(src/runner.sh:1105), and report writers (src/reports.sh).Suggested mechanics: change the default of
BASHUNIT_SHOW_EXECUTION_TIMEfrom"true"to"auto";autoresolves to false only when the clock is expensive. An explicitBASHUNIT_SHOW_EXECUTION_TIME=true,--profile,--verbose, or an active report keeps precise timing (and its perl cost). Explicitfalsealready skips display but currently NOT the clock reads — fix that too: when duration is not consumed, do not read the clock at all.print_successful_test/ report writers handle an empty or0duration gracefully — decide and test one representation).bashunit::clock::total_runtime_in_milliseconds, 2 reads per run) stays as-is.adrs/documenting the decision and the alternative considered and rejected: a persistent co-process timestamp server over FIFOs (one fork per suite). Rejected because concurrent readers in--parallelmode would race on the FIFO without a portable lock (noflockon stock macOS), plus lifecycle/cleanup complexity.If during implementation a materially better design emerges, propose it in the issue before building.
Tests first (TDD)
_BASHUNIT_CLOCK_NOW_IMPLvalues).BASHUNIT_SHOW_EXECUTION_TIME=truerestores them;--profile/report runs still produce durations. UseBASHUNIT_TEST_BASH_VERSION-style seams or env overrides rather than depending on the host — check how existing clock tests (tests/unit/clock_test.shif present) fake impls.Constraints
declare -A, no[[ ]], no${var,,}, no negative array indexing, no&>>.Acceptance criteria
./bashunit tests/and./bashunit --parallel tests/pass.make sa,make lintpass;shfmt -w .produces no diff.Line references valid at commit 4d80e7c.