Problem
tests/unit/assert_duration_test.sh takes ~3.3s for 7 tests — second-slowest unit file. Measured cause: three tests execute a real sleep 1:
test_unsuccessful_assert_duration_exceeds_threshold — assert_duration "sleep 1" 1000 (line 17)
test_unsuccessful_assert_duration_less_than — assert_duration_less_than "sleep 1" 100 (line 29)
test_successful_assert_duration_greater_than — assert_duration_greater_than "sleep 1" 500 (line 33)
That's 3s of wall time spent sleeping to test pure threshold-comparison logic.
Proposed fix
Two-part approach (don't just shrink all the sleeps — see the clock caveat below):
1. Failure-path tests (the two unsuccessful_* sleep tests): mock the measurement.
assert_duration / assert_duration_less_than / assert_duration_greater_than all delegate timing to bashunit::duration::measure_ms (src/assert_duration.sh:3) and then compare against the threshold. Mock it:
mock bashunit::duration::measure_ms echo 2000
Then assert_duration "sleep 1" 1000 can become assert_duration "fake_cmd" 1000 with a mocked elapsed of 2000ms — deterministic, zero sleep, and still exercises the real comparison + failure-message path. Note the expected failure message embeds the command string (print_failed_test ... "sleep 1"), so update the expected string to match the new command argument. Use bashunit's own mock helper (see tests/functional/doubles_test.sh for patterns).
Also applies to test_unsuccessful_assert_duration_greater_than (already fast, echo hello — can stay as-is or be unified with the mock style).
2. Success-path integration test (test_successful_assert_duration_greater_than): shrink the sleep, keep it real.
Keep one real-timing test so measure_ms integration stays covered: assert_duration_greater_than "sleep 0.2" 100 (2x margin).
⚠️ Clock resolution caveat: the clock has a last-resort fallback date-seconds with 1-second resolution (src/clock.sh:150, used only when EPOCHREALTIME, date +%s%N, perl, python, node, and powershell are all unavailable). Under that impl a sleep 0.2 measures 0ms and the greater_than test would fail. Guard it:
# in the test, before asserting:
bashunit::clock::is_expensive >/dev/null 2>&1 || true # ensures impl chosen
if [ "$_BASHUNIT_CLOCK_NOW_IMPL" = "date-seconds" ]; then
bashunit::skip "clock has 1s resolution" && return
fi
(Or keep sleep 1/500 for this single test if the guard is judged too intrusive — that alone still cuts the file from 3.3s to ~1.3s. Preferred: guard + sleep 0.2 → ~0.5s total.)
The two test_successful_*_within tests (sleep 0) and test_successful_assert_duration_less_than are already instant — leave them.
Files
tests/unit/assert_duration_test.sh — the only file to change
src/assert_duration.sh — read-only reference (measure_ms seam at line 3)
src/clock.sh:150 — date-seconds fallback (read-only reference)
Constraints
- No changes to
src/ — test-only
- Bash 3.0+ compatible: no
[[ ]], no Bash 4+ features (.claude/rules/bash-style.md)
sleep 0.2 (fractional) is an external-binary feature, fine on macOS/BSD/GNU/busybox — but only use it in the guarded integration test
- TDD: one test at a time, suite green after each step
- Safe under
./bashunit --parallel tests/
Acceptance criteria
Verification commands
time ./bashunit tests/unit/assert_duration_test.sh
./bashunit tests/ && ./bashunit --parallel tests/
make sa && make lint && shfmt -d .
Problem
tests/unit/assert_duration_test.shtakes ~3.3s for 7 tests — second-slowest unit file. Measured cause: three tests execute a realsleep 1:test_unsuccessful_assert_duration_exceeds_threshold—assert_duration "sleep 1" 1000(line 17)test_unsuccessful_assert_duration_less_than—assert_duration_less_than "sleep 1" 100(line 29)test_successful_assert_duration_greater_than—assert_duration_greater_than "sleep 1" 500(line 33)That's 3s of wall time spent sleeping to test pure threshold-comparison logic.
Proposed fix
Two-part approach (don't just shrink all the sleeps — see the clock caveat below):
1. Failure-path tests (the two
unsuccessful_*sleep tests): mock the measurement.assert_duration/assert_duration_less_than/assert_duration_greater_thanall delegate timing tobashunit::duration::measure_ms(src/assert_duration.sh:3) and then compare against the threshold. Mock it:mock bashunit::duration::measure_ms echo 2000Then
assert_duration "sleep 1" 1000can becomeassert_duration "fake_cmd" 1000with a mocked elapsed of 2000ms — deterministic, zero sleep, and still exercises the real comparison + failure-message path. Note the expected failure message embeds the command string (print_failed_test ... "sleep 1"), so update the expected string to match the new command argument. Use bashunit's ownmockhelper (seetests/functional/doubles_test.shfor patterns).Also applies to
test_unsuccessful_assert_duration_greater_than(already fast,echo hello— can stay as-is or be unified with the mock style).2. Success-path integration test (
test_successful_assert_duration_greater_than): shrink the sleep, keep it real.Keep one real-timing test so
measure_msintegration stays covered:assert_duration_greater_than "sleep 0.2" 100(2x margin).date-secondswith 1-second resolution (src/clock.sh:150, used only when EPOCHREALTIME,date +%s%N, perl, python, node, and powershell are all unavailable). Under that impl asleep 0.2measures 0ms and the greater_than test would fail. Guard it:(Or keep
sleep 1/500for this single test if the guard is judged too intrusive — that alone still cuts the file from 3.3s to ~1.3s. Preferred: guard +sleep 0.2→ ~0.5s total.)The two
test_successful_*_withintests (sleep 0) andtest_successful_assert_duration_less_thanare already instant — leave them.Files
tests/unit/assert_duration_test.sh— the only file to changesrc/assert_duration.sh— read-only reference (measure_msseam at line 3)src/clock.sh:150—date-secondsfallback (read-only reference)Constraints
src/— test-only[[ ]], no Bash 4+ features (.claude/rules/bash-style.md)sleep 0.2(fractional) is an external-binary feature, fine on macOS/BSD/GNU/busybox — but only use it in the guarded integration test./bashunit --parallel tests/Acceptance criteria
time ./bashunit tests/unit/assert_duration_test.sh≤ ~0.6s (baseline: ~3.3s)measure_ms), still assert the exact failure-message outputbashunit::duration::measure_mstiming end-to-enddate-secondsguarded or avoided)./bashunit tests/and./bashunit --parallel tests/greenmake sa,make lintpass;shfmt -w .produces no diffVerification commands