diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fdb66b0..e3db6e17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The `--parallel` unsupported-OS warning no longer claims Alpine is excluded: Alpine has been a supported parallel platform since the race conditions were fixed, the message was simply never updated ### Fixed +- `bashunit assert ` with no arguments now reports a clear error and exits non-zero. It used to compute a `-1` array index, which Bash 3.x rejects, so it leaked a raw `bad array subscript` and still exited `0` — a malformed standalone assertion in a deploy script reported success (#877) - A missing `--env`/`--boot` bootstrap file is now an error instead of a green run that tested nothing: the file used to be sourced unchecked, so a typo'd path leaked a raw `No such file or directory`, skipped the entire suite and still exited `0`. The report options (`--report-json`, `--log-junit`, `--report-tap`, `--report-html`, `--log-gha`) are likewise checked before the run instead of failing silently after it — an unwritable destination produced a passing run, no report on disk, and exit `0`. `--seed` now validates as a non-negative integer like the other numeric options (#875) - The numeric options (`--jobs`, `--retry`, `--test-timeout`, `--coverage-min`) and `--output` now reject an invalid value with a clear error and a non-zero exit, instead of dropping the setting. `--jobs abc` was the worst case: `[ n -lt abc ]` errors rather than returning false, so the Bash 3.x job-slot poll never reached its `break` and the run **hung**, while Bash 4.3+ silently ignored the concurrency limit. `--coverage-min abc` leaked a raw `[: abc: integer expression expected` and still exited `0`, so a CI job never enforced its threshold. Validation runs on the resolved configuration, so it covers the `BASHUNIT_*` environment variables too (#873) - An unknown option is now rejected with a clear error and a non-zero exit instead of being silently filed under test paths. A mistyped flag used to degrade the run while still reporting success: `bashunit --parralel tests/` ran sequentially and exited `0`, and `bashunit --filterr foo tests/` swallowed both the flag and its value and ran the whole suite. Applies to `test` and `bench`; the `assert` subcommand parses its own arguments and is unchanged (#871) diff --git a/src/main.sh b/src/main.sh index e005b260..3087f5ef 100644 --- a/src/main.sh +++ b/src/main.sh @@ -1177,6 +1177,16 @@ function bashunit::main::exec_assert() { fi fi + # Every assertion needs at least one argument. Without this guard args_count is + # 0, so the last_index below is -1: Bash 3.x has no negative subscripts, so the + # expansion failed with a raw `bad array subscript` and the run still exited 0 + # (#877). + if [ "$args_count" -lt 1 ]; then + printf "%sError: assert %s requires at least one argument.%s\n" \ + "${_BASHUNIT_COLOR_FAILED}" "$original_assert_fn" "${_BASHUNIT_COLOR_DEFAULT}" >&2 + exit 1 + fi + # Get the last argument safely by calculating the array length local last_index=$((args_count - 1)) local last_arg="${args[$last_index]}" diff --git a/tests/acceptance/bashunit_assert_errors_test.sh b/tests/acceptance/bashunit_assert_errors_test.sh index 513e0269..fc12aa58 100644 --- a/tests/acceptance/bashunit_assert_errors_test.sh +++ b/tests/acceptance/bashunit_assert_errors_test.sh @@ -21,6 +21,35 @@ function test_bashunit_assert_subcommand_non_existing_function() { assert_command_not_found "" "" "$exit_code" } +# args_count was 0, so `${args[-1]}` expanded to a negative subscript: a raw +# `bad array subscript` on Bash 3.x, and the run still exited 0 (#877). +function test_bashunit_assert_subcommand_without_arguments() { + local output + local exit_code + output=$(./bashunit assert equals 2>&1) && exit_code=$? || exit_code=$? + + assert_general_error "" "" "$exit_code" + assert_not_contains "bad array subscript" "$output" + assert_contains "equals" "$output" +} + +function test_bashunit_assert_subcommand_without_arguments_for_a_code_assertion() { + local output + local exit_code + output=$(./bashunit assert successful_code 2>&1) && exit_code=$? || exit_code=$? + + assert_general_error "" "" "$exit_code" + assert_not_contains "bad array subscript" "$output" +} + +function test_bashunit_assert_subcommand_still_accepts_a_single_argument() { + local output + local exit_code + output=$(./bashunit assert empty "" 2>&1) && exit_code=$? || exit_code=$? + + assert_successful_code "" "" "$exit_code" +} + function test_bashunit_assert_subcommand_failure() { local exit_code ./bashunit --no-parallel assert equals "foo" "bar" 2>&1 && exit_code=$? || exit_code=$?