Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- `--jobs auto` / `-j auto` caps parallel concurrency at the CPU core count (portable across Linux/macOS/BSD); the default stays unlimited (#766)

### Changed
- Faster cold start: selecting the `perl` clock reads the time once instead of an empty `Time::HiRes` probe followed by a separate first read (2 -> 1 `perl` fork per cold start on bash 3.2 / macOS). No behaviour change
- Faster cold start: the deferred-output scratch files (failures/skipped/incomplete/risky/profile/rerun) now share one run-unique directory instead of forking `mktemp` once each (6 -> 0 `mktemp` forks per cold start; ~62ms -> ~50ms per nested run on bash 3.2). No behaviour change (#798)
- Faster cold start: `check_os` detects the OS with a single `uname` fork at load instead of two (matters across the acceptance suite's nested `bashunit` runs). No behaviour change (#798)
- Faster test execution: removed the remaining round-2 fork leftovers — `wait_for_job_slot` counts running background jobs without forking `wc` on every poll (145 -> 0 `wc` forks across a small parallel run), and `check_duplicate_functions` folds its per-file grep+awk+sort+uniq scan into a single awk pass. No behaviour change (#761)
Expand Down
26 changes: 22 additions & 4 deletions src/clock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,23 @@ function bashunit::clock::_choose_impl() {
esac
fi

# 3. Try Perl with Time::HiRes
# 3. Try Perl with Time::HiRes. Probe by reading the actual time (not an empty
# `-e ""`) so the pending first read reuses this fork instead of paying a
# second one; a non-digit/empty result means perl or Time::HiRes is missing, so
# fall through. The value is seeded into the return slot for now_to_slot.
attempts[attempts_count]="Perl"
attempts_count=$((attempts_count + 1))
if bashunit::dependencies::has_perl && perl -MTime::HiRes -e "" &>/dev/null; then
_BASHUNIT_CLOCK_NOW_IMPL="perl"
return 0
if bashunit::dependencies::has_perl; then
local perl_now
perl_now="$(perl -MTime::HiRes -e 'printf("%.0f\n", Time::HiRes::time() * 1000000000)' 2>/dev/null)"
case "$perl_now" in
'' | *[!0-9]*) ;;
*)
_BASHUNIT_CLOCK_NOW_IMPL="perl"
_BASHUNIT_CLOCK_NOW_OUT="$perl_now"
return 0
;;
esac
fi

# 4. Try Python 3 with time module
Expand Down Expand Up @@ -99,7 +110,14 @@ _BASHUNIT_CLOCK_NOW_OUT=""
# Returns: 0 on success, 1 when no clock implementation is available.
function bashunit::clock::now_to_slot() {
if [ -z "$_BASHUNIT_CLOCK_NOW_IMPL" ]; then
_BASHUNIT_CLOCK_NOW_OUT=""
bashunit::clock::_choose_impl || return 1
# _choose_impl may have already read the current time while selecting an
# interpreter impl (e.g. perl); reuse that value for this first read instead
# of forking a second interpreter.
if [ -n "$_BASHUNIT_CLOCK_NOW_OUT" ]; then
return 0
fi
fi

case "$_BASHUNIT_CLOCK_NOW_IMPL" in
Expand Down
22 changes: 22 additions & 0 deletions tests/acceptance/bashunit_coldstart_forks_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,25 @@ function test_coldstart_does_not_fork_mktemp_per_scratch_file() {

assert_less_than 1 "$mktemp_forks"
}

# Regression guard for the clock cold start. Selecting the `perl` clock impl used
# to fork perl twice: once for an empty `Time::HiRes` probe and once for the
# first real time read. Choosing an interpreter impl must cost at most one fork
# now (the probe reads the actual time and seeds it for the first read). On
# platforms that pick a fork-free/`date` clock (e.g. Linux) this is zero.
function test_coldstart_selects_clock_impl_without_a_probe_fork() {
if bashunit::check_os::is_windows; then
bashunit::skip "process tracing is unreliable under Git Bash" && return
fi

local trace
trace="$(PS4='+ ' bash -x ./bashunit --version 2>&1 >/dev/null)"

# Count real interpreter executions used by the clock (perl/python/node) at
# cold start. `command -v <interp>` is a shell builtin and never appears as an
# executed command line, so it is not counted.
local interp_forks
interp_forks="$(printf '%s\n' "$trace" | grep -cE '^\++ +(perl|python|node) ' || true)"

assert_less_or_equal_than 1 "$interp_forks"
}
Loading