From 446a94c101aaf24969269709ad0f95f572ae0468 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 9 Jul 2026 21:18:50 +0200 Subject: [PATCH 1/3] feat(assert): add assert_array_length Assert an array has exactly N elements. Includes a doc section (with the regenerated `bashunit doc` snapshot) and CHANGELOG entry. Closes #743 --- CHANGELOG.md | 1 + docs/assertions.md | 21 +++++++++++++++++ src/assert_arrays.sh | 23 +++++++++++++++++++ ...it_should_display_all_assert_docs.snapshot | 7 ++++++ tests/unit/assert_advanced_test.sh | 23 +++++++++++++++++++ 5 files changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1962715..f04135b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Added +- `assert_array_length ` asserts an array has exactly `n` elements (#743) - `assert_file_permissions ` asserts a file's octal permission mode (`644`, `0755`); portable across GNU/BSD `stat` (#742) ### Fixed diff --git a/docs/assertions.md b/docs/assertions.md index 39a42817..81212065 100644 --- a/docs/assertions.md +++ b/docs/assertions.md @@ -613,6 +613,27 @@ function test_failure() { ``` ::: +## assert_array_length +> `assert_array_length "expected_length" "array"` + +Reports an error if `array` does not have exactly `expected_length` elements. + +::: code-group +```bash [Example] +function test_success() { + local haystack=(foo bar baz) + + assert_array_length 3 "${haystack[@]}" +} + +function test_failure() { + local haystack=(foo bar baz) + + assert_array_length 2 "${haystack[@]}" +} +``` +::: + ## assert_successful_code > `assert_successful_code` diff --git a/src/assert_arrays.sh b/src/assert_arrays.sh index 94672151..fa39c0e7 100644 --- a/src/assert_arrays.sh +++ b/src/assert_arrays.sh @@ -78,6 +78,29 @@ function assert_array_contains() { bashunit::state::add_assertions_passed } +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")" + shift + + local -a actual=("$@") + local actual_length="${#actual[@]}" + + if [ "$expected" != "$actual_length" ]; then + bashunit::assert::mark_failed + bashunit::console_results::print_failed_test \ + "${label}" "${actual[*]:-}" "to have length ${expected}" "but got ${actual_length}" + return + fi + + bashunit::state::add_assertions_passed +} + function assert_array_not_contains() { bashunit::assert::should_skip && return 0 diff --git a/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot b/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot index 3657a9d4..9e0977ec 100644 --- a/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot +++ b/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot @@ -254,6 +254,13 @@ Reports an error if `needle` is not an element of `haystack`. - assert_array_not_contains is the inverse of this assertion and takes the same arguments. +## assert_array_length +-------------- +> `assert_array_length "expected_length" "array"` + +Reports an error if `array` does not have exactly `expected_length` elements. + + ## assert_successful_code -------------- > `assert_successful_code` diff --git a/tests/unit/assert_advanced_test.sh b/tests/unit/assert_advanced_test.sh index 208442a0..dbd69e0d 100644 --- a/tests/unit/assert_advanced_test.sh +++ b/tests/unit/assert_advanced_test.sh @@ -118,6 +118,29 @@ function test_unsuccessful_assert_array_not_contains() { "$(assert_array_not_contains "123" "${distros[@]}")" } +function test_successful_assert_array_length() { + local distros + distros=(Ubuntu 123 Linux\ Mint) + + assert_empty "$(assert_array_length 3 "${distros[@]}")" +} + +function test_successful_assert_array_length_with_empty_array() { + local empty=() + + assert_empty "$(assert_array_length 0 "${empty[@]}")" +} + +function test_unsuccessful_assert_array_length() { + local distros + distros=(Ubuntu 123 Linux\ Mint) + + assert_same \ + "$(bashunit::console_results::print_failed_test \ + "Unsuccessful assert array length" "Ubuntu 123 Linux Mint" "to have length 2" "but got 3")" \ + "$(assert_array_length 2 "${distros[@]}")" +} + function test_successful_assert_arrays_equal() { local expected_values expected_values=(Ubuntu 123 Linux\ Mint) From 007b4dd2b0b1013ffd248f7543214ccfcf7f1f6d Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 9 Jul 2026 21:24:33 +0200 Subject: [PATCH 2/3] test(assert): make empty-array length test Bash 3.2 safe Expanding an empty array under set -u is an unbound-variable error on Bash 3.2; call assert_array_length with just the length instead. --- tests/unit/assert_advanced_test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/assert_advanced_test.sh b/tests/unit/assert_advanced_test.sh index dbd69e0d..fc29dd07 100644 --- a/tests/unit/assert_advanced_test.sh +++ b/tests/unit/assert_advanced_test.sh @@ -126,9 +126,9 @@ function test_successful_assert_array_length() { } function test_successful_assert_array_length_with_empty_array() { - local empty=() - - assert_empty "$(assert_array_length 0 "${empty[@]}")" + # Passing no elements (expanding an empty array under `set -u` is an + # "unbound variable" error on Bash 3.2, so we call it with just the length). + assert_empty "$(assert_array_length 0)" } function test_unsuccessful_assert_array_length() { From 0d64a4c7324ccc3b1b94221aa3f2ea80bca372b3 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 9 Jul 2026 21:31:38 +0200 Subject: [PATCH 3/3] fix(assert): use $#/$* in assert_array_length for Bash 3.0 Building a local array from "$@" is unreliable under Bash 3.0 strict mode; count via $# and display via $* instead (no array construction). --- src/assert_arrays.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/assert_arrays.sh b/src/assert_arrays.sh index fa39c0e7..2f917cc6 100644 --- a/src/assert_arrays.sh +++ b/src/assert_arrays.sh @@ -88,13 +88,14 @@ function assert_array_length() { label="$(bashunit::helper::normalize_test_function_name "$test_fn")" shift - local -a actual=("$@") - local actual_length="${#actual[@]}" + # Use $# / $* rather than building an array: on Bash 3.0 under `set -u`, + # expanding "$@" into an array with zero elements is an unbound-variable error. + local actual_length="$#" if [ "$expected" != "$actual_length" ]; then bashunit::assert::mark_failed bashunit::console_results::print_failed_test \ - "${label}" "${actual[*]:-}" "to have length ${expected}" "but got ${actual_length}" + "${label}" "$*" "to have length ${expected}" "but got ${actual_length}" return fi