diff --git a/CHANGELOG.md b/CHANGELOG.md index 788768cc..bdc51ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Changed +- Spies are substantially cheaper. `assert_have_been_called` and the spy call counter dropped their `cat` and command-substitution forks in favour of the `read` builtin and existing return-slot helpers: a 200-call spy test went from 1111ms to 170ms, against a 111ms fork-free floor - `assert_contains_ignore_case` folds case with `shopt -s nocasematch` on Bash 3.1+ instead of two `tr` subprocesses, and falls back to `tr` only on Bash 3.0. Roughly 10x faster in a run dominated by that assertion (300 calls: 1419ms -> 131ms), with identical results including non-ASCII folding - Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925) diff --git a/src/doubles/assertions.sh b/src/doubles/assertions.sh index 13acb012..b694a17f 100644 --- a/src/doubles/assertions.sh +++ b/src/doubles/assertions.sh @@ -6,7 +6,11 @@ function assert_have_been_called() { local command=$1 bashunit::spy::times_to_slot "$command" local times=$_BASHUNIT_SPY_TIMES_OUT - local label="${2:-$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")}" + local label="${2:-}" + if [ -z "$label" ]; then + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT + fi if [ "$_BASHUNIT_SPY_REGISTERED_OUT" = false ]; then bashunit::spy::fail_unregistered "$command" "$label" @@ -43,10 +47,12 @@ function assert_have_been_called_with() { local expected="$*" local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local file_var="_BASHUNIT_SPY_${variable}_PARAMS_FILE" local label - label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")" + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT if [ -z "${!file_var-}" ]; then bashunit::spy::fail_unregistered "$command" "$label" @@ -80,10 +86,12 @@ function assert_have_been_called_with_args() { local expected=$_BASHUNIT_SPY_SERIALIZED_OUT local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local file_var="_BASHUNIT_SPY_${variable}_PARAMS_FILE" local label - label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")" + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT if [ -z "${!file_var-}" ]; then bashunit::spy::fail_unregistered "$command" "$label" @@ -116,10 +124,12 @@ function assert_have_been_called_with_any() { local expected="$*" local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local file_var="_BASHUNIT_SPY_${variable}_PARAMS_FILE" local label - label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")" + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT if [ -z "${!file_var-}" ]; then bashunit::spy::fail_unregistered "$command" "$label" @@ -156,7 +166,11 @@ function assert_have_been_called_times() { local command=$2 bashunit::spy::times_to_slot "$command" local times=$_BASHUNIT_SPY_TIMES_OUT - local label="${3:-$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")}" + local label="${3:-}" + if [ -z "$label" ]; then + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT + fi if [ "$_BASHUNIT_SPY_REGISTERED_OUT" = false ]; then bashunit::spy::fail_unregistered "$command" "$label" @@ -183,10 +197,12 @@ function assert_have_been_called_nth_with() { local expected="$*" local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local file_var="_BASHUNIT_SPY_${variable}_PARAMS_FILE" local label - label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")" + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT bashunit::spy::times_to_slot "$command" local times=$_BASHUNIT_SPY_TIMES_OUT @@ -227,6 +243,10 @@ function assert_have_been_called_nth_with() { function assert_not_called() { local command=$1 - local label="${2:-$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")}" + local label="${2:-}" + if [ -z "$label" ]; then + bashunit::helper::normalize_test_function_name_to_slot "${FUNCNAME[1]}" + label=$_BASHUNIT_HELPER_NORMALIZED_OUT + fi assert_have_been_called_times 0 "$command" "$label" } diff --git a/src/doubles/spy.sh b/src/doubles/spy.sh index 225e23f4..1ce0dff0 100644 --- a/src/doubles/spy.sh +++ b/src/doubles/spy.sh @@ -18,7 +18,8 @@ _BASHUNIT_SPY_REGISTERED_OUT=false function bashunit::spy::times_to_slot() { local command="$1" local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local file_var="_BASHUNIT_SPY_${variable}_TIMES_FILE" _BASHUNIT_SPY_TIMES_OUT=0 _BASHUNIT_SPY_REGISTERED_OUT=false @@ -26,7 +27,14 @@ function bashunit::spy::times_to_slot() { _BASHUNIT_SPY_REGISTERED_OUT=true fi if [ -f "${!file_var-}" ]; then - _BASHUNIT_SPY_TIMES_OUT=$(cat "${!file_var}" 2>/dev/null || builtin echo 0) + # `read` is a builtin: the count is a single short line, so this avoids a + # `cat` fork on a path a spy-heavy test hits once per assertion. + local times_line="" + read -r times_line <"${!file_var}" 2>/dev/null || times_line="" + case "$times_line" in + '' | *[!0-9]*) _BASHUNIT_SPY_TIMES_OUT=0 ;; + *) _BASHUNIT_SPY_TIMES_OUT=$times_line ;; + esac fi } @@ -87,7 +95,8 @@ function bashunit::spy::call_log_to_slot() { _BASHUNIT_SPY_CALL_LOG_OUT="" local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local file_var="_BASHUNIT_SPY_${variable}_PARAMS_FILE" if [ -z "${!file_var-}" ] || [ ! -f "${!file_var}" ]; then return @@ -159,7 +168,8 @@ function bashunit::spy() { local command=$1 local exit_code_or_impl="${2:-}" local variable - variable="$(bashunit::helper::normalize_variable_name "$command")" + bashunit::helper::normalize_variable_name_to_slot "$command" + variable=$_BASHUNIT_HELPER_VARNAME_OUT local times_file params_file local test_id="${BASHUNIT_CURRENT_TEST_ID:-global}" @@ -188,8 +198,9 @@ function bashunit::spy() { done serialized=\${serialized%$'\\x1f'} builtin printf '%s\x1e%s\\n' \"\$raw\" \"\$serialized\" >> '$params_file' - local _c - _c=\$(cat '$times_file' 2>/dev/null || builtin echo 0) + local _c=\"\" + read -r _c < '$times_file' 2>/dev/null || _c=\"\" + case \"\$_c\" in '' | *[!0-9]*) _c=0 ;; esac _c=\$((_c+1)) builtin echo \"\$_c\" > '$times_file' $body_suffix