diff --git a/CHANGELOG.md b/CHANGELOG.md index 47607190..1515b04b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ ## Unreleased +### Added +- Add risky test detection for tests with zero assertions (shown as warning, does not fail) + ### Fixed - Fix `source` of non-existent file in `set_up()` silently passing all tests (#611) +- Fix `set_up` running before strict mode — unbound variables in hooks now detected with `--strict` +- Fix `source` failure in `tear_down()`, `set_up_before_script()`, and `tear_down_after_script()` silently passing +- Add missing runtime error patterns: ambiguous redirect, integer expression expected, too many arguments, value too great, not a valid identifier, unexpected EOF ## [0.34.0](https://github.com/TypedDevs/bashunit/compare/0.33.0...0.34.0) - 2026-03-17 diff --git a/src/assert_dates.sh b/src/assert_dates.sh index cfd49ad5..3b709ab4 100644 --- a/src/assert_dates.sh +++ b/src/assert_dates.sh @@ -12,13 +12,31 @@ function bashunit::date::to_epoch() { ;; esac + # Normalize ISO 8601: replace T with space, strip Z suffix, strip tz offset + local normalized="$input" + normalized="${normalized/T/ }" + normalized="${normalized%Z}" + # Strip timezone offset (+HHMM or -HHMM) at end for initial parsing + case "$normalized" in + *[+-][0-9][0-9][0-9][0-9]) + normalized="${normalized%[+-][0-9][0-9][0-9][0-9]}" + ;; + esac + # Format conversion (GNU vs BSD date) local epoch - # Try GNU date first (-d flag) + # Try GNU date first (-d flag) with original input epoch=$(date -d "$input" +%s 2>/dev/null) && { echo "$epoch" return 0 } + # Try GNU date with normalized (space-separated) input + if [[ "$normalized" != "$input" ]]; then + epoch=$(date -d "$normalized" +%s 2>/dev/null) && { + echo "$epoch" + return 0 + } + fi # Try BSD date (-j -f flag) with ISO 8601 datetime + timezone offset epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S%z" "$input" +%s 2>/dev/null) && { echo "$epoch" diff --git a/src/colors.sh b/src/colors.sh index 3ce77abc..429a6b84 100644 --- a/src/colors.sh +++ b/src/colors.sh @@ -27,11 +27,13 @@ if bashunit::env::is_no_color_enabled; then _BASHUNIT_COLOR_SKIPPED="" _BASHUNIT_COLOR_INCOMPLETE="" _BASHUNIT_COLOR_SNAPSHOT="" + _BASHUNIT_COLOR_RISKY="" _BASHUNIT_COLOR_RETURN_ERROR="" _BASHUNIT_COLOR_RETURN_SUCCESS="" _BASHUNIT_COLOR_RETURN_SKIPPED="" _BASHUNIT_COLOR_RETURN_INCOMPLETE="" _BASHUNIT_COLOR_RETURN_SNAPSHOT="" + _BASHUNIT_COLOR_RETURN_RISKY="" _BASHUNIT_COLOR_DEFAULT="" else _BASHUNIT_COLOR_BOLD="$(bashunit::sgr 1)" @@ -42,10 +44,12 @@ else _BASHUNIT_COLOR_SKIPPED="$(bashunit::sgr 33)" _BASHUNIT_COLOR_INCOMPLETE="$(bashunit::sgr 36)" _BASHUNIT_COLOR_SNAPSHOT="$(bashunit::sgr 34)" + _BASHUNIT_COLOR_RISKY="$(bashunit::sgr 35)" _BASHUNIT_COLOR_RETURN_ERROR="$(bashunit::sgr 41)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD" _BASHUNIT_COLOR_RETURN_SUCCESS="$(bashunit::sgr 42)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD" _BASHUNIT_COLOR_RETURN_SKIPPED="$(bashunit::sgr 43)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD" _BASHUNIT_COLOR_RETURN_INCOMPLETE="$(bashunit::sgr 46)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD" _BASHUNIT_COLOR_RETURN_SNAPSHOT="$(bashunit::sgr 44)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD" + _BASHUNIT_COLOR_RETURN_RISKY="$(bashunit::sgr 45)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD" _BASHUNIT_COLOR_DEFAULT="$(bashunit::sgr 0)" fi diff --git a/src/console_results.sh b/src/console_results.sh index abebc6d7..665cbe22 100644 --- a/src/console_results.sh +++ b/src/console_results.sh @@ -30,6 +30,7 @@ function bashunit::console_results::render_result() { local tests_incomplete=$_BASHUNIT_TESTS_INCOMPLETE local tests_snapshot=$_BASHUNIT_TESTS_SNAPSHOT local tests_failed=$_BASHUNIT_TESTS_FAILED + local tests_risky=$_BASHUNIT_TESTS_RISKY local assertions_passed=$_BASHUNIT_ASSERTIONS_PASSED local assertions_skipped=$_BASHUNIT_ASSERTIONS_SKIPPED local assertions_incomplete=$_BASHUNIT_ASSERTIONS_INCOMPLETE @@ -42,6 +43,7 @@ function bashunit::console_results::render_result() { total_tests=$((total_tests + tests_incomplete)) total_tests=$((total_tests + tests_snapshot)) total_tests=$((total_tests + tests_failed)) + total_tests=$((total_tests + tests_risky)) local total_assertions=0 total_assertions=$((total_assertions + assertions_passed)) @@ -66,6 +68,9 @@ function bashunit::console_results::render_result() { if [[ "$tests_failed" -gt 0 ]] || [[ "$assertions_failed" -gt 0 ]]; then printf " %s%s failed%s," "$_BASHUNIT_COLOR_FAILED" "$tests_failed" "$_BASHUNIT_COLOR_DEFAULT" fi + if [[ "$tests_risky" -gt 0 ]]; then + printf " %s%s risky%s," "$_BASHUNIT_COLOR_RISKY" "$tests_risky" "$_BASHUNIT_COLOR_DEFAULT" + fi printf " %s total\n" "$total_tests" printf "%sAssertions:%s" "$_BASHUNIT_COLOR_FAINT" "$_BASHUNIT_COLOR_DEFAULT" @@ -92,6 +97,12 @@ function bashunit::console_results::render_result() { return 1 fi + if [[ "$tests_risky" -gt 0 ]]; then + printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_RISKY" " Some tests risky (no assertions) " "$_BASHUNIT_COLOR_DEFAULT" + bashunit::console_results::print_execution_time + return 0 + fi + if [[ "$tests_incomplete" -gt 0 ]]; then printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_BASHUNIT_COLOR_DEFAULT" bashunit::console_results::print_execution_time @@ -359,6 +370,23 @@ function bashunit::console_results::print_snapshot_test() { bashunit::state::print_line "snapshot" "$line" } +function bashunit::console_results::print_risky_test() { + local test_name=$1 + local duration=${2:-"0"} + + local line + line=$(printf "%s⚠ Risky%s: %s" "$_BASHUNIT_COLOR_RISKY" "$_BASHUNIT_COLOR_DEFAULT" "$test_name") + + local full_line=$line + if bashunit::env::is_show_execution_time_enabled; then + local time_display + time_display=$(bashunit::console_results::format_duration "$duration") + full_line="$(printf "%s\n" "$(bashunit::str::rpad "$line" "$time_display")")" + fi + + bashunit::state::print_line "risky" "$full_line" +} + function bashunit::console_results::print_error_test() { local function_name=$1 local error="$2" @@ -447,3 +475,25 @@ function bashunit::console_results::print_incomplete_tests_and_reset() { echo "" 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 "" + fi +} diff --git a/src/env.sh b/src/env.sh index c48f1c37..5d191aac 100644 --- a/src/env.sh +++ b/src/env.sh @@ -276,6 +276,7 @@ MKTEMP="$(command -v mktemp)" FAILURES_OUTPUT_PATH=$("$MKTEMP") SKIPPED_OUTPUT_PATH=$("$MKTEMP") INCOMPLETE_OUTPUT_PATH=$("$MKTEMP") +RISKY_OUTPUT_PATH=$("$MKTEMP") # Initialize temp directory once at startup for performance BASHUNIT_TEMP_DIR="${TMPDIR:-/tmp}/bashunit/tmp" diff --git a/src/main.sh b/src/main.sh index e5cc579b..e8017ba1 100644 --- a/src/main.sh +++ b/src/main.sh @@ -657,6 +657,7 @@ function bashunit::main::exec_tests() { if ! bashunit::env::is_tap_output_enabled; then bashunit::console_results::print_failing_tests_and_reset + bashunit::console_results::print_risky_tests_and_reset bashunit::console_results::print_incomplete_tests_and_reset bashunit::console_results::print_skipped_tests_and_reset fi @@ -750,6 +751,7 @@ function bashunit::main::cleanup() { function bashunit::main::handle_stop_on_failure_sync() { printf "\n%sStop on failure enabled...%s\n" "${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}" bashunit::console_results::print_failing_tests_and_reset + bashunit::console_results::print_risky_tests_and_reset bashunit::console_results::print_incomplete_tests_and_reset bashunit::console_results::print_skipped_tests_and_reset bashunit::console_results::render_result diff --git a/src/parallel.sh b/src/parallel.sh index 6787e4ab..edd3fa91 100755 --- a/src/parallel.sh +++ b/src/parallel.sh @@ -87,6 +87,13 @@ function bashunit::parallel::aggregate_test_results() { continue fi + # Check for risky test (zero assertions, no error) + local total_for_test=$((failed + passed + skipped + incomplete + snapshot)) + if [ "$total_for_test" -eq 0 ] && [ "${exit_code:-0}" -eq 0 ]; then + bashunit::state::add_tests_risky + continue + fi + bashunit::state::add_tests_passed done done diff --git a/src/reports.sh b/src/reports.sh index 21a10123..e8024698 100755 --- a/src/reports.sh +++ b/src/reports.sh @@ -24,6 +24,10 @@ function bashunit::reports::add_test_passed() { bashunit::reports::add_test "$1" "$2" "$3" "$4" "passed" } +function bashunit::reports::add_test_risky() { + bashunit::reports::add_test "$1" "$2" "$3" "$4" "risky" +} + function bashunit::reports::add_test_failed() { bashunit::reports::add_test "$1" "$2" "$3" "$4" "failed" "$5" } @@ -94,6 +98,8 @@ function bashunit::reports::generate_junit_xml() { local escaped_message escaped_message=$(bashunit::reports::__xml_escape "$failure_message") echo " $escaped_message" + elif [[ "$status" == "risky" ]]; then + echo " " elif [[ "$status" == "skipped" ]]; then echo " " elif [[ "$status" == "incomplete" ]]; then @@ -151,6 +157,7 @@ function bashunit::reports::generate_report_html() { echo " .skipped { background-color: #fcf8e3; }" echo " .incomplete { background-color: #d9edf7; }" echo " .snapshot { background-color: #dfe6e9; }" + echo " .risky { background-color: #f5e6f5; }" echo " " echo "" echo "" diff --git a/src/runner.sh b/src/runner.sh index 54cd0628..6c8255de 100755 --- a/src/runner.sh +++ b/src/runner.sh @@ -691,7 +691,10 @@ function bashunit::runner::run_test() { "division by 0" "cannot allocate memory" "bad file descriptor" \ "segmentation fault" "illegal option" "argument list too long" \ "readonly variable" "missing keyword" "killed" \ - "cannot execute binary file" "invalid arithmetic operator"; do + "cannot execute binary file" "invalid arithmetic operator" \ + "ambiguous redirect" "integer expression expected" \ + "too many arguments" "value too great" \ + "not a valid identifier" "unexpected EOF"; do if [[ "$runtime_output" == *"$error"* ]]; then runtime_error="${runtime_output#*: }" # Remove everything up to and including ": " runtime_error=${runtime_error//$'\n'/} # Remove all newlines using parameter expansion @@ -814,6 +817,18 @@ function bashunit::runner::run_test() { return fi + # Check for risky test (zero assertions) + if [[ "$total_assertions" -eq 0 ]]; then + bashunit::state::add_tests_risky + if ! bashunit::env::is_failures_only_enabled; then + bashunit::console_results::print_risky_test "${label}" "$duration" + fi + bashunit::reports::add_test_risky "$test_file" "$label" "$duration" "$total_assertions" + bashunit::runner::write_risky_result_output "$test_file" "$fn_name" + bashunit::internal_log "Test risky" "$label" + return + fi + # In failures-only mode, suppress successful test output if ! bashunit::env::is_failures_only_enabled; then if [[ "$fn_name" == "$interpolated_fn_name" ]]; then @@ -1066,6 +1081,21 @@ function bashunit::runner::write_incomplete_result_output() { echo -e "$test_nr) $test_file:$line_number\n$output_msg" >>"$INCOMPLETE_OUTPUT_PATH" } +function bashunit::runner::write_risky_result_output() { + local test_file=$1 + local fn_name=$2 + + local line_number + line_number=$(bashunit::helper::get_function_line_number "$fn_name") + + local test_nr="*" + if ! bashunit::parallel::is_enabled; then + test_nr=$(bashunit::state::get_tests_risky) + fi + + echo -e "$test_nr) $test_file:$line_number\nTest has no assertions (risky)" >>"$RISKY_OUTPUT_PATH" +} + function bashunit::runner::record_file_hook_failure() { local hook_name="$1" local test_file="$2" @@ -1103,15 +1133,19 @@ function bashunit::runner::execute_file_hook() { local hook_output_file hook_output_file=$(bashunit::temp_file "${hook_name}_output") - # Enable errexit and errtrace to catch any failing command in the hook. - # The ERR trap saves the exit status to a global variable (since return value - # from trap doesn't propagate properly), disables errexit (to prevent caller - # from exiting) and returns from the hook function, preventing subsequent - # commands from executing. + # Enable errtrace to catch any failing command in the hook. + # Using -E (errtrace) without -e (errexit) prevents the main process from + # exiting on source failures (Bash 3.2 doesn't trigger ERR trap with -eE). + # The ERR trap saves the exit status to a global variable, cleans up shell + # options, and returns from the hook function to prevent subsequent commands + # from executing. # Variables set before the failure are preserved since we don't use a subshell. _BASHUNIT_HOOK_ERR_STATUS=0 - set -eE - trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +eE; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR + set -E + if bashunit::env::is_strict_mode_enabled; then + set -uo pipefail + fi + trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +Eu +o pipefail; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR { "$hook_name" @@ -1120,7 +1154,7 @@ function bashunit::runner::execute_file_hook() { # Capture exit status from global variable and clean up status=$_BASHUNIT_HOOK_ERR_STATUS trap - ERR - set +eE + set +Eu +o pipefail if [[ -f "$hook_output_file" ]]; then hook_output="" @@ -1204,15 +1238,19 @@ function bashunit::runner::execute_test_hook() { local hook_output_file hook_output_file=$(bashunit::temp_file "${hook_name}_output") - # Enable errexit and errtrace to catch any failing command in the hook. - # The ERR trap saves the exit status to a global variable (since return value - # from trap doesn't propagate properly), disables errexit (to prevent caller - # from exiting) and returns from the hook function, preventing subsequent - # commands from executing. + # Enable errtrace to catch any failing command in the hook. + # Using -E (errtrace) without -e (errexit) prevents the subshell from + # exiting on source failures (Bash 3.2 doesn't trigger ERR trap with -eE). + # The ERR trap saves the exit status to a global variable, cleans up shell + # options, and returns from the hook function to prevent subsequent commands + # from executing. # Variables set before the failure are preserved since we don't use a subshell. _BASHUNIT_HOOK_ERR_STATUS=0 - set -eE - trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +eE; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR + set -E + if bashunit::env::is_strict_mode_enabled; then + set -uo pipefail + fi + trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +Eu +o pipefail; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR { "$hook_name" @@ -1221,7 +1259,7 @@ function bashunit::runner::execute_test_hook() { # Capture exit status from global variable and clean up status=$_BASHUNIT_HOOK_ERR_STATUS trap - ERR - set +eE + set +Eu +o pipefail if [[ -f "$hook_output_file" ]]; then hook_output="" diff --git a/src/state.sh b/src/state.sh index 08d732a4..4d526776 100644 --- a/src/state.sh +++ b/src/state.sh @@ -12,6 +12,7 @@ _BASHUNIT_TESTS_FAILED=0 _BASHUNIT_TESTS_SKIPPED=0 _BASHUNIT_TESTS_INCOMPLETE=0 _BASHUNIT_TESTS_SNAPSHOT=0 +_BASHUNIT_TESTS_RISKY=0 _BASHUNIT_ASSERTIONS_PASSED=0 _BASHUNIT_ASSERTIONS_FAILED=0 _BASHUNIT_ASSERTIONS_SKIPPED=0 @@ -68,6 +69,14 @@ function bashunit::state::add_tests_snapshot() { ((_BASHUNIT_TESTS_SNAPSHOT++)) || true } +function bashunit::state::get_tests_risky() { + echo "$_BASHUNIT_TESTS_RISKY" +} + +function bashunit::state::add_tests_risky() { + ((_BASHUNIT_TESTS_RISKY++)) || true +} + function bashunit::state::get_assertions_passed() { echo "$_BASHUNIT_ASSERTIONS_PASSED" } @@ -298,6 +307,7 @@ function bashunit::state::print_line() { skipped) char="${_BASHUNIT_COLOR_SKIPPED}S${_BASHUNIT_COLOR_DEFAULT}" ;; incomplete) char="${_BASHUNIT_COLOR_INCOMPLETE}I${_BASHUNIT_COLOR_DEFAULT}" ;; snapshot) char="${_BASHUNIT_COLOR_SNAPSHOT}N${_BASHUNIT_COLOR_DEFAULT}" ;; + risky) char="${_BASHUNIT_COLOR_RISKY}R${_BASHUNIT_COLOR_DEFAULT}" ;; error) char="${_BASHUNIT_COLOR_FAILED}E${_BASHUNIT_COLOR_DEFAULT}" ;; *) char="?" && bashunit::log "warning" "unknown test type '$type'" ;; esac @@ -364,6 +374,10 @@ function bashunit::state::print_tap_line() { printf "ok %d - %s # snapshot\n" \ "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name" ;; + risky) + printf "ok %d - %s # RISKY no assertions\n" \ + "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name" + ;; *) printf "not ok %d - %s\n" \ "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name" diff --git a/tests/acceptance/bashunit_hook_source_failure_test.sh b/tests/acceptance/bashunit_hook_source_failure_test.sh new file mode 100644 index 00000000..211e4deb --- /dev/null +++ b/tests/acceptance/bashunit_hook_source_failure_test.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" +} + +function strip_ansi() { + sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' +} + +function test_bashunit_when_tear_down_sources_nonexistent_file() { + local test_file=./tests/acceptance/fixtures/test_bashunit_when_teardown_sources_nonexistent_file.sh + + local actual_raw + set +e + actual_raw="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file")" + set -e + + local actual + actual="$(printf "%s" "$actual_raw" | strip_ansi)" + + assert_contains "failed" "$actual" + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")" +} + +function test_bashunit_when_set_up_before_script_sources_nonexistent_file() { + local test_file=./tests/acceptance/fixtures/test_bashunit_when_setup_before_script_sources_nonexistent_file.sh + + local actual_raw + set +e + actual_raw="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file")" + set -e + + local actual + actual="$(printf "%s" "$actual_raw" | strip_ansi)" + + assert_contains "failed" "$actual" + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")" +} + +function test_bashunit_when_tear_down_after_script_sources_nonexistent_file() { + local test_file=./tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_sources_nonexistent_file.sh + + local actual_raw + set +e + actual_raw="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file")" + set -e + + local actual + actual="$(printf "%s" "$actual_raw" | strip_ansi)" + + assert_contains "failed" "$actual" + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")" +} diff --git a/tests/acceptance/bashunit_risky_test.sh b/tests/acceptance/bashunit_risky_test.sh new file mode 100644 index 00000000..e4964991 --- /dev/null +++ b/tests/acceptance/bashunit_risky_test.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" +} + +function strip_ansi() { + sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' +} + +function test_bashunit_risky_test_shows_warning() { + local test_file=./tests/acceptance/fixtures/test_bashunit_risky_no_assertions.sh + + local actual_raw + actual_raw="$(BASHUNIT_STRICT_MODE=false ./bashunit \ + --no-parallel --detailed --skip-env-file --env "$TEST_ENV_FILE" "$test_file")" + + local actual + actual="$(printf "%s" "$actual_raw" | strip_ansi)" + + assert_contains "Risky" "$actual" + assert_contains "1 risky" "$actual" +} + +function test_bashunit_risky_test_does_not_fail() { + local test_file=./tests/acceptance/fixtures/test_bashunit_risky_no_assertions.sh + + local actual_raw + actual_raw="$(BASHUNIT_STRICT_MODE=false ./bashunit \ + --no-parallel --simple --skip-env-file --env "$TEST_ENV_FILE" "$test_file")" + + local actual + actual="$(printf "%s" "$actual_raw" | strip_ansi)" + + assert_contains "risky" "$actual" + assert_not_contains "failed" "$actual" +} diff --git a/tests/acceptance/bashunit_strict_mode_test.sh b/tests/acceptance/bashunit_strict_mode_test.sh index cc05ea61..64d8bf93 100644 --- a/tests/acceptance/bashunit_strict_mode_test.sh +++ b/tests/acceptance/bashunit_strict_mode_test.sh @@ -36,6 +36,14 @@ function test_strict_mode_fails_on_nonzero_returns() { assert_contains "failed" "$output" } +function test_strict_mode_fails_on_unset_variable_in_set_up() { + local output + output=$(BASHUNIT_STRICT_MODE=true ./bashunit --no-parallel --simple --skip-env-file --env "$TEST_ENV_FILE" \ + tests/acceptance/fixtures/strict_mode_setup_unset_variable.sh 2>&1) || true + + assert_contains "failed" "$output" +} + function test_cli_flag_overrides_env_var() { local output output=$(BASHUNIT_STRICT_MODE=false ./bashunit \ diff --git a/tests/acceptance/bashunit_teardown_after_script_error_test.sh b/tests/acceptance/bashunit_teardown_after_script_error_test.sh index 9562717d..03878918 100644 --- a/tests/acceptance/bashunit_teardown_after_script_error_test.sh +++ b/tests/acceptance/bashunit_teardown_after_script_error_test.sh @@ -17,7 +17,7 @@ function test_bashunit_when_tear_down_after_script_errors() { local error_line="āœ— Error: Tear down after script" local message_line=" $fixture: line 4: missing_cleanup_command: command not found" local tests_summary="Tests: 1 passed, 1 failed, 2 total" - local assertions_summary="Assertions: 0 passed, 0 failed, 0 total" + local assertions_summary="Assertions: 1 passed, 0 failed, 1 total" local actual_raw set +e diff --git a/tests/acceptance/fixtures/strict_mode_setup_unset_variable.sh b/tests/acceptance/fixtures/strict_mode_setup_unset_variable.sh new file mode 100644 index 00000000..4e85109f --- /dev/null +++ b/tests/acceptance/fixtures/strict_mode_setup_unset_variable.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +function set_up() { + local result="prefix_${UNDEFINED_VAR}_suffix" +} + +function test_dummy() { + assert_same "foo" "foo" +} diff --git a/tests/acceptance/fixtures/test_bashunit_risky_no_assertions.sh b/tests/acceptance/fixtures/test_bashunit_risky_no_assertions.sh new file mode 100644 index 00000000..716de633 --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_risky_no_assertions.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +function test_has_no_assertions() { + local user="invalid" +} diff --git a/tests/acceptance/fixtures/test_bashunit_when_setup_before_script_sources_nonexistent_file.sh b/tests/acceptance/fixtures/test_bashunit_when_setup_before_script_sources_nonexistent_file.sh new file mode 100644 index 00000000..47f15a7a --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_when_setup_before_script_sources_nonexistent_file.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +function set_up_before_script() { + # shellcheck disable=SC1091 + source ./this_file_does_not_exist.sh +} + +function test_dummy() { + assert_same "foo" "foo" +} diff --git a/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_errors.sh b/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_errors.sh index d70bf67a..e19d04cc 100644 --- a/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_errors.sh +++ b/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_errors.sh @@ -5,5 +5,5 @@ function tear_down_after_script() { } function test_sample() { - : + assert_same "foo" "foo" } diff --git a/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_sources_nonexistent_file.sh b/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_sources_nonexistent_file.sh new file mode 100644 index 00000000..d3f8d677 --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_when_teardown_after_script_sources_nonexistent_file.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +function tear_down_after_script() { + # shellcheck disable=SC1091 + source ./this_file_does_not_exist.sh +} + +function test_dummy() { + assert_same "foo" "foo" +} diff --git a/tests/acceptance/fixtures/test_bashunit_when_teardown_sources_nonexistent_file.sh b/tests/acceptance/fixtures/test_bashunit_when_teardown_sources_nonexistent_file.sh new file mode 100644 index 00000000..38f1977d --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_when_teardown_sources_nonexistent_file.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +function tear_down() { + # shellcheck disable=SC1091 + source ./this_file_does_not_exist.sh +} + +function test_dummy() { + assert_same "foo" "foo" +} diff --git a/tests/unit/console_results_test.sh b/tests/unit/console_results_test.sh index 87597c4a..1c28a0e5 100644 --- a/tests/unit/console_results_test.sh +++ b/tests/unit/console_results_test.sh @@ -13,6 +13,7 @@ function set_state_value() { bashunit::state::get_tests_skipped) var_name="_BASHUNIT_TESTS_SKIPPED" ;; bashunit::state::get_tests_incomplete) var_name="_BASHUNIT_TESTS_INCOMPLETE" ;; bashunit::state::get_tests_snapshot) var_name="_BASHUNIT_TESTS_SNAPSHOT" ;; + bashunit::state::get_tests_risky) var_name="_BASHUNIT_TESTS_RISKY" ;; bashunit::state::get_assertions_passed) var_name="_BASHUNIT_ASSERTIONS_PASSED" ;; bashunit::state::get_assertions_failed) var_name="_BASHUNIT_ASSERTIONS_FAILED" ;; bashunit::state::get_assertions_skipped) var_name="_BASHUNIT_ASSERTIONS_SKIPPED" ;; @@ -40,6 +41,7 @@ function mock_all_state_getters() { set_state_value "bashunit::state::get_tests_skipped" "0" set_state_value "bashunit::state::get_tests_incomplete" "0" set_state_value "bashunit::state::get_tests_snapshot" "0" + set_state_value "bashunit::state::get_tests_risky" "0" set_state_value "bashunit::state::get_assertions_passed" "0" set_state_value "bashunit::state::get_assertions_failed" "0" set_state_value "bashunit::state::get_assertions_skipped" "0" @@ -52,6 +54,7 @@ function mock_all_state_getters() { _BASHUNIT_TESTS_SKIPPED=0 _BASHUNIT_TESTS_INCOMPLETE=0 _BASHUNIT_TESTS_SNAPSHOT=0 + _BASHUNIT_TESTS_RISKY=0 _BASHUNIT_ASSERTIONS_PASSED=0 _BASHUNIT_ASSERTIONS_FAILED=0 _BASHUNIT_ASSERTIONS_SKIPPED=0