Skip to content

Commit 1cae56f

Browse files
authored
perf(runner): outvar pattern in hot-path result helpers (#672)
1 parent f81c7e4 commit 1cae56f

3 files changed

Lines changed: 106 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Faster runtime-error detection: single `case` glob instead of 23-iteration loop in `detect_runtime_error` (#668)
77
- Hot-path coverage flag now cached in `_BASHUNIT_COVERAGE_ON`, removing a function dispatch per call (#664)
88
- Parallel runner blocks on `wait -n` on Bash 4.3+ instead of polling `jobs -r`, removing sleep-induced slot-release latency (#667)
9+
- Hot-path result helpers (`extract_encoded_field`, `extract_subshell_type`, `format_subshell_output`, `compute_total_assertions`) use outvar pattern, dropping a fork per call per test (#662)
910

1011
## [0.36.0](https://github.com/TypedDevs/bashunit/compare/0.35.0...0.36.0) - 2026-05-07
1112

src/runner.sh

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,48 +57,71 @@ function bashunit::runner::apply_interpolated_title() {
5757
printf '%s' "$interpolated"
5858
}
5959

60+
# All four helpers below use the outvar pattern (first argument is the name of
61+
# the variable to assign into) so callers can avoid the per-test $(...) subshell
62+
# capture in the hot path. Internal locals use a `__bu_` prefix to avoid name
63+
# collisions with caller variables passed by name.
64+
65+
# Writes the value of an encoded field (##KEY=value##) into the named outvar.
66+
# Arguments: $1 outvar name, $2 test_execution_result, $3 key
6067
function bashunit::runner::extract_encoded_field() {
61-
local test_execution_result=$1
62-
local key=$2
63-
local marker="##${key}="
64-
case "$test_execution_result" in
65-
*"$marker"*)
66-
local rest="${test_execution_result#*"$marker"}"
67-
printf '%s' "${rest%%##*}"
68+
local __bu_out=$1
69+
local __bu_in=$2
70+
local __bu_key=$3
71+
local __bu_marker="##${__bu_key}="
72+
local __bu_val=""
73+
case "$__bu_in" in
74+
*"$__bu_marker"*)
75+
local __bu_rest="${__bu_in#*"$__bu_marker"}"
76+
__bu_val="${__bu_rest%%##*}"
6877
;;
69-
*) printf '' ;;
7078
esac
79+
eval "$__bu_out=\$__bu_val"
7180
}
7281

82+
# Writes the sum of all ASSERTIONS_* counters into the named outvar.
83+
# Arguments: $1 outvar name, $2 test_execution_result
7384
function bashunit::runner::compute_total_assertions() {
74-
local test_execution_result=$1
75-
local failed passed skipped incomplete snapshot
76-
failed="${test_execution_result##*##ASSERTIONS_FAILED=}"
77-
failed="${failed%%##*}"
78-
passed="${test_execution_result##*##ASSERTIONS_PASSED=}"
79-
passed="${passed%%##*}"
80-
skipped="${test_execution_result##*##ASSERTIONS_SKIPPED=}"
81-
skipped="${skipped%%##*}"
82-
incomplete="${test_execution_result##*##ASSERTIONS_INCOMPLETE=}"
83-
incomplete="${incomplete%%##*}"
84-
snapshot="${test_execution_result##*##ASSERTIONS_SNAPSHOT=}"
85-
snapshot="${snapshot%%##*}"
86-
printf '%d' "$((${failed:-0} + ${passed:-0} + ${skipped:-0} + ${incomplete:-0} + ${snapshot:-0}))"
85+
local __bu_out=$1
86+
local __bu_in=$2
87+
local __bu_failed __bu_passed __bu_skipped __bu_incomplete __bu_snapshot
88+
__bu_failed="${__bu_in##*##ASSERTIONS_FAILED=}"
89+
__bu_failed="${__bu_failed%%##*}"
90+
__bu_passed="${__bu_in##*##ASSERTIONS_PASSED=}"
91+
__bu_passed="${__bu_passed%%##*}"
92+
__bu_skipped="${__bu_in##*##ASSERTIONS_SKIPPED=}"
93+
__bu_skipped="${__bu_skipped%%##*}"
94+
__bu_incomplete="${__bu_in##*##ASSERTIONS_INCOMPLETE=}"
95+
__bu_incomplete="${__bu_incomplete%%##*}"
96+
__bu_snapshot="${__bu_in##*##ASSERTIONS_SNAPSHOT=}"
97+
__bu_snapshot="${__bu_snapshot%%##*}"
98+
local __bu_val
99+
__bu_val=$((${__bu_failed:-0} + ${__bu_passed:-0} + ${__bu_skipped:-0}))
100+
__bu_val=$((__bu_val + ${__bu_incomplete:-0} + ${__bu_snapshot:-0}))
101+
eval "$__bu_out=\$__bu_val"
87102
}
88103

104+
# Writes the subshell type marker (text inside leading [...]) into the named outvar.
105+
# Arguments: $1 outvar name, $2 subshell_output
89106
function bashunit::runner::extract_subshell_type() {
90-
local subshell_output=$1
91-
local type="${subshell_output%%]*}"
92-
printf '%s' "${type#[}"
107+
local __bu_out=$1
108+
local __bu_in=$2
109+
local __bu_val="${__bu_in%%]*}"
110+
__bu_val="${__bu_val#[}"
111+
eval "$__bu_out=\$__bu_val"
93112
}
94113

114+
# Writes the subshell output (minus the leading [type] marker, with embedded
115+
# status markers replaced by newlines) into the named outvar.
116+
# Arguments: $1 outvar name, $2 subshell_output
95117
function bashunit::runner::format_subshell_output() {
96-
local subshell_output=$1
97-
local line="${subshell_output#*]}"
98-
line=${line//\[failed\]/$'\n'}
99-
line=${line//\[skipped\]/$'\n'}
100-
line=${line//\[incomplete\]/$'\n'}
101-
printf '%s' "$line"
118+
local __bu_out=$1
119+
local __bu_in=$2
120+
local __bu_val="${__bu_in#*]}"
121+
__bu_val=${__bu_val//\[failed\]/$'\n'}
122+
__bu_val=${__bu_val//\[skipped\]/$'\n'}
123+
__bu_val=${__bu_val//\[incomplete\]/$'\n'}
124+
eval "$__bu_out=\$__bu_val"
102125
}
103126

104127
function bashunit::runner::detect_runtime_error() {
@@ -815,8 +838,8 @@ function bashunit::runner::run_test() {
815838

816839
if [ -n "$subshell_output" ]; then
817840
local type
818-
type=$(bashunit::runner::extract_subshell_type "$subshell_output")
819-
subshell_output=$(bashunit::runner::format_subshell_output "$subshell_output")
841+
bashunit::runner::extract_subshell_type type "$subshell_output"
842+
bashunit::runner::format_subshell_output subshell_output "$subshell_output"
820843
if ! bashunit::env::is_failures_only_enabled; then
821844
bashunit::state::print_line "$type" "$subshell_output"
822845
fi
@@ -832,12 +855,12 @@ function bashunit::runner::run_test() {
832855
local test_exit_code="$_BASHUNIT_TEST_EXIT_CODE"
833856

834857
local total_assertions
835-
total_assertions=$(bashunit::runner::compute_total_assertions "$test_execution_result")
858+
bashunit::runner::compute_total_assertions total_assertions "$test_execution_result"
836859

837860
local encoded_test_title hook_failure encoded_hook_message
838-
encoded_test_title=$(bashunit::runner::extract_encoded_field "$test_execution_result" "TEST_TITLE")
839-
hook_failure=$(bashunit::runner::extract_encoded_field "$test_execution_result" "TEST_HOOK_FAILURE")
840-
encoded_hook_message=$(bashunit::runner::extract_encoded_field "$test_execution_result" "TEST_HOOK_MESSAGE")
861+
bashunit::runner::extract_encoded_field encoded_test_title "$test_execution_result" "TEST_TITLE"
862+
bashunit::runner::extract_encoded_field hook_failure "$test_execution_result" "TEST_HOOK_FAILURE"
863+
bashunit::runner::extract_encoded_field encoded_hook_message "$test_execution_result" "TEST_HOOK_MESSAGE"
841864

842865
local test_title=""
843866
[ -n "$encoded_test_title" ] && test_title="$(bashunit::helper::decode_base64 "$encoded_test_title")"

tests/unit/runner_test.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,49 @@ function test_detect_runtime_error_matches_unexpected_eof() {
127127

128128
assert_same "line 5: unexpected EOF while looking for matching" "$actual"
129129
}
130+
131+
function test_extract_encoded_field_writes_value_to_outvar() {
132+
local out=""
133+
bashunit::runner::extract_encoded_field out \
134+
"preamble##TEST_TITLE=hello world##ASSERTIONS_PASSED=1" "TEST_TITLE"
135+
136+
assert_same "hello world" "$out"
137+
}
138+
139+
function test_extract_encoded_field_writes_empty_when_key_missing() {
140+
local out="prior"
141+
bashunit::runner::extract_encoded_field out "##ASSERTIONS_PASSED=1" "TEST_TITLE"
142+
143+
assert_empty "$out"
144+
}
145+
146+
function test_compute_total_assertions_sums_into_outvar() {
147+
local out=""
148+
bashunit::runner::compute_total_assertions out \
149+
"##ASSERTIONS_FAILED=1##ASSERTIONS_PASSED=2##ASSERTIONS_SKIPPED=3##ASSERTIONS_INCOMPLETE=4##ASSERTIONS_SNAPSHOT=5"
150+
151+
assert_same "15" "$out"
152+
}
153+
154+
function test_compute_total_assertions_treats_missing_counters_as_zero() {
155+
local out=""
156+
bashunit::runner::compute_total_assertions out "##ASSERTIONS_PASSED=2"
157+
158+
assert_same "2" "$out"
159+
}
160+
161+
function test_extract_subshell_type_strips_brackets_into_outvar() {
162+
local out=""
163+
bashunit::runner::extract_subshell_type out "[failed] something happened"
164+
165+
assert_same "failed" "$out"
166+
}
167+
168+
function test_format_subshell_output_strips_type_and_expands_markers() {
169+
local out=""
170+
bashunit::runner::format_subshell_output out "[failed] line1[skipped]line2[incomplete]line3"
171+
172+
local expected
173+
expected=$' line1\nline2\nline3'
174+
assert_same "$expected" "$out"
175+
}

0 commit comments

Comments
 (0)