Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make asserting failed exit codes and output nicer #336

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Renamed `BASHUNIT_TESTS_ENV` to `BASHUNIT_LOAD_FILE`
- Better handler runtime errors
- Display failing tests after running the entire suite
- Added defer expressions with `eval` when using standalone assertions

## [0.16.0](https://github.com/TypedDevs/bashunit/compare/0.15.0...0.16.0) - 2024-09-15

Expand Down
50 changes: 50 additions & 0 deletions docs/standalone.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,53 @@ The prefix `assert_` is optional.
```
:::

## Lazy evaluations

You can evaluate the `exit_code` for your scripts using the command to call as raw-string instead of
executing them with `$(...)` to avoid interrupting the CI when encountering a potential error (anything but `0`).

::: code-group
```bash [Example]
./bashunit -a exit_code "1" "$PHPSTAN_PATH analyze \
--no-progress --level 8 \
--error-format raw ./"
```
```[Output]
Testing.php:3:Method Testing::bar() has no return type specified.
```
:::

This is useful to get control over the output of your "callable":

::: code-group
```bash [Example]
OUTPUT=$(./bashunit -a exit_code "1" "$PHPSTAN_PATH analyze \
--no-progress --level 8 \
--error-format raw ./")
./bashunit -a line_count 1 "$OUTPUT"
```
```[Output]
# No output
```
:::

### Full control over the stdout and stderr

The stdout will be used for the callable result, while bashunit output will be on stderr.
This way you can control the FD and redirect the output as you need.

::: code-group
```bash [Example]
./bashunit -a exit_code "0" "$PHPSTAN_PATH analyze \
--no-progress --level 8 \
--error-format raw ./" 2> /tmp/error.log
```
```[Output]
Testing.php:3:Method Testing::bar() has no return type specified.
```
```[/tmp/error.log]
βœ— Failed: Main::exec assert
Expected '0'
but got '1'
```
:::
2 changes: 1 addition & 1 deletion src/assert.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ function assert_general_error() {
local actual_exit_code=${3-"$?"}
local expected_exit_code=1

if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then
if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then
local label
label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
Expand Down
65 changes: 60 additions & 5 deletions src/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,76 @@ function main::exec_tests() {

function main::exec_assert() {
local original_assert_fn=$1
local assert_fn=$original_assert_fn
local args=("${@:2}")

local assert_fn=$original_assert_fn

# Check if the function exists
if ! type "$assert_fn" > /dev/null 2>&1; then
# try again using prefix `assert_`
assert_fn="assert_$assert_fn"
if ! type "$assert_fn" > /dev/null 2>&1; then
echo "Function $original_assert_fn does not exist."
echo "Function $original_assert_fn does not exist." 1>&2
exit 127
fi
fi

"$assert_fn" "${args[@]}"
# Get the last argument safely by calculating the array length
local last_index=$((${#args[@]} - 1))
local last_arg="${args[$last_index]}"
local output=""
local inner_exit_code=0
local bashunit_exit_code=0

# Handle different assert_* functions
case "$assert_fn" in
assert_exit_code)
output=$(main::handle_assert_exit_code "$last_arg")
inner_exit_code=$?
# Remove the last argument and append the exit code
args=("${args[@]:0:last_index}")
args+=("$inner_exit_code")
;;
*)
# Add more cases here for other assert_* handlers if needed
;;
esac

if [[ -n "$output" ]]; then
echo "$output" 1>&1
assert_fn="assert_same"
fi

# Run the assertion function and write into stderr
"$assert_fn" "${args[@]}" 1>&2
bashunit_exit_code=$?

if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then
exit 1
return 1
fi

return "$bashunit_exit_code"
}

function main::handle_assert_exit_code() {
local cmd="$1"
local output
local inner_exit_code=0

if [[ $(command -v "${cmd%% *}") ]]; then
output=$(eval "$cmd" 2>&1 || echo "inner_exit_code:$?")
local last_line
last_line=$(echo "$output" | tail -n 1)
if echo "$last_line" | grep -q 'inner_exit_code:[0-9]*'; then
inner_exit_code=$(echo "$last_line" | grep -o 'inner_exit_code:[0-9]*' | cut -d':' -f2)
if ! [[ $inner_exit_code =~ ^[0-9]+$ ]]; then
inner_exit_code=1
fi
output=$(echo "$output" | sed '$d')
fi
echo "$output"
return "$inner_exit_code"
else
echo "Command not found: $cmd" 1>&2
return 127
fi
}
66 changes: 64 additions & 2 deletions tests/acceptance/bashunit_direct_fn_call_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,73 @@ function test_bashunit_direct_fn_call_failure() {
local expected="foo"
local actual="bar"

assert_match_snapshot "$(./bashunit -a assert_same --env "$TEST_ENV_FILE" "$expected" $actual)"
assert_match_snapshot "$(./bashunit -a assert_same --env "$TEST_ENV_FILE" "$expected" $actual 2>&1)"
assert_general_error "$(./bashunit -a assert_same --env "$TEST_ENV_FILE" "$expected" $actual)"
}

function test_bashunit_direct_fn_call_non_existing_fn() {
assert_match_snapshot "$(./bashunit -a non_existing_fn --env "$TEST_ENV_FILE")"
assert_match_snapshot "$(./bashunit -a non_existing_fn --env "$TEST_ENV_FILE" 2>&1)"
assert_command_not_found "$(./bashunit -a non_existing_fn --env "$TEST_ENV_FILE")"
}

# shellcheck disable=SC2155
function test_bashunit_assert_exit_code_successful_with_inner_func() {
local temp=$(mktemp)
# shellcheck disable=SC2116
local output="$(./bashunit -a exit_code "0" "$(echo "unknown command")" 2> "$temp")"

assert_empty "$output"
assert_same "Command not found: unknown command" "$(cat "$temp")"

rm "$temp"
}

# shellcheck disable=SC2155
function test_bashunit_assert_exit_code_error_with_inner_func() {
local temp=$(mktemp)
# shellcheck disable=SC2116
local output="$(./bashunit -a exit_code "1" "$(echo "unknown command")" 2> "$temp")"

assert_empty "$output"

assert_contains\
"$(console_results::print_failed_test "Main::exec assert" "0" "to be" "1")"\
"$(cat "$temp")"

rm "$temp"
}

function test_bashunit_assert_exit_code_str_successful_code() {
./bashunit -a exit_code "0" "./bashunit -a same 1 1"
assert_successful_code
}

function test_bashunit_assert_exit_code_str_general_error() {
./bashunit -a exit_code "1" "./bashunit -a same 1 2"
assert_successful_code
}

# shellcheck disable=SC2155
function test_bashunit_assert_exit_code_str_successful_but_exit_code_error() {
local temp=$(mktemp)
local output="$(./bashunit -a exit_code "1" "echo something to stdout" 2> "$temp")"

assert_same "something to stdout" "$output"

assert_contains\
"$(console_results::print_failed_test "Main::exec assert" "1" "but got " "0")"\
"$(cat "$temp")"

rm "$temp"
}

# shellcheck disable=SC2155
function test_bashunit_assert_exit_code_str_successful_and_exit_code_ok() {
local temp=$(mktemp)
local output="$(./bashunit -a exit_code "0" "echo something to stdout" 2> "$temp")"

assert_same "something to stdout" "$output"
assert_empty "$(cat "$temp")"

rm "$temp"
}
Loading