From d6b7bd355b6a9e826fd70c945a5c28287c4aa689 Mon Sep 17 00:00:00 2001 From: SauronBot Date: Sun, 29 Mar 2026 22:37:37 +0200 Subject: [PATCH 1/2] feat(assert): add optional custom label parameter to assert functions (#77) Most assert functions now accept an optional trailing label parameter that overrides the auto-derived test function name in failure output. Examples: assert_same "expected" "$actual" "checking user name" assert_empty "$result" "result should be empty" assert_not_same "foo" "$val" "values must differ" Affected functions in src/assert.sh: assert_same, assert_equals, assert_not_equals, assert_not_same, assert_empty, assert_not_empty, assert_less_than, assert_less_or_equal_than, assert_greater_than, assert_greater_or_equal_than, assert_contains_ignore_case, assert_string_not_starts_with, assert_string_matches_format, assert_string_not_matches_format Variadic functions (assert_contains, assert_not_contains, assert_matches, assert_not_matches, assert_string_starts_with, assert_string_ends_with, assert_string_not_ends_with, assert_line_count) and exit-code functions ($3 already taken) do not support the label override. Note: assert_files.sh and assert_folders.sh already supported custom labels via their existing optional parameters. --- src/assert.sh | 152 +++++++++++++++----------------- tests/unit/assert_basic_test.sh | 24 +++++ 2 files changed, 95 insertions(+), 81 deletions(-) diff --git a/src/assert.sh b/src/assert.sh index f33b0e40..968b974c 100755 --- a/src/assert.sh +++ b/src/assert.sh @@ -11,6 +11,18 @@ function bashunit::assert::should_skip() { bashunit::env::is_stop_on_assertion_failure_enabled && ((_BASHUNIT_ASSERTION_FAILED_IN_TEST)) } +# Resolve assertion label: use custom label if provided, otherwise derive from test function name +function bashunit::assert::label() { + local custom_label="${1:-}" + if [ -n "$custom_label" ]; then + builtin echo "$custom_label" + return + fi + local test_fn + test_fn="$(bashunit::helper::find_test_function_name)" + bashunit::helper::normalize_test_function_name "$test_fn" +} + function bashunit::fail() { bashunit::assert::should_skip && return 0 @@ -115,12 +127,11 @@ function assert_same() { local expected="$1" local actual="$2" + local label_override="${3:-}" if [ "$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")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${actual}" return @@ -134,6 +145,7 @@ function assert_equals() { local expected="$1" local actual="$2" + local label_override="${3:-}" local actual_cleaned actual_cleaned=$(bashunit::str::strip_ansi "$actual") @@ -141,10 +153,8 @@ function assert_equals() { expected_cleaned=$(bashunit::str::strip_ansi "$expected") if [ "$expected_cleaned" != "$actual_cleaned" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected_cleaned}" "but got " "${actual_cleaned}" return @@ -158,6 +168,7 @@ function assert_not_equals() { local expected="$1" local actual="$2" + local label_override="${3:-}" local actual_cleaned actual_cleaned=$(bashunit::str::strip_ansi "$actual") @@ -165,10 +176,8 @@ function assert_not_equals() { expected_cleaned=$(bashunit::str::strip_ansi "$expected") if [ "$expected_cleaned" = "$actual_cleaned" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected_cleaned}" "to not be" "${actual_cleaned}" return @@ -181,12 +190,11 @@ function assert_empty() { bashunit::assert::should_skip && return 0 local expected="$1" + local label_override="${2:-}" if [ "$expected" != "" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "to be empty" "but got " "${expected}" return @@ -199,12 +207,11 @@ function assert_not_empty() { bashunit::assert::should_skip && return 0 local expected="$1" + local label_override="${2:-}" if [ "$expected" = "" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "to not be empty" "but got " "${expected}" return @@ -218,12 +225,11 @@ function assert_not_same() { local expected="$1" local actual="$2" + local label_override="${3:-}" if [ "$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")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${expected}" "to not be" "${actual}" return @@ -239,16 +245,20 @@ function assert_contains() { local expected="$1" local -a actual_arr actual_arr=("${@:2}") + local label_override="" + local label_override="" + local label_override="" + local label_override="" + local label_override="" + local label_override="" local actual actual=$(printf '%s\n' "${actual_arr[@]}") case "$actual" in *"$expected"*) ;; *) - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}" return @@ -263,6 +273,7 @@ function assert_contains_ignore_case() { local expected="$1" local actual="$2" + local label_override="${3:-}" # Bash 3.0 compatible: use tr for case-insensitive comparison # (shopt nocasematch was introduced in Bash 3.1) @@ -274,10 +285,8 @@ function assert_contains_ignore_case() { case "$actual_lower" in *"$expected_lower"*) ;; *) - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}" return @@ -288,6 +297,7 @@ function assert_contains_ignore_case() { } function assert_not_contains() { + local label_override="" bashunit::assert::should_skip && return 0 local IFS=$' \t\n' @@ -299,10 +309,8 @@ function assert_not_contains() { case "$actual" in *"$expected"*) - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to not contain" "${expected}" return @@ -339,6 +347,7 @@ function assert_matches() { } function assert_not_matches() { + local label_override="" bashunit::assert::should_skip && return 0 local IFS=$' \t\n' @@ -351,10 +360,8 @@ function assert_not_matches() { # Check both line-by-line and with newlines collapsed for cross-line patterns if [ "$(printf '%s' "$actual" | "$GREP" -cE "$expected" || true)" -gt 0 ] || [ "$(printf '%s' "$actual" | tr '\n' ' ' | "$GREP" -cE "$expected" || true)" -gt 0 ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to not match" "${expected}" return @@ -365,6 +372,7 @@ function assert_not_matches() { function assert_exec() { bashunit::assert::should_skip && return 0 + local label_override="" local cmd="$1" shift @@ -436,10 +444,8 @@ function assert_exec() { fi if [ "$failed" -eq 1 ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "$label" "$expected_desc" "but got " "$actual_desc" return @@ -450,15 +456,14 @@ function assert_exec() { function assert_exit_code() { local actual_exit_code=${3-"$?"} # Capture $? before guard check + local label_override="" bashunit::assert::should_skip && return 0 local expected_exit_code="$1" if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual_exit_code}" "to be" "${expected_exit_code}" return @@ -469,15 +474,14 @@ function assert_exit_code() { function assert_successful_code() { local actual_exit_code=${3-"$?"} # Capture $? before guard check + local label_override="" bashunit::assert::should_skip && return 0 local expected_exit_code=0 if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test \ "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" @@ -489,13 +493,12 @@ function assert_successful_code() { function assert_unsuccessful_code() { local actual_exit_code=${3-"$?"} # Capture $? before guard check + local label_override="" bashunit::assert::should_skip && return 0 if [ "$actual_exit_code" -eq 0 ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual_exit_code}" "to be non-zero" "but was 0" return @@ -506,15 +509,14 @@ function assert_unsuccessful_code() { function assert_general_error() { local actual_exit_code=${3-"$?"} # Capture $? before guard check + local label_override="" bashunit::assert::should_skip && return 0 local expected_exit_code=1 if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test \ "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" @@ -526,15 +528,14 @@ function assert_general_error() { function assert_command_not_found() { local actual_exit_code=${3-"$?"} # Capture $? before guard check + local label_override="" bashunit::assert::should_skip && return 0 local expected_exit_code=127 if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test \ "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" @@ -545,6 +546,7 @@ function assert_command_not_found() { } function assert_string_starts_with() { + local label_override="" bashunit::assert::should_skip && return 0 local IFS=$' \t\n' @@ -557,10 +559,8 @@ function assert_string_starts_with() { case "$actual" in "$expected"*) ;; *) - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to start with" "${expected}" return @@ -575,13 +575,12 @@ function assert_string_not_starts_with() { local expected="$1" local actual="$2" + local label_override="${3:-}" case "$actual" in "$expected"*) - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to not start with" "${expected}" return @@ -592,6 +591,7 @@ function assert_string_not_starts_with() { } function assert_string_ends_with() { + local label_override="" bashunit::assert::should_skip && return 0 local IFS=$' \t\n' @@ -604,10 +604,8 @@ function assert_string_ends_with() { case "$actual" in *"$expected") ;; *) - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to end with" "${expected}" return @@ -618,6 +616,7 @@ function assert_string_ends_with() { } function assert_string_not_ends_with() { + local label_override="" bashunit::assert::should_skip && return 0 local IFS=$' \t\n' @@ -629,10 +628,8 @@ function assert_string_not_ends_with() { case "$actual" in *"$expected") - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to not end with" "${expected}" return @@ -647,12 +644,11 @@ function assert_less_than() { local expected="$1" local actual="$2" + local label_override="${3:-}" if ! [ "$actual" -lt "$expected" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be less than" "${expected}" return @@ -666,12 +662,11 @@ function assert_less_or_equal_than() { local expected="$1" local actual="$2" + local label_override="${3:-}" 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")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be less or equal than" "${expected}" return @@ -685,12 +680,11 @@ function assert_greater_than() { local expected="$1" local actual="$2" + local label_override="${3:-}" if ! [ "$actual" -gt "$expected" ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be greater than" "${expected}" return @@ -704,12 +698,11 @@ function assert_greater_or_equal_than() { local expected="$1" local actual="$2" + local label_override="${3:-}" 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")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to be greater or equal than" "${expected}" return @@ -725,6 +718,7 @@ function assert_line_count() { local expected="$1" local -a input_arr input_arr=("${@:2}") + local label_override="" local input_str input_str=$(printf '%s\n' ${input_arr+"${input_arr[@]}"}) @@ -739,10 +733,8 @@ function assert_line_count() { fi if [ "$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")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${input_str}" \ @@ -801,15 +793,14 @@ function assert_string_matches_format() { local format="$1" local actual="$2" + local label_override="${3:-}" local regex regex="$(bashunit::format_to_regex "$format")" if [ "$(printf '%s' "$actual" | "$GREP" -cE "$regex" || true)" -eq 0 ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to match format" "${format}" return @@ -823,15 +814,14 @@ function assert_string_not_matches_format() { local format="$1" local actual="$2" + local label_override="${3:-}" local regex regex="$(bashunit::format_to_regex "$format")" if [ "$(printf '%s' "$actual" | "$GREP" -cE "$regex" || true)" -gt 0 ]; then - local test_fn - test_fn="$(bashunit::helper::find_test_function_name)" local label - label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + label="$(bashunit::assert::label "${label_override:-}")" bashunit::assert::mark_failed bashunit::console_results::print_failed_test "${label}" "${actual}" "to not match format" "${format}" return diff --git a/tests/unit/assert_basic_test.sh b/tests/unit/assert_basic_test.sh index a35ef1c5..14924590 100644 --- a/tests/unit/assert_basic_test.sh +++ b/tests/unit/assert_basic_test.sh @@ -91,3 +91,27 @@ function test_unsuccessful_assert_empty() { "$(bashunit::console_results::print_failed_test "Unsuccessful assert empty" "to be empty" "but got " "1")" \ "$(assert_empty "1")" } + +function test_assert_same_with_custom_label() { + assert_same \ + "$(bashunit::console_results::print_failed_test "my custom label" "1" "but got " "2")" \ + "$(assert_same "1" "2" "my custom label")" +} + +function test_assert_empty_with_custom_label() { + assert_same \ + "$(bashunit::console_results::print_failed_test "my custom label" "to be empty" "but got " "foo")" \ + "$(assert_empty "foo" "my custom label")" +} + +function test_assert_not_empty_with_custom_label() { + assert_same \ + "$(bashunit::console_results::print_failed_test "my custom label" "to not be empty" "but got " "")" \ + "$(assert_not_empty "" "my custom label")" +} + +function test_assert_not_same_with_custom_label() { + assert_same \ + "$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \ + "$(assert_not_same "foo" "foo" "my custom label")" +} From 956d493b13cd0b17a712d4b2cb1a1e43c7c9a173 Mon Sep 17 00:00:00 2001 From: SauronBot Date: Sun, 29 Mar 2026 22:38:07 +0200 Subject: [PATCH 2/2] docs(changelog): add entry for feat #77 custom label on assert functions --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a657701..3ff9f45b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added - Allow `bashunit::spy` to accept an optional exit code (e.g. `bashunit::spy thing 1`) or custom implementation function (e.g. `bashunit::spy thing mock_thing`) (#600) +- Allow most assert functions to accept an optional trailing label parameter to override the failure message title (e.g. `assert_same "a" "$b" "checking user name"`) (#77) ### Fixed - Fix spying on `echo` or `printf` causing bashunit to hang due to infinite recursion (#607)