From 1869e2891181baf6a98c12e41a43e02ffe4a5692 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 23 Jul 2026 00:29:10 +0200 Subject: [PATCH 1/5] ref(state): centralize the base64 empty-payload sentinel The "_BASHUNIT_EMPTY_" wire token was hardcoded in three places across two files: encode_base64 (helpers.sh) emits it and both decode sites (helpers.sh, runner.sh) recognize it. Extract it into a single constant next to _BASHUNIT_BASE64_WRAP_FLAG in state.sh so the encode and decode sides can never drift. No behavior change; the on-wire byte sequence is unchanged. Claude-Session: https://claude.ai/code/session_01JSeB8UzLCpqst55hAK6dED --- src/helpers.sh | 4 ++-- src/runner.sh | 2 +- src/state.sh | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/helpers.sh b/src/helpers.sh index deb9c310..bb67f426 100755 --- a/src/helpers.sh +++ b/src/helpers.sh @@ -176,7 +176,7 @@ function bashunit::helper::encode_base64() { # Handle empty string specially - base64 of "" is "", which gets lost in line parsing if [ -z "$value" ]; then - printf '%s' "_BASHUNIT_EMPTY_" + printf '%s' "$_BASHUNIT_BASE64_EMPTY_SENTINEL" return fi @@ -193,7 +193,7 @@ function bashunit::helper::decode_base64() { local value="$1" # Empty input decodes to empty; short-circuit to skip the base64 fork (#762). - if [ -z "$value" ] || [ "$value" = "_BASHUNIT_EMPTY_" ]; then + if [ -z "$value" ] || [ "$value" = "$_BASHUNIT_BASE64_EMPTY_SENTINEL" ]; then printf '' return fi diff --git a/src/runner.sh b/src/runner.sh index b07ff49d..bc3864e8 100755 --- a/src/runner.sh +++ b/src/runner.sh @@ -1482,7 +1482,7 @@ function bashunit::runner::decode_subshell_output() { local test_output_base64="${test_execution_result##*##TEST_OUTPUT=}" test_output_base64="${test_output_base64%%##*}" - if [ -z "$test_output_base64" ] || [ "$test_output_base64" = "_BASHUNIT_EMPTY_" ]; then + if [ -z "$test_output_base64" ] || [ "$test_output_base64" = "$_BASHUNIT_BASE64_EMPTY_SENTINEL" ]; then _BASHUNIT_RUNNER_SUBSHELL_OUTPUT_OUT="" return fi diff --git a/src/state.sh b/src/state.sh index 67b8a19a..c36b74ff 100644 --- a/src/state.sh +++ b/src/state.sh @@ -10,6 +10,13 @@ case "$_bashunit_base64_help" in esac unset _bashunit_base64_help +# Wire sentinel for an empty base64 payload. base64 of "" is "", which gets lost +# in line parsing, so encode_base64 emits this token and both decode sites map it +# back to "". Single source of truth keeps the encode (helpers.sh) and decode +# (helpers.sh, runner.sh) sides byte-identical. +# shellcheck disable=SC2034 # read cross-file in helpers.sh and runner.sh +_BASHUNIT_BASE64_EMPTY_SENTINEL="_BASHUNIT_EMPTY_" + _BASHUNIT_TESTS_PASSED=0 _BASHUNIT_TESTS_FAILED=0 _BASHUNIT_TESTS_SKIPPED=0 From 3bb1f570c7a2540bd193178f10189fb03a60f72b Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 23 Jul 2026 00:21:51 +0200 Subject: [PATCH 2/5] docs(assert): tighten the join_to_slot contract comment Drop the dangling "behave exactly as the previous command-substitution did" clause (refactor-history narration with no referent in the current code) while keeping the useful contract: output matches $(printf '%s\n' "$@") exactly, including trailing-newline stripping. Claude-Session: https://claude.ai/code/session_01JSeB8UzLCpqst55hAK6dED --- src/assert.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/assert.sh b/src/assert.sh index 6436fb5b..a893a28d 100755 --- a/src/assert.sh +++ b/src/assert.sh @@ -31,9 +31,8 @@ function bashunit::assert::label_to_slot() { _BASHUNIT_ASSERT_JOINED_OUT="" # Join positional args into _BASHUNIT_ASSERT_JOINED_OUT with no fork. -# Mirrors $(printf '%s\n' "$@"): joins with newlines and strips trailing -# newlines, so callers that match on the result behave exactly as the previous -# command-substitution did. +# Output matches $(printf '%s\n' "$@") exactly: newline-joined, trailing +# newlines stripped (as command substitution strips them). function bashunit::assert::join_to_slot() { local IFS=$'\n' local joined="$*" From fda87069361478f347f3ce06a0ab534f19cb6210 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 23 Jul 2026 00:42:16 +0200 Subject: [PATCH 3/5] ref: reuse label_to_slot and share the deferred summary flush Route the specialized assertions (arrays, dates, duration, json, files, folders) and the bashunit::assertion_failed facade through the existing fork-free bashunit::assert::label_to_slot slot helper instead of re-deriving the label with a per-call find_test_function_name + normalize_test_function_name command substitution, matching assert.sh. Output, custom-label overrides ($2 folders, $3 files) and the derived default are unchanged; label resolution stays on the failure path. Collapse the three byte-identical skipped/incomplete/risky summary renderers into one bashunit::console_results::flush_deferred_block; each caller keeps its own guard. --- src/assert_arrays.sh | 18 +++------ src/assert_dates.sh | 30 +++++---------- src/assert_duration.sh | 18 +++------ src/assert_files.sh | 50 +++++++++---------------- src/assert_folders.sh | 45 +++++++++-------------- src/assert_json.sh | 24 ++++-------- src/bashunit.sh | 6 +-- src/console_results.sh | 83 +++++++++++++++++------------------------- 8 files changed, 102 insertions(+), 172 deletions(-) diff --git a/src/assert_arrays.sh b/src/assert_arrays.sh index 2f917cc6..705cd7da 100644 --- a/src/assert_arrays.sh +++ b/src/assert_arrays.sh @@ -56,10 +56,8 @@ function assert_array_contains() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT shift local -a actual @@ -82,10 +80,8 @@ function assert_array_length() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT shift # Use $# / $* rather than building an array: on Bash 3.0 under `set -u`, @@ -106,10 +102,8 @@ function assert_array_not_contains() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT shift local -a actual actual=("$@") diff --git a/src/assert_dates.sh b/src/assert_dates.sh index 849aff9d..48a609df 100644 --- a/src/assert_dates.sh +++ b/src/assert_dates.sh @@ -105,10 +105,8 @@ function assert_date_equals() { actual="$(bashunit::date::to_epoch "$2")" if [ "$actual" -ne "$expected" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be equal to" "${expected}" return @@ -126,10 +124,8 @@ function assert_date_before() { actual="$(bashunit::date::to_epoch "$2")" if [ "$actual" -ge "$expected" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be before" "${expected}" return @@ -147,10 +143,8 @@ function assert_date_after() { actual="$(bashunit::date::to_epoch "$2")" if [ "$actual" -le "$expected" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be after" "${expected}" return @@ -170,10 +164,8 @@ function assert_date_within_range() { actual="$(bashunit::date::to_epoch "$3")" if [ "$actual" -lt "$from" ] || [ "$actual" -gt "$to" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be between" "${from} and ${to}" return @@ -197,10 +189,8 @@ function assert_date_within_delta() { fi if [ "$diff" -gt "$delta" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be within" "${delta} seconds of ${expected}" return diff --git a/src/assert_duration.sh b/src/assert_duration.sh index 7e33f0b9..af290e9c 100644 --- a/src/assert_duration.sh +++ b/src/assert_duration.sh @@ -27,10 +27,8 @@ function assert_duration() { elapsed_ms=$(bashunit::duration::measure_ms "$command") if [ "$elapsed_ms" -gt "$threshold_ms" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}" return @@ -49,10 +47,8 @@ function assert_duration_less_than() { elapsed_ms=$(bashunit::duration::measure_ms "$command") if [ "$elapsed_ms" -ge "$threshold_ms" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}" return @@ -71,10 +67,8 @@ function assert_duration_greater_than() { elapsed_ms=$(bashunit::duration::measure_ms "$command") if [ "$elapsed_ms" -le "$threshold_ms" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to take at least (ms)" "${command}" return diff --git a/src/assert_files.sh b/src/assert_files.sh index aeca0048..757ff09a 100644 --- a/src/assert_files.sh +++ b/src/assert_files.sh @@ -4,11 +4,10 @@ function assert_file_exists() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -f "$expected" ]; then + bashunit::assert::label_to_slot "${3:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist" return @@ -21,11 +20,10 @@ function assert_file_not_exists() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ -f "$expected" ]; then + bashunit::assert::label_to_slot "${3:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the file exists" return @@ -38,11 +36,10 @@ function assert_is_file() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -f "$expected" ]; then + bashunit::assert::label_to_slot "${3:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be a file" "but is not a file" return @@ -55,11 +52,10 @@ function assert_is_file_empty() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ -s "$expected" ]; then + bashunit::assert::label_to_slot "${3:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty" return @@ -75,10 +71,8 @@ function assert_files_equals() { local actual="$2" if [ "$(diff -u "$expected" "$actual")" != '' ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "Compared" "${actual}" \ @@ -96,10 +90,8 @@ function assert_files_not_equals() { local actual="$2" if [ "$(diff -u "$expected" "$actual")" = '' ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "Compared" "${actual}" \ @@ -117,10 +109,8 @@ function assert_file_contains() { local string="$2" if ! grep -F -q "$string" "$file"; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${file}" "to contain" "${string}" @@ -137,10 +127,8 @@ function assert_file_not_contains() { local string="$2" if grep -q "$string" "$file"; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${file}" "to not contain" "${string}" @@ -172,10 +160,8 @@ function assert_file_permissions() { local expected="$1" local file="$2" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT if [ ! -e "$file" ]; then bashunit::assert::mark_failed diff --git a/src/assert_folders.sh b/src/assert_folders.sh index 9faddacc..369b21d2 100644 --- a/src/assert_folders.sh +++ b/src/assert_folders.sh @@ -4,11 +4,10 @@ function assert_directory_exists() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist" return @@ -21,11 +20,10 @@ function assert_directory_not_exists() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ -d "$expected" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the directory exists" return @@ -38,11 +36,10 @@ function assert_is_directory() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be a directory" "but is not a directory" return @@ -55,11 +52,10 @@ function assert_is_directory_empty() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ] || [ -n "$(ls -A "$expected")" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty" return @@ -72,11 +68,10 @@ function assert_is_directory_not_empty() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ] || [ -z "$(ls -A "$expected")" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to not be empty" "but is empty" return @@ -89,11 +84,10 @@ function assert_is_directory_readable() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ] || [ ! -r "$expected" ] || [ ! -x "$expected" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be readable" "but is not readable" return @@ -106,11 +100,10 @@ function assert_is_directory_not_readable() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ] || { [ -r "$expected" ] && [ -x "$expected" ]; }; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be not readable" "but is readable" return @@ -123,11 +116,10 @@ function assert_is_directory_writable() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ] || [ ! -w "$expected" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be writable" "but is not writable" return @@ -140,11 +132,10 @@ function assert_is_directory_not_writable() { bashunit::assert::should_skip && return 0 local expected="$1" - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}" if [ ! -d "$expected" ] || [ -w "$expected" ]; then + bashunit::assert::label_to_slot "${2:-}" + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to be not writable" "but is writable" return diff --git a/src/assert_json.sh b/src/assert_json.sh index 8ec163a1..3ddf55ef 100644 --- a/src/assert_json.sh +++ b/src/assert_json.sh @@ -17,10 +17,8 @@ function assert_json_key_exists() { local result if ! result=$(printf '%s' "$json" | jq -e "$key" 2>/dev/null) || [ "$result" = "null" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${json}" "to have key" "${key}" return @@ -39,20 +37,16 @@ function assert_json_contains() { local result if ! result=$(printf '%s' "$json" | jq -e -r "$key" 2>/dev/null) || [ "$result" = "null" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${json}" "to have key" "${key}" return fi if [ "$result" != "$expected" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${result}" return @@ -74,10 +68,8 @@ function assert_json_equals() { actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) if [ "$expected_sorted" != "$actual_sorted" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${actual}" return diff --git a/src/bashunit.sh b/src/bashunit.sh index d7c56ef5..48f6a99b 100644 --- a/src/bashunit.sh +++ b/src/bashunit.sh @@ -11,10 +11,8 @@ function bashunit::assertion_failed() { local actual=$2 local failure_condition_message=${3:-"but got "} - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" - local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + bashunit::assert::label_to_slot + local label=$_BASHUNIT_ASSERT_LABEL_OUT bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" \ "$failure_condition_message" "${actual}" diff --git a/src/console_results.sh b/src/console_results.sh index 5ef55a30..e6d685a9 100644 --- a/src/console_results.sh +++ b/src/console_results.sh @@ -607,68 +607,53 @@ function bashunit::console_results::print_profile_and_reset() { rm -f "$PROFILE_OUTPUT_PATH" } -function bashunit::console_results::print_skipped_tests_and_reset() { - if [ -s "$SKIPPED_OUTPUT_PATH" ] && bashunit::env::is_show_skipped_enabled; then - local total_skipped - total_skipped=$(bashunit::state::get_tests_skipped) +## +# Flushes a deferred summary block (skipped/incomplete/risky): prints the +# "There was 1 " / "There were N " header, then the recorded lines +# from output_path (carriage returns stripped, blank lines dropped, each prefixed +# with "|"), removes the file and prints a trailing blank line. Callers own the +# `[ -s path ]` (and any `is_show_*`) guard so each block keeps its own gate. +# Arguments: $1 output path, $2 total count, $3 singular noun, $4 plural noun +## +function bashunit::console_results::flush_deferred_block() { + local output_path=$1 + local total=$2 + local singular=$3 + local plural=$4 - if bashunit::env::is_simple_output_enabled; then - printf "\n" - fi + if bashunit::env::is_simple_output_enabled; then + printf "\n" + fi - if [ "$total_skipped" -eq 1 ]; then - echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 skipped test:${_BASHUNIT_COLOR_DEFAULT}\n" - else - echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_skipped skipped tests:${_BASHUNIT_COLOR_DEFAULT}\n" - fi + if [ "$total" -eq 1 ]; then + echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 ${singular}:${_BASHUNIT_COLOR_DEFAULT}\n" + else + echo -e "${_BASHUNIT_COLOR_BOLD}There were ${total} ${plural}:${_BASHUNIT_COLOR_DEFAULT}\n" + fi - tr -d '\r' <"$SKIPPED_OUTPUT_PATH" | sed '/^[[:space:]]*$/d' | sed 's/^/|/' - rm "$SKIPPED_OUTPUT_PATH" + tr -d '\r' <"$output_path" | sed '/^[[:space:]]*$/d' | sed 's/^/|/' + rm "$output_path" - echo "" + echo "" +} + +function bashunit::console_results::print_skipped_tests_and_reset() { + if [ -s "$SKIPPED_OUTPUT_PATH" ] && bashunit::env::is_show_skipped_enabled; then + bashunit::console_results::flush_deferred_block "$SKIPPED_OUTPUT_PATH" \ + "$(bashunit::state::get_tests_skipped)" "skipped test" "skipped tests" fi } function bashunit::console_results::print_incomplete_tests_and_reset() { if [ -s "$INCOMPLETE_OUTPUT_PATH" ] && bashunit::env::is_show_incomplete_enabled; then - local total_incomplete - total_incomplete=$(bashunit::state::get_tests_incomplete) - - if bashunit::env::is_simple_output_enabled; then - printf "\n" - fi - - if [ "$total_incomplete" -eq 1 ]; then - echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 incomplete test:${_BASHUNIT_COLOR_DEFAULT}\n" - else - echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_incomplete incomplete tests:${_BASHUNIT_COLOR_DEFAULT}\n" - fi - - tr -d '\r' <"$INCOMPLETE_OUTPUT_PATH" | sed '/^[[:space:]]*$/d' | sed 's/^/|/' - rm "$INCOMPLETE_OUTPUT_PATH" - - echo "" + bashunit::console_results::flush_deferred_block "$INCOMPLETE_OUTPUT_PATH" \ + "$(bashunit::state::get_tests_incomplete)" "incomplete test" "incomplete tests" fi } function bashunit::console_results::print_risky_tests_and_reset() { if [ -s "$RISKY_OUTPUT_PATH" ]; then - local total_risky - total_risky=$(bashunit::state::get_tests_risky) - - if bashunit::env::is_simple_output_enabled; then - printf "\n" - fi - - if [ "$total_risky" -eq 1 ]; then - echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 risky test:${_BASHUNIT_COLOR_DEFAULT}\n" - else - echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_risky risky tests:${_BASHUNIT_COLOR_DEFAULT}\n" - fi - - tr -d '\r' <"$RISKY_OUTPUT_PATH" | sed '/^[[:space:]]*$/d' | sed 's/^/|/' - rm "$RISKY_OUTPUT_PATH" - - echo "" + bashunit::console_results::flush_deferred_block "$RISKY_OUTPUT_PATH" \ + "$(bashunit::state::get_tests_risky)" "risky test" "risky tests" fi } From 7925caafb9cf168ea4f2ba81ecfc9b28d281b328 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 23 Jul 2026 00:45:26 +0200 Subject: [PATCH 4/5] chore(helpers): drop dead provider_data array in find_total_tests The local array carried a shellcheck SC2034 (unused) disable; only the scalar provider_data_count is read in the counting loop. Leftover from an earlier provider-counting refactor. Claude-Session: https://claude.ai/code/session_01JSeB8UzLCpqst55hAK6dED --- src/helpers.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/helpers.sh b/src/helpers.sh index bb67f426..555a5463 100755 --- a/src/helpers.sh +++ b/src/helpers.sh @@ -569,8 +569,6 @@ function bashunit::helper::find_total_tests() { local -a functions_to_run=() # shellcheck disable=SC2206 functions_to_run=($filtered_functions) - # shellcheck disable=SC2034 - local -a provider_data=() local provider_data_count=0 local fn_name line # Scan once; functions without a provider count as 1 with no fork (#763). From 1413aada71a0aff16b71a7b1db4164cb21f8ea9b Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 23 Jul 2026 00:49:21 +0200 Subject: [PATCH 5/5] docs: changelog for the specialized-assert label dedup Claude-Session: https://claude.ai/code/session_01JSeB8UzLCpqst55hAK6dED --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbe7a1d..f6aa7323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed - Core string assertions (`assert_contains`/`assert_not_contains`, `assert_matches`/`assert_not_matches`, `assert_string_starts_with`/`assert_string_ends_with` and their negations) no longer fork a subshell per call to join their arguments; a fork-free join with identical behaviour replaces it (#844) +- The array, date, duration, json, files and folders assertions now resolve their failure label through the fork-free slot helper instead of a per-call command substitution — same labels, fewer forks ## [0.42.0](https://github.com/TypedDevs/bashunit/compare/0.41.0...0.42.0) - 2026-07-20