From 16142f904ff8b3acb343ef1f05359815519a859c Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 14:51:06 +0100 Subject: [PATCH 01/10] ref: improve assert_output to handle output and stderr streams Refactor the `assert_output` function to improve clarity and maintainability. Introduce a new helper function `__assert_stream` to handle different stream types (output and stderr). --- src/assert_output.bash | 46 ++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/assert_output.bash b/src/assert_output.bash index 82d0a0c..265f63d 100644 --- a/src/assert_output.bash +++ b/src/assert_output.bash @@ -122,11 +122,26 @@ # -- # ``` assert_output() { + __assert_stream "$@" +} + +__assert_stream() { + local -r caller=${FUNCNAME[1]} + local -r stream_type=${caller/assert_/} local -i is_mode_partial=0 local -i is_mode_regexp=0 local -i is_mode_nonempty=0 local -i use_stdin=0 - : "${output?}" + + if [[ ${stream_type} == "output" ]]; then + : "${output?}" + elif [[ ${stream_type} == "stderr" ]]; then + : "${stderr?}" + else + # Not reachable: should be either output or stderr + : + fi + local -r stream="${!stream_type}" # Handle options. if (( $# == 0 )); then @@ -145,7 +160,7 @@ assert_output() { if (( is_mode_partial )) && (( is_mode_regexp )); then echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: assert_output' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -160,37 +175,38 @@ assert_output() { # Matching. if (( is_mode_nonempty )); then - if [ -z "$output" ]; then - echo 'expected non-empty output, but output was empty' \ - | batslib_decorate 'no output' \ + if [ -z "$stream" ]; then + echo "expected non-empty $stream_type, but $stream_type was empty" \ + | batslib_decorate "no $stream_type" \ | fail fi elif (( is_mode_regexp )); then + # shellcheck disable=2319 if [[ '' =~ $expected ]] || (( $? == 2 )); then echo "Invalid extended regular expression: \`$expected'" \ - | batslib_decorate 'ERROR: assert_output' \ + | batslib_decorate "ERROR: ${caller}" \ | fail - elif ! [[ $output =~ $expected ]]; then + elif ! [[ $stream =~ $expected ]]; then batslib_print_kv_single_or_multi 6 \ 'regexp' "$expected" \ - 'output' "$output" \ - | batslib_decorate 'regular expression does not match output' \ + "$stream_type" "$stream" \ + | batslib_decorate "regular expression does not match $stream_type" \ | fail fi elif (( is_mode_partial )); then - if [[ $output != *"$expected"* ]]; then + if [[ $stream != *"$expected"* ]]; then batslib_print_kv_single_or_multi 9 \ 'substring' "$expected" \ - 'output' "$output" \ - | batslib_decorate 'output does not contain substring' \ + "$stream_type" "$stream" \ + | batslib_decorate "$stream_type does not contain substring" \ | fail fi else - if [[ $output != "$expected" ]]; then + if [[ $stream != "$expected" ]]; then batslib_print_kv_single_or_multi 8 \ 'expected' "$expected" \ - 'actual' "$output" \ - | batslib_decorate 'output differs' \ + 'actual' "$stream" \ + | batslib_decorate "$stream_type differs" \ | fail fi fi From 6e238d745885924c584fa58c0f23cd65908b32e4 Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 13:32:12 +0100 Subject: [PATCH 02/10] feat: add assert_stderr Introduce the `assert_stderr` function to validate that the standard error output matches the expected value. --- README.md | 111 ++++++++++++++- src/assert_output.bash | 32 +++++ test/assert_stderr.bats | 298 ++++++++++++++++++++++++++++++++++++++++ test/test_helper.bash | 1 + 4 files changed, 441 insertions(+), 1 deletion(-) create mode 100755 test/assert_stderr.bats diff --git a/README.md b/README.md index 6f1f80e..8d0e633 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ This project provides the following functions: - [assert_output](#assert_output) / [refute_output](#refute_output) Assert output does (or does not) contain given content. - [assert_line](#assert_line) / [refute_line](#refute_line) Assert a specific line of output does (or does not) contain given content. - [assert_regex](#assert_regex) / [refute_regex](#refute_regex) Assert a parameter does (or does not) match given pattern. + - [assert_stderr](#assert_stderr) Assert stderr does contain given content. These commands are described in more detail below. @@ -750,7 +751,7 @@ Fail if the value (first parameter) matches the pattern (second parameter). On failure, the value, the pattern and the match are displayed. -``` +```bash @test 'refute_regex()' { refute_regex 'WhatsApp' 'What.' } @@ -777,6 +778,114 @@ For description of the matching behavior, refer to the documentation of the > Thus, it's good practice to avoid using `BASH_REMATCH` in conjunction with `refute_regex()`. > The valuable information the array contains is the matching part of the value which is printed in the failing test log, as mentioned above._ +### `assert_stderr` + +> _**Note**: +> `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. +> If not, `$stderr` will be empty, causing `assert_stderr` to always fail. + +Similarly to `assert_output`, this function verifies that a command or function produces the expected stderr. +The stderr matching can be literal (the default), partial or by regular expression. +The expected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. + +#### Literal matching + +By default, literal matching is performed. +The assertion fails if `$stderr` does not equal the expected stderr. + + ```bash + echo_err() { + echo "$@" >&2 + } + + @test 'assert_stderr()' { + run --separate-stderr echo_err 'have' + assert_stderr 'want' + } + + @test 'assert_stderr() with pipe' { + run --separate-stderr echo_err 'hello' + echo_err 'hello' | assert_stderr - + } + + @test 'assert_stderr() with herestring' { + run --separate-stderr echo_err 'hello' + assert_stderr - <<< hello + } + ``` + +On failure, the expected and actual stderr are displayed. + + ``` + -- stderr differs -- + expected : want + actual : have + -- + ``` + +#### Existence + +To assert that any stderr exists at all, omit the `expected` argument. + + ```bash + @test 'assert_stderr()' { + run --separate-stderr echo_err 'have' + assert_stderr + } + ``` + +On failure, an error message is displayed. + + ``` + -- no stderr -- + expected non-empty stderr, but stderr was empty + -- + ``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for short). +When used, the assertion fails if the expected _substring_ is not found in `$stderr`. + + ```bash + @test 'assert_stderr() partial matching' { + run --separate-stderr echo_err 'ERROR: no such file or directory' + assert_stderr --partial 'SUCCESS' + } + ``` + +On failure, the substring and the stderr are displayed. + + ``` + -- stderr does not contain substring -- + substring : SUCCESS + stderr : ERROR: no such file or directory + -- + ``` + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +When used, the assertion fails if the *extended regular expression* does not match `$stderr`. + +*Note: The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire stderr; not individual lines.* + + ```bash + @test 'assert_stderr() regular expression matching' { + run --separate-stderr echo_err 'Foobar 0.1.0' + assert_stderr --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' + } + ``` + +On failure, the regular expression and the stderr are displayed. + + ``` + -- regular expression does not match stderr -- + regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ + stderr : Foobar 0.1.0 + -- + ``` + [bats]: https://github.com/bats-core/bats-core diff --git a/src/assert_output.bash b/src/assert_output.bash index 265f63d..ec86531 100644 --- a/src/assert_output.bash +++ b/src/assert_output.bash @@ -125,6 +125,38 @@ assert_output() { __assert_stream "$@" } +# assert_stderr +# ============= +# +# Summary: Fail if `$stderr' does not match the expected stderr. +# +# Usage: assert_stderr [-p | -e] [- | [--] ] +# +# Options: +# -p, --partial Match if `expected` is a substring of `$stderr` +# -e, --regexp Treat `expected` as an extended regular expression +# -, --stdin Read `expected` value from STDIN +# The expected value, substring or regular expression +# +# IO: +# STDIN - [=$1] expected stderr +# STDERR - details, on failure +# error message, on error +# Globals: +# stderr +# Returns: +# 0 - if stderr matches the expected value/partial/regexp +# 1 - otherwise +# +# Similarly to `assert_output`, this function verifies that a command or function produces the expected stderr. +# (It is the logical complement of `refute_stderr`.) +# The stderr matching can be literal (the default), partial or by regular expression. +# The expected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. +# +assert_stderr() { + __assert_stream "$@" +} + __assert_stream() { local -r caller=${FUNCNAME[1]} local -r stream_type=${caller/assert_/} diff --git a/test/assert_stderr.bats b/test/assert_stderr.bats new file mode 100755 index 0000000..ebedde8 --- /dev/null +++ b/test/assert_stderr.bats @@ -0,0 +1,298 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + bats_require_minimum_version 1.5.0 +} + +echo_err() { + echo "$@" >&2 +} + +printf_err() { + # shellcheck disable=2059 + printf "$@" >&2 +} + +# +# Literal matching +# + +# Correctness +@test "assert_stderr() : returns 0 if equals \`\$stderr'" { + run --separate-stderr echo_err 'a' + run assert_stderr 'a' + assert_test_pass +} + +@test "assert_stderr() : returns 1 and displays details if does not equal \`\$stderr'" { + run --separate-stderr echo_err 'b' + run assert_stderr 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr differs -- +expected : a +actual : b +-- +ERR_MSG +} + +@test 'assert_stderr(): succeeds if stderr is non-empty' { + run --separate-stderr echo_err 'a' + run assert_stderr + + assert_test_pass +} + +@test 'assert_stderr(): fails if stderr is empty' { + run --separate-stderr echo_err '' + run assert_stderr + + assert_test_fail <<'ERR_MSG' + +-- no stderr -- +expected non-empty stderr, but stderr was empty +-- +ERR_MSG +} + +@test 'assert_stderr() - : reads from STDIN' { + run --separate-stderr echo_err 'a' + run assert_stderr - < from STDIN' { + run --separate-stderr echo_err 'a' + run assert_stderr --stdin <: displays details in multi-line format if \`\$stderr' is longer than one line" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr differs -- +expected (1 lines): + a +actual (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +@test 'assert_stderr() : displays details in multi-line format if is longer than one line' { + run --separate-stderr echo_err 'b' + run assert_stderr $'a 0\na 1' + + assert_test_fail <<'ERR_MSG' + +-- stderr differs -- +expected (2 lines): + a 0 + a 1 +actual (1 lines): + b +-- +ERR_MSG +} + +# Options +@test 'assert_stderr() : performs literal matching by default' { + run --separate-stderr echo_err 'a' + run assert_stderr '*' + + assert_test_fail <<'ERR_MSG' + +-- stderr differs -- +expected : * +actual : a +-- +ERR_MSG +} + + +# +# Partial matching: `-p' and `--partial' +# + +@test 'assert_stderr() -p : enables partial matching' { + run --separate-stderr echo_err 'abc' + run assert_stderr -p 'b' + assert_test_pass +} + +@test 'assert_stderr() --partial : enables partial matching' { + run --separate-stderr echo_err 'abc' + run assert_stderr --partial 'b' + assert_test_pass +} + +# Correctness +@test "assert_stderr() --partial : returns 0 if is a substring in \`\$stderr'" { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr --partial 'b' + assert_test_pass +} + +@test "assert_stderr() --partial : returns 1 and displays details if is not a substring in \`\$stderr'" { + run --separate-stderr echo_err 'b' + run assert_stderr --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr does not contain substring -- +substring : a +stderr : b +-- +ERR_MSG +} + +# stderr formatting +@test "assert_stderr() --partial : displays details in multi-line format if \`\$stderr' is longer than one line" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr does not contain substring -- +substring (1 lines): + a +stderr (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +@test 'assert_stderr() --partial : displays details in multi-line format if is longer than one line' { + run --separate-stderr echo_err 'b' + run assert_stderr --partial $'a 0\na 1' + + assert_test_fail <<'ERR_MSG' + +-- stderr does not contain substring -- +substring (2 lines): + a 0 + a 1 +stderr (1 lines): + b +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +@test 'assert_stderr() -e : enables regular expression matching' { + run --separate-stderr echo_err 'abc' + run assert_stderr -e '^a' + assert_test_pass +} + +@test 'assert_stderr() --regexp : enables regular expression matching' { + run --separate-stderr echo_err 'abc' + run assert_stderr --regexp '^a' + assert_test_pass +} + +# Correctness +@test "assert_stderr() --regexp : returns 0 if matches \`\$stderr'" { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr --regexp '.*b.*' + assert_test_pass +} + +@test "assert_stderr() --regexp : returns 1 and displays details if does not match \`\$stderr'" { + run --separate-stderr echo_err 'b' + run assert_stderr --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match stderr -- +regexp : .*a.* +stderr : b +-- +ERR_MSG +} + +# stderr formatting +@test "assert_stderr() --regexp : displays details in multi-line format if \`\$stderr' is longer than one line" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match stderr -- +regexp (1 lines): + .*a.* +stderr (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +@test 'assert_stderr() --regexp : displays details in multi-line format if is longer than one line' { + run --separate-stderr echo_err 'b' + run assert_stderr --regexp $'.*a\nb.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match stderr -- +regexp (2 lines): + .*a + b.* +stderr (1 lines): + b +-- +ERR_MSG +} + +# Error handling +@test 'assert_stderr() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run assert_stderr --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_stderr -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + + +# +# Common +# + +@test "assert_stderr(): \`--partial' and \`--regexp' are mutually exclusive" { + run assert_stderr --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_stderr -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test "assert_stderr(): \`--' stops parsing options" { + run --separate-stderr echo_err '-p' + run assert_stderr -- '-p' + assert_test_pass +} diff --git a/test/test_helper.bash b/test/test_helper.bash index 8d476a8..c8fbb10 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -11,6 +11,7 @@ set -u : "${status:=}" : "${lines:=}" : "${output:=}" +: "${stderr:=}" assert_test_pass() { test "$status" -eq 0 From 96ee19966d4ead84f18ba723eeb80f4ca7c48e5c Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 16:13:05 +0100 Subject: [PATCH 03/10] ref: improve refute_output to handle output and stderr streams Refactor the `refute_output` function to improve stream handling by introducing a new `__refute_stream` function. This change allows for more flexible handling of both `output` and `stderr` streams. This enhances the maintainability and usability of the code. --- src/refute_output.bash | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/refute_output.bash b/src/refute_output.bash index 92d7e8f..5452b24 100644 --- a/src/refute_output.bash +++ b/src/refute_output.bash @@ -121,11 +121,26 @@ # -- # ``` refute_output() { + __refute_stream "$@" +} + +__refute_stream() { + local -r caller=${FUNCNAME[1]} + local -r stream_type=${caller/refute_/} local -i is_mode_partial=0 local -i is_mode_regexp=0 local -i is_mode_empty=0 local -i use_stdin=0 - : "${output?}" + + if [[ ${stream_type} == "output" ]]; then + : "${output?}" + elif [[ ${stream_type} == "stderr" ]]; then + : "${stderr?}" + else + # Not reachable: should be either output or stderr + : + fi + local -r stream="${!stream_type}" # Handle options. if (( $# == 0 )); then @@ -144,7 +159,7 @@ refute_output() { if (( is_mode_partial )) && (( is_mode_regexp )); then echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: refute_output' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -159,40 +174,40 @@ refute_output() { if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then echo "Invalid extended regular expression: \`$unexpected'" \ - | batslib_decorate 'ERROR: refute_output' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi # Matching. if (( is_mode_empty )); then - if [ -n "$output" ]; then + if [ -n "${stream}" ]; then batslib_print_kv_single_or_multi 6 \ - 'output' "$output" \ - | batslib_decorate 'output non-empty, but expected no output' \ + "${stream_type}" "${stream}" \ + | batslib_decorate "${stream_type} non-empty, but expected no ${stream_type}" \ | fail fi elif (( is_mode_regexp )); then - if [[ $output =~ $unexpected ]]; then + if [[ ${stream} =~ $unexpected ]]; then batslib_print_kv_single_or_multi 6 \ 'regexp' "$unexpected" \ - 'output' "$output" \ - | batslib_decorate 'regular expression should not match output' \ + "${stream_type}" "${stream}" \ + | batslib_decorate "regular expression should not match ${stream_type}" \ | fail fi elif (( is_mode_partial )); then - if [[ $output == *"$unexpected"* ]]; then + if [[ ${stream} == *"$unexpected"* ]]; then batslib_print_kv_single_or_multi 9 \ 'substring' "$unexpected" \ - 'output' "$output" \ - | batslib_decorate 'output should not contain substring' \ + "${stream_type}" "${stream}" \ + | batslib_decorate "${stream_type} should not contain substring" \ | fail fi else - if [[ $output == "$unexpected" ]]; then + if [[ ${stream} == "$unexpected" ]]; then batslib_print_kv_single_or_multi 6 \ - 'output' "$output" \ - | batslib_decorate 'output equals, but it was expected to differ' \ + "${stream_type}" "${stream}" \ + | batslib_decorate "${stream_type} equals, but it was expected to differ" \ | fail fi fi From ae556de11907b6331616c3316dc2d860d67a773b Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 13:35:38 +0100 Subject: [PATCH 04/10] feat: add refute_stderr Introduce the `refute_stderr` function to allow verification that a command or function does not produce unexpected stderr. This function complements `assert_stderr` and supports matching via literal, partial, or regular expression. Update documentation to reflect this addition and clarify usage of `assert_stderr` with `--separate-stderr` for proper stderr handling. --- README.md | 13 ++- src/refute_output.bash | 32 ++++++ test/refute_stderr.bats | 242 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100755 test/refute_stderr.bats diff --git a/README.md b/README.md index 8d0e633..276ae66 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This project provides the following functions: - [assert_output](#assert_output) / [refute_output](#refute_output) Assert output does (or does not) contain given content. - [assert_line](#assert_line) / [refute_line](#refute_line) Assert a specific line of output does (or does not) contain given content. - [assert_regex](#assert_regex) / [refute_regex](#refute_regex) Assert a parameter does (or does not) match given pattern. - - [assert_stderr](#assert_stderr) Assert stderr does contain given content. + - [assert_stderr](#assert_stderr) / [refute_stderr](#refute_stderr) Assert stderr does (or does not) contain given content. These commands are described in more detail below. @@ -886,6 +886,17 @@ On failure, the regular expression and the stderr are displayed. -- ``` +### `refute_stderr` + +> _**Note**: +> `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. +> If not, `$stderr` will be empty, causing `refute_stderr` to always pass. + +Similar to `refute_output`, this function verifies that a command or function does not produce the unexpected stderr. +(It is the logical complement of `assert_stderr`.) +The stderr matching can be literal (the default), partial or by regular expression. +The unexpected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. + [bats]: https://github.com/bats-core/bats-core diff --git a/src/refute_output.bash b/src/refute_output.bash index 5452b24..d656515 100644 --- a/src/refute_output.bash +++ b/src/refute_output.bash @@ -124,6 +124,38 @@ refute_output() { __refute_stream "$@" } +# refute_stderr +# ============= +# +# Summary: Fail if `$stderr' matches the unexpected output. +# +# Usage: refute_stderr [-p | -e] [- | [--] ] +# +# Options: +# -p, --partial Match if `unexpected` is a substring of `$stderr` +# -e, --regexp Treat `unexpected` as an extended regular expression +# -, --stdin Read `unexpected` value from STDIN +# The unexpected value, substring, or regular expression +# +# IO: +# STDIN - [=$1] unexpected stderr +# STDERR - details, on failure +# error message, on error +# Globals: +# stderr +# Returns: +# 0 - if stderr matches the unexpected value/partial/regexp +# 1 - otherwise +# +# Similar to `refute_output`, this function verifies that a command or function does not produce the unexpected stderr. +# (It is the logical complement of `assert_stderr`.) +# The stderr matching can be literal (the default), partial or by regular expression. +# The unexpected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. +# +refute_stderr() { + __refute_stream "$@" +} + __refute_stream() { local -r caller=${FUNCNAME[1]} local -r stream_type=${caller/refute_/} diff --git a/test/refute_stderr.bats b/test/refute_stderr.bats new file mode 100755 index 0000000..4122179 --- /dev/null +++ b/test/refute_stderr.bats @@ -0,0 +1,242 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + bats_require_minimum_version 1.5.0 +} + +echo_err() { + echo "$@" >&2 +} + +printf_err() { + # shellcheck disable=2059 + printf "$@" >&2 +} + +# +# Literal matching +# + +# Correctness +@test "refute_stderr() : returns 0 if does not equal \`\$stderr'" { + run --separate-stderr echo_err 'b' + run refute_stderr 'a' + assert_test_pass +} + +@test "refute_stderr() : returns 1 and displays details if equals \`\$stderr'" { + run --separate-stderr echo_err 'a' + run refute_stderr 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr equals, but it was expected to differ -- +stderr : a +-- +ERR_MSG +} + +@test 'refute_stderr(): succeeds if stderr is empty' { + run --separate-stderr echo_err '' + run refute_stderr + + assert_test_pass +} + +@test 'refute_stderr(): fails if stderr is non-empty' { + run --separate-stderr echo_err 'a' + run refute_stderr + + assert_test_fail <<'ERR_MSG' + +-- stderr non-empty, but expected no stderr -- +stderr : a +-- +ERR_MSG +} + +@test 'refute_stderr() - : reads from STDIN' { + run --separate-stderr echo_err '-' + run refute_stderr - < from STDIN' { + run --separate-stderr echo_err '--stdin' + run refute_stderr --stdin <: displays details in multi-line format if necessary' { + run --separate-stderr printf_err 'a 0\na 1' + run refute_stderr $'a 0\na 1' + + assert_test_fail <<'ERR_MSG' + +-- stderr equals, but it was expected to differ -- +stderr (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + +# Options +@test 'refute_stderr() : performs literal matching by default' { + run --separate-stderr echo_err 'a' + run refute_stderr '*' + assert_test_pass +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'refute_stderr() -p : enables partial matching' { + run --separate-stderr echo_err 'abc' + run refute_stderr -p 'd' + assert_test_pass +} + +@test 'refute_stderr() --partial : enables partial matching' { + run --separate-stderr echo_err 'abc' + run refute_stderr --partial 'd' + assert_test_pass +} + +# Correctness +@test "refute_stderr() --partial : returns 0 if is not a substring in \`\$stderr'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr --partial 'd' + assert_test_pass +} + +@test "refute_stderr() --partial : returns 1 and displays details if is a substring in \`\$stderr'" { + run --separate-stderr echo_err 'a' + run refute_stderr --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr should not contain substring -- +substring : a +stderr : a +-- +ERR_MSG +} + +# Output formatting +@test 'refute_stderr() --partial : displays details in multi-line format if necessary' { + run --separate-stderr printf_err 'a 0\na 1' + run refute_stderr --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr should not contain substring -- +substring (1 lines): + a +stderr (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'refute_stderr() -e : enables regular expression matching' { + run --separate-stderr echo_err 'abc' + run refute_stderr -e '^d' + assert_test_pass +} + +@test 'refute_stderr() --regexp : enables regular expression matching' { + run --separate-stderr echo_err 'abc' + run refute_stderr --regexp '^d' + assert_test_pass +} + +# Correctness +@test "refute_stderr() --regexp : returns 0 if does not match \`\$stderr'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr --regexp '.*d.*' + assert_test_pass +} + +@test "refute_stderr() --regexp : returns 1 and displays details if matches \`\$stderr'" { + run --separate-stderr echo_err 'a' + run refute_stderr --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression should not match stderr -- +regexp : .*a.* +stderr : a +-- +ERR_MSG +} + +# Output formatting +@test 'refute_stderr() --regexp : displays details in multi-line format if necessary' { + run --separate-stderr printf_err 'a 0\na 1' + run refute_stderr --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression should not match stderr -- +regexp (1 lines): + .*a.* +stderr (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + +# Error handling +@test 'refute_stderr() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run refute_stderr --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_stderr -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + + +# +# Common +# + +@test "refute_stderr(): \`--partial' and \`--regexp' are mutually exclusive" { + run refute_stderr --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_stderr -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test "refute_stderr(): \`--' stops parsing options" { + run echo_err '--' + run refute_stderr -- '-p' + assert_test_pass +} From a214bc9f553f0fd74784a0edf9a23153f807e668 Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 16:26:48 +0100 Subject: [PATCH 05/10] ref: improve assert_line to handle output and stderr streams Refactor the `assert_line` function to improve clarity and maintainability. Introduce a new helper function `__assert_stream_line` to handle different stream types (output and stderr). --- src/assert_line.bash | 63 +++++++++++++++++++++++++++---------------- test/test_helper.bash | 1 + 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/assert_line.bash b/src/assert_line.bash index ee05bb8..32b24f3 100644 --- a/src/assert_line.bash +++ b/src/assert_line.bash @@ -128,10 +128,27 @@ # ``` # FIXME(ztombol): Display `${lines[@]}' instead of `$output'! assert_line() { + __assert_line "$@" +} + +__assert_line() { + local -r caller=${FUNCNAME[1]} local -i is_match_line=0 local -i is_mode_partial=0 local -i is_mode_regexp=0 - : "${lines?}" + + if [[ "${caller}" == "assert_line" ]]; then + : "${lines?}" + local -ar stream_lines=("${lines[@]}") + local -r stream_type=output + elif [[ "${caller}" == "assert_stderr_line" ]]; then + : "${stderr_lines?}" + local -ar stream_lines=("${stderr_lines[@]}") + local -r stream_type=stderr + else + # Coding error: unknown caller + : + fi # Handle options. while (( $# > 0 )); do @@ -139,7 +156,7 @@ assert_line() { -n|--index) if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then echo "\`--index' requires an integer argument: \`$2'" \ - | batslib_decorate 'ERROR: assert_line' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -156,7 +173,7 @@ assert_line() { if (( is_mode_partial )) && (( is_mode_regexp )); then echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: assert_line' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -166,7 +183,7 @@ assert_line() { if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then echo "Invalid extended regular expression: \`$expected'" \ - | batslib_decorate 'ERROR: assert_line' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -175,73 +192,73 @@ assert_line() { if (( is_match_line )); then # Specific line. if (( is_mode_regexp )); then - if ! [[ ${lines[$idx]} =~ $expected ]]; then + if ! [[ ${stream_lines[$idx]} =~ $expected ]]; then batslib_print_kv_single 6 \ 'index' "$idx" \ 'regexp' "$expected" \ - 'line' "${lines[$idx]}" \ + 'line' "${stream_lines[$idx]}" \ | batslib_decorate 'regular expression does not match line' \ | fail fi elif (( is_mode_partial )); then - if [[ ${lines[$idx]} != *"$expected"* ]]; then + if [[ ${stream_lines[$idx]} != *"$expected"* ]]; then batslib_print_kv_single 9 \ 'index' "$idx" \ 'substring' "$expected" \ - 'line' "${lines[$idx]}" \ + 'line' "${stream_lines[$idx]}" \ | batslib_decorate 'line does not contain substring' \ | fail fi else - if [[ ${lines[$idx]} != "$expected" ]]; then + if [[ ${stream_lines[$idx]} != "$expected" ]]; then batslib_print_kv_single 8 \ 'index' "$idx" \ 'expected' "$expected" \ - 'actual' "${lines[$idx]}" \ + 'actual' "${stream_lines[$idx]}" \ | batslib_decorate 'line differs' \ | fail fi fi else - # Contained in output. + # Contained in output/error stream. if (( is_mode_regexp )); then local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} =~ $expected ]] && return 0 + for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do + [[ ${stream_lines[$idx]} =~ $expected ]] && return 0 done { local -ar single=( 'regexp' "$expected" ) - local -ar may_be_multi=( 'output' "$output" ) + local -ar may_be_multi=( "${stream_type}" "${!stream_type}" ) local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" batslib_print_kv_single "$width" "${single[@]}" batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" } \ - | batslib_decorate 'no output line matches regular expression' \ + | batslib_decorate "no ${stream_type} line matches regular expression" \ | fail elif (( is_mode_partial )); then local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} == *"$expected"* ]] && return 0 + for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do + [[ ${stream_lines[$idx]} == *"$expected"* ]] && return 0 done { local -ar single=( 'substring' "$expected" ) - local -ar may_be_multi=( 'output' "$output" ) + local -ar may_be_multi=( "${stream_type}" "${!stream_type}" ) local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" batslib_print_kv_single "$width" "${single[@]}" batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" } \ - | batslib_decorate 'no output line contains substring' \ + | batslib_decorate "no ${stream_type} line contains substring" \ | fail else local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} == "$expected" ]] && return 0 + for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do + [[ ${stream_lines[$idx]} == "$expected" ]] && return 0 done { local -ar single=( 'line' "$expected" ) - local -ar may_be_multi=( 'output' "$output" ) + local -ar may_be_multi=( "${stream_type}" "${!stream_type}" ) local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" batslib_print_kv_single "$width" "${single[@]}" batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" } \ - | batslib_decorate 'output does not contain line' \ + | batslib_decorate "${stream_type} does not contain line" \ | fail fi fi diff --git a/test/test_helper.bash b/test/test_helper.bash index c8fbb10..af1ee0a 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -12,6 +12,7 @@ set -u : "${lines:=}" : "${output:=}" : "${stderr:=}" +: "${stderr_lines:=}" assert_test_pass() { test "$status" -eq 0 From b80a77905e2da69feffc607cbe34a7d5b9f0631c Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 13:48:54 +0100 Subject: [PATCH 06/10] feat: add assert_stderr_line Add a new assert_stderr_line function for checking expected lines in the stderr output. This improves the clarity and functionality of the assertion process. --- README.md | 110 +++++++++++ src/assert_line.bash | 32 +++ test/assert_stderr_line.bats | 364 +++++++++++++++++++++++++++++++++++ 3 files changed, 506 insertions(+) create mode 100755 test/assert_stderr_line.bats diff --git a/README.md b/README.md index 276ae66..6d9998e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ This project provides the following functions: - [assert_line](#assert_line) / [refute_line](#refute_line) Assert a specific line of output does (or does not) contain given content. - [assert_regex](#assert_regex) / [refute_regex](#refute_regex) Assert a parameter does (or does not) match given pattern. - [assert_stderr](#assert_stderr) / [refute_stderr](#refute_stderr) Assert stderr does (or does not) contain given content. + - [assert_stderr_line](#assert_stderr_line) Assert a specific line of stderr does contain given content. These commands are described in more detail below. @@ -897,6 +898,115 @@ Similar to `refute_output`, this function verifies that a command or function do The stderr matching can be literal (the default), partial or by regular expression. The unexpected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. +### `assert_stderr_line` + +> _**Note**: +> `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. +> If not, `$stderr` will be empty, causing `assert_stderr_line` to always fail. + +Similarly to `assert_stderr`, this function verifies that a command or function produces the expected stderr. +It checks that the expected line appears in the stderr (default) or at a specific line number. +Matching can be literal (default), partial or regular expression. +This function is the logical complement of `refute_stderr_line`. + +#### Looking for a line in the stderr + +By default, the entire stderr is searched for the expected line. +The assertion fails if the expected line is not found in `${stderr_lines[@]}`. + + ```bash + echo_err() { + echo "$@" >&2 + } + + @test 'assert_stderr_line() looking for line' { + run --separate-stderr echo_err $'have-0\nhave-1\nhave-2' + assert_stderr_line 'want' + } + ``` + +On failure, the expected line and the stderr are displayed. + + ``` + -- stderr does not contain line -- + line : want + stderr (3 lines): + have-0 + have-1 + have-2 + -- + ``` + +#### Matching a specific line + +When the `--index ` option is used (`-n ` for short), the expected line is matched only against the line identified by the given index. +The assertion fails if the expected line does not equal `${stderr_lines[]}`. + + ```bash + @test 'assert_stderr_line() specific line' { + run --separate-stderr echo_err $'have-0\nhave-1\nhave-2' + assert_stderr_line --index 1 'want-1' + } + ``` + +On failure, the index and the compared stderr_lines are displayed. + + ``` + -- line differs -- + index : 1 + expected : want-1 + actual : have-1 + -- + ``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for short). +When used, a match fails if the expected *substring* is not found in the matched line. + + ```bash + @test 'assert_stderr_line() partial matching' { + run --separate-stderr echo_err $'have 1\nhave 2\nhave 3' + assert_stderr_line --partial 'want' + } + ``` + +On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line. + + ``` + -- no stderr line contains substring -- + substring : want + stderr (3 lines): + have 1 + have 2 + have 3 + -- + ``` + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +When used, a match fails if the *extended regular expression* does not match the line being tested. + +*Note: As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.* + + ```bash + @test 'assert_stderr_line() regular expression matching' { + run --separate-stderr echo_err $'have-0\nhave-1\nhave-2' + assert_stderr_line --index 1 --regexp '^want-[0-9]$' + } + ``` + +On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line. + + ``` + -- regular expression does not match line -- + index : 1 + regexp : ^want-[0-9]$ + line : have-1 + -- + ``` + [bats]: https://github.com/bats-core/bats-core diff --git a/src/assert_line.bash b/src/assert_line.bash index 32b24f3..df1b81b 100644 --- a/src/assert_line.bash +++ b/src/assert_line.bash @@ -131,6 +131,38 @@ assert_line() { __assert_line "$@" } +# assert_stderr_line +# =========== +# +# Summary: Fail if the expected line is not found in the stderr (default) or at a specific line number. +# +# Usage: assert_stderr_line [-n index] [-p | -e] [--] +# +# Options: +# -n, --index Match the th line +# -p, --partial Match if `expected` is a substring of `$stderr` or line +# -e, --regexp Treat `expected` as an extended regular expression +# The expected line string, substring, or regular expression +# +# IO: +# STDERR - details, on failure +# error message, on error +# Globals: +# stderr +# stderr_lines +# Returns: +# 0 - if matching line found +# 1 - otherwise +# +# Similarly to `assert_stderr`, this function verifies that a command or function produces the expected stderr. +# (It is the logical complement of `refute_stderr_line`.) +# It checks that the expected line appears in the stderr (default) or at a specific line number. +# Matching can be literal (default), partial or regular expression. +# +assert_stderr_line() { + __assert_line "$@" +} + __assert_line() { local -r caller=${FUNCNAME[1]} local -i is_match_line=0 diff --git a/test/assert_stderr_line.bats b/test/assert_stderr_line.bats new file mode 100755 index 0000000..bb3a071 --- /dev/null +++ b/test/assert_stderr_line.bats @@ -0,0 +1,364 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + bats_require_minimum_version 1.5.0 +} + +echo_err() { + echo "$@" >&2 +} + +printf_err() { + # shellcheck disable=2059 + printf "$@" >&2 +} + + +############################################################################### +# Containing a line +############################################################################### + +# +# Literal matching +# + +# Correctness +@test "assert_stderr_line() : returns 0 if is a line in \`\${stderr_lines[@]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line 'b' + assert_test_pass +} + +@test "assert_stderr_line() : returns 1 and displays details if is not a line in \`\${stderr_lines[@]}'" { + run --separate-stderr echo_err 'b' + run assert_stderr_line 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr does not contain line -- +line : a +stderr : b +-- +ERR_MSG +} + +# stderr formatting +@test "assert_stderr_line() : displays \`\$stderr' in multi-line format if it is longer than one line" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr_line 'a' + + assert_test_fail <<'ERR_MSG' + +-- stderr does not contain line -- +line : a +stderr (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +# Options +@test 'assert_stderr_line() : performs literal matching by default' { + run --separate-stderr echo_err 'a' + run assert_stderr_line '*' + + assert_test_fail <<'ERR_MSG' + +-- stderr does not contain line -- +line : * +stderr : a +-- +ERR_MSG +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'assert_stderr_line() -p : enables partial matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line -p 'b' + assert_test_pass +} + +@test 'assert_stderr_line() --partial : enables partial matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --partial 'b' + assert_test_pass +} + +# Correctness +@test "assert_stderr_line() --partial : returns 0 if is a substring in any line in \`\${stderr_lines[@]}'" { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --partial 'b' + assert_test_pass +} + +@test "assert_stderr_line() --partial : returns 1 and displays details if is not a substring in any lines in \`\${stderr_lines[@]}'" { + run --separate-stderr echo_err 'b' + run assert_stderr_line --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- no stderr line contains substring -- +substring : a +stderr : b +-- +ERR_MSG +} + +# stderr formatting +@test "assert_stderr_line() --partial : displays \`\$stderr' in multi-line format if it is longer than one line" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr_line --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- no stderr line contains substring -- +substring : a +stderr (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'assert_stderr_line() -e : enables regular expression matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line -e '^.b' + assert_test_pass +} + +@test 'assert_stderr_line() --regexp : enables regular expression matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --regexp '^.b' + assert_test_pass +} + +# Correctness +@test "assert_stderr_line() --regexp : returns 0 if matches any line in \`\${stderr_lines[@]}'" { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --regexp '^.b' + assert_test_pass +} + +@test "assert_stderr_line() --regexp : returns 1 and displays details if does not match any lines in \`\${stderr_lines[@]}'" { + run --separate-stderr echo_err 'b' + run assert_stderr_line --regexp '^.a' + + assert_test_fail <<'ERR_MSG' + +-- no stderr line matches regular expression -- +regexp : ^.a +stderr : b +-- +ERR_MSG +} + +# stderr formatting +@test "assert_stderr_line() --regexp : displays \`\$stderr' in multi-line format if longer than one line" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr_line --regexp '^.a' + + assert_test_fail <<'ERR_MSG' + +-- no stderr line matches regular expression -- +regexp : ^.a +stderr (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + + +############################################################################### +# Matching single line: `-n' and `--index' +############################################################################### + +# Options +@test 'assert_stderr_line() -n : matches against the -th line only' { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line -n 1 'b' + assert_test_pass +} + +@test 'assert_stderr_line() --index : matches against the -th line only' { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line --index 1 'b' + assert_test_pass +} + +@test 'assert_stderr_line() --index : returns 1 and displays an error message if is not an integer' { + run assert_stderr_line --index 1a + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_stderr_line -- +`--index' requires an integer argument: `1a' +-- +ERR_MSG +} + + +# +# Literal matching +# + +# Correctness +@test "assert_stderr_line() --index : returns 0 if equals \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line --index 1 'b' + assert_test_pass +} + +@test "assert_stderr_line() --index : returns 1 and displays details if does not equal \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line --index 1 'a' + + assert_test_fail <<'ERR_MSG' + +-- line differs -- +index : 1 +expected : a +actual : b +-- +ERR_MSG +} + +# Options +@test 'assert_stderr_line() --index : performs literal matching by default' { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line --index 1 '*' + + assert_test_fail <<'ERR_MSG' + +-- line differs -- +index : 1 +expected : * +actual : b +-- +ERR_MSG +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'assert_stderr_line() --index -p : enables partial matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --index 1 -p 'b' + assert_test_pass +} + +@test 'assert_stderr_line() --index --partial : enables partial matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --index 1 --partial 'b' + assert_test_pass +} + +# Correctness +@test "assert_stderr_line() --index --partial : returns 0 if is a substring in \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --index 1 --partial 'b' + assert_test_pass +} + +@test "assert_stderr_line() --index --partial : returns 1 and displays details if is not a substring in \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'b 0\nb 1' + run assert_stderr_line --index 1 --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- line does not contain substring -- +index : 1 +substring : a +line : b 1 +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'assert_stderr_line() --index -e : enables regular expression matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --index 1 -e '^.b' + assert_test_pass +} + +@test 'assert_stderr_line() --index --regexp : enables regular expression matching' { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --index 1 --regexp '^.b' + assert_test_pass +} + +# Correctness +@test "assert_stderr_line() --index --regexp : returns 0 if matches \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\n_b_\nc' + run assert_stderr_line --index 1 --regexp '^.b' + assert_test_pass +} + +@test "assert_stderr_line() --index --regexp : returns 1 and displays details if does not match \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run assert_stderr_line --index 1 --regexp '^.a' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match line -- +index : 1 +regexp : ^.a +line : b +-- +ERR_MSG +} + + +############################################################################### +# Common +############################################################################### + +@test "assert_stderr_line(): \`--partial' and \`--regexp' are mutually exclusive" { + run assert_stderr_line --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_stderr_line -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test 'assert_stderr_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run assert_stderr_line --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_stderr_line -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + +@test "assert_stderr_line(): \`--' stops parsing options" { + run --separate-stderr printf_err 'a\n-p\nc' + run assert_stderr_line -- '-p' + assert_test_pass +} From 448b1bc8b4c1962804673ebf054f4b77043be30b Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sun, 16 Feb 2025 16:35:27 +0100 Subject: [PATCH 07/10] ref: improve refute_line to handle output and input streams Introduce a new helper function `__refute_stream_line` to streamline the logic and reduce code duplication. --- src/refute_line.bash | 65 ++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/refute_line.bash b/src/refute_line.bash index 689bea1..5c4c4e1 100644 --- a/src/refute_line.bash +++ b/src/refute_line.bash @@ -26,12 +26,6 @@ # It checks that the unexpected line does not appear in the output (default) or at a specific line number. # Matching can be literal (default), partial or regular expression. # -# *__Warning:__ -# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, -# causing line indices to change and preventing testing for empty lines.* -# -# [bats-93]: https://github.com/sstephenson/bats/pull/93 -# # ## Looking for a line in the output # # By default, the entire output is searched for the unexpected line. @@ -131,10 +125,27 @@ # ``` # FIXME(ztombol): Display `${lines[@]}' instead of `$output'! refute_line() { + __refute_stream_line "$@" +} + +__refute_stream_line() { + local -r caller=${FUNCNAME[1]} local -i is_match_line=0 local -i is_mode_partial=0 local -i is_mode_regexp=0 - : "${lines?}" + + if [[ "${caller}" == "refute_line" ]]; then + : "${lines?}" + local -ar stream_lines=("${lines[@]}") + local -r stream_type=output + elif [[ "${caller}" == "refute_stderr_line" ]]; then + : "${stderr_lines?}" + local -ar stream_lines=("${stderr_lines[@]}") + local -r stream_type=stderr + else + # Coding error: unknown caller + : + fi # Handle options. while (( $# > 0 )); do @@ -142,7 +153,7 @@ refute_line() { -n|--index) if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then echo "\`--index' requires an integer argument: \`$2'" \ - | batslib_decorate 'ERROR: refute_line' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -159,7 +170,7 @@ refute_line() { if (( is_mode_partial )) && (( is_mode_regexp )); then echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: refute_line' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -169,7 +180,7 @@ refute_line() { if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then echo "Invalid extended regular expression: \`$unexpected'" \ - | batslib_decorate 'ERROR: refute_line' \ + | batslib_decorate "ERROR: ${caller}" \ | fail return $? fi @@ -178,40 +189,40 @@ refute_line() { if (( is_match_line )); then # Specific line. if (( is_mode_regexp )); then - if [[ ${lines[$idx]} =~ $unexpected ]]; then + if [[ ${stream_lines[$idx]} =~ $unexpected ]]; then batslib_print_kv_single 6 \ 'index' "$idx" \ 'regexp' "$unexpected" \ - 'line' "${lines[$idx]}" \ + 'line' "${stream_lines[$idx]}" \ | batslib_decorate 'regular expression should not match line' \ | fail fi elif (( is_mode_partial )); then - if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + if [[ ${stream_lines[$idx]} == *"$unexpected"* ]]; then batslib_print_kv_single 9 \ 'index' "$idx" \ 'substring' "$unexpected" \ - 'line' "${lines[$idx]}" \ + 'line' "${stream_lines[$idx]}" \ | batslib_decorate 'line should not contain substring' \ | fail fi else - if [[ ${lines[$idx]} == "$unexpected" ]]; then + if [[ ${stream_lines[$idx]} == "$unexpected" ]]; then batslib_print_kv_single 5 \ 'index' "$idx" \ - 'line' "${lines[$idx]}" \ + 'line' "${stream_lines[$idx]}" \ | batslib_decorate 'line should differ' \ | fail fi fi else - # Line contained in output. + # Line contained in output/error stream. if (( is_mode_regexp )); then local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} =~ $unexpected ]]; then + for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do + if [[ ${stream_lines[$idx]} =~ $unexpected ]]; then { local -ar single=( 'regexp' "$unexpected" 'index' "$idx" ) - local -a may_be_multi=( 'output' "$output" ) + local -a may_be_multi=( "${stream_type}" "${!stream_type}" ) local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" batslib_print_kv_single "$width" "${single[@]}" if batslib_is_single_line "${may_be_multi[1]}"; then @@ -228,10 +239,10 @@ refute_line() { done elif (( is_mode_partial )); then local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do + if [[ ${stream_lines[$idx]} == *"$unexpected"* ]]; then { local -ar single=( 'substring' "$unexpected" 'index' "$idx" ) - local -a may_be_multi=( 'output' "$output" ) + local -a may_be_multi=( "${stream_type}" "${!stream_type}" ) local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" batslib_print_kv_single "$width" "${single[@]}" if batslib_is_single_line "${may_be_multi[1]}"; then @@ -248,10 +259,10 @@ refute_line() { done else local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} == "$unexpected" ]]; then + for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do + if [[ ${stream_lines[$idx]} == "$unexpected" ]]; then { local -ar single=( 'line' "$unexpected" 'index' "$idx" ) - local -a may_be_multi=( 'output' "$output" ) + local -a may_be_multi=( "${stream_type}" "${!stream_type}" ) local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" batslib_print_kv_single "$width" "${single[@]}" if batslib_is_single_line "${may_be_multi[1]}"; then @@ -261,7 +272,7 @@ refute_line() { batslib_print_kv_multi "${may_be_multi[@]}" fi } \ - | batslib_decorate 'line should not be in output' \ + | batslib_decorate "line should not be in ${stream_type}" \ | fail return $? fi From 8d919a12393fdf1b673ff9b351307c079e99f56f Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Sat, 15 Feb 2025 17:14:22 +0100 Subject: [PATCH 08/10] feat: add refute_stderr_line Refactor the `refute_line` function to handle both output and stderr streams uniformly. Introduce a new helper function `__refute_stream_line` to streamline the logic and reduce code duplication. Introduce the `refute_stderr_line` function to verify that an unexpected line does not appear in the stderr output. Update documentation to reflect usage and necessary conditions for correct operation, including the requirement to use `--separate-stderr` when running commands. --- README.md | 13 +- src/refute_line.bash | 32 ++++ test/refute_stderr_line.bats | 356 +++++++++++++++++++++++++++++++++++ 3 files changed, 400 insertions(+), 1 deletion(-) create mode 100755 test/refute_stderr_line.bats diff --git a/README.md b/README.md index 6d9998e..2c99433 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ This project provides the following functions: - [assert_line](#assert_line) / [refute_line](#refute_line) Assert a specific line of output does (or does not) contain given content. - [assert_regex](#assert_regex) / [refute_regex](#refute_regex) Assert a parameter does (or does not) match given pattern. - [assert_stderr](#assert_stderr) / [refute_stderr](#refute_stderr) Assert stderr does (or does not) contain given content. - - [assert_stderr_line](#assert_stderr_line) Assert a specific line of stderr does contain given content. + - [assert_stderr_line](#assert_stderr_line) / [refute_stderr_line](#refute_stderr_line) Assert a specific line of stderr does (or does not) contain given content. These commands are described in more detail below. @@ -1007,6 +1007,17 @@ On failure, the same details are displayed as for literal matching, except that -- ``` +### `refute_stderr_line` + +> _**Note**: +> `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. +> If not, `$stderr` will be empty, causing `refute_stderr_line` to always pass. + +Similarly to `refute_stderr`, this function helps to verify that a command or function produces the correct stderr. +It checks that the unexpected line does not appear in the stderr (default) or in a specific line of it. +Matching can be literal (default), partial or regular expression. +This function is the logical complement of `assert_stderr_line`. + [bats]: https://github.com/bats-core/bats-core diff --git a/src/refute_line.bash b/src/refute_line.bash index 5c4c4e1..c3d6afd 100644 --- a/src/refute_line.bash +++ b/src/refute_line.bash @@ -128,6 +128,38 @@ refute_line() { __refute_stream_line "$@" } +# refute_stderr_line +# ================== +# +# Summary: Fail if the unexpected line is found in the stderr (default) or at a specific line number. +# +# Usage: refute_stderr_line [-n index] [-p | -e] [--] +# +# Options: +# -n, --index Match the th line +# -p, --partial Match if `unexpected` is a substring of `$stderr` or line +# -e, --regexp Treat `unexpected` as an extended regular expression +# The unexpected line string, substring, or regular expression. +# +# IO: +# STDERR - details, on failure +# error message, on error +# Globals: +# stderr +# stderr_lines +# Returns: +# 0 - if match not found +# 1 - otherwise +# +# Similarly to `refute_stderr`, this function verifies that a command or function does not produce the unexpected stderr. +# (It is the logical complement of `assert_stderr_line`.) +# It checks that the unexpected line does not appear in the stderr (default) or at a specific line number. +# Matching can be literal (default), partial or regular expression. +# +refute_stderr_line() { + __refute_stream_line "$@" +} + __refute_stream_line() { local -r caller=${FUNCNAME[1]} local -i is_match_line=0 diff --git a/test/refute_stderr_line.bats b/test/refute_stderr_line.bats new file mode 100755 index 0000000..515a5f3 --- /dev/null +++ b/test/refute_stderr_line.bats @@ -0,0 +1,356 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + bats_require_minimum_version 1.5.0 +} + +echo_err() { + echo "$@" >&2 +} + +printf_err() { + # shellcheck disable=2059 + printf "$@" >&2 +} + +############################################################################### +# Containing a line +############################################################################### + +# +# Literal matching +# + +# Correctness +@test "refute_stderr_line() : returns 0 if is not a line in \`\${stderr_lines[@]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line 'd' + assert_test_pass +} + +@test "refute_stderr_line() : returns 1 and displays details if is not a line in \`\${stderr_lines[@]}'" { + run --separate-stderr echo_err 'a' + run refute_stderr_line 'a' + + assert_test_fail <<'ERR_MSG' + +-- line should not be in stderr -- +line : a +index : 0 +stderr : a +-- +ERR_MSG +} + +# Output formatting +@test "refute_stderr_line() : displays \`\$stderr' in multi-line format if it is longer than one line" { + run --separate-stderr printf_err 'a 0\na 1\na 2' + run refute_stderr_line 'a 1' + + assert_test_fail <<'ERR_MSG' + +-- line should not be in stderr -- +line : a 1 +index : 1 +stderr (3 lines): + a 0 +> a 1 + a 2 +-- +ERR_MSG +} + +# Options +@test 'refute_stderr_line() : performs literal matching by default' { + run --separate-stderr echo_err 'a' + run refute_stderr_line '*' + assert_test_pass +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'refute_stderr_line() -p : enables partial matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line -p 'd' + assert_test_pass +} + +@test 'refute_stderr_line() --partial : enables partial matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --partial 'd' + assert_test_pass +} + +# Correctness +@test "refute_stderr_line() --partial : returns 0 if is not a substring in any line in \`\${stderr_lines[@]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --partial 'd' + assert_test_pass +} + +@test "refute_stderr_line() --partial : returns 1 and displays details if is a substring in any line in \`\${stderr_lines[@]}'" { + run --separate-stderr echo_err 'a' + run refute_stderr_line --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- no line should contain substring -- +substring : a +index : 0 +stderr : a +-- +ERR_MSG +} + +# Output formatting +@test "refute_stderr_line() --partial : displays \`\$stderr' in multi-line format if it is longer than one line" { + run --separate-stderr printf_err 'a\nabc\nc' + run refute_stderr_line --partial 'b' + + assert_test_fail <<'ERR_MSG' + +-- no line should contain substring -- +substring : b +index : 1 +stderr (3 lines): + a +> abc + c +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'refute_stderr_line() -e : enables regular expression matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line -e '^.d' + assert_test_pass +} + +@test 'refute_stderr_line() --regexp : enables regular expression matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --regexp '^.d' + assert_test_pass +} + +# Correctness +@test "refute_stderr_line() --regexp : returns 0 if does not match any line in \`\${stderr_lines[@]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --regexp '.*d.*' + assert_test_pass +} + +@test "refute_stderr_line() --regexp : returns 1 and displays details if matches any lines in \`\${stderr_lines[@]}'" { + run --separate-stderr echo_err 'a' + run refute_stderr_line --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- no line should match the regular expression -- +regexp : .*a.* +index : 0 +stderr : a +-- +ERR_MSG +} + +# Output formatting +@test "refute_stderr_line() --regexp : displays \`\$stderr' in multi-line format if longer than one line" { + run --separate-stderr printf_err 'a\nabc\nc' + run refute_stderr_line --regexp '.*b.*' + + assert_test_fail <<'ERR_MSG' + +-- no line should match the regular expression -- +regexp : .*b.* +index : 1 +stderr (3 lines): + a +> abc + c +-- +ERR_MSG +} + + +############################################################################### +# Matching single line: `-n' and `--index' +############################################################################### + +# Options +@test 'refute_stderr_line() -n : matches against the -th line only' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line -n 1 'd' + assert_test_pass +} + +@test 'refute_stderr_line() --index : matches against the -th line only' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 'd' + assert_test_pass +} + +@test 'refute_stderr_line() --index : returns 1 and displays an error message if is not an integer' { + run refute_stderr_line --index 1a + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_stderr_line -- +`--index' requires an integer argument: `1a' +-- +ERR_MSG +} + + +# +# Literal matching +# + +# Correctness +@test "refute_stderr_line() --index : returns 0 if does not equal \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 'd' + assert_test_pass +} + +@test "refute_stderr_line() --index : returns 1 and displays details if equals \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 'b' + + assert_test_fail <<'ERR_MSG' + +-- line should differ -- +index : 1 +line : b +-- +ERR_MSG +} + +# Options +@test 'refute_stderr_line() --index : performs literal matching by default' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 '*' + assert_test_pass +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'refute_stderr_line() --index -p : enables partial matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 -p 'd' + assert_test_pass +} + +@test 'refute_stderr_line() --index --partial : enables partial matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 --partial 'd' + assert_test_pass +} + +# Correctness +@test "refute_stderr_line() --index --partial : returns 0 if is not a substring in \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nabc\nc' + run refute_stderr_line --index 1 --partial 'd' + assert_test_pass +} + +@test "refute_stderr_line() --index --partial : returns 1 and displays details if is a substring in \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nabc\nc' + run refute_stderr_line --index 1 --partial 'b' + + assert_test_fail <<'ERR_MSG' + +-- line should not contain substring -- +index : 1 +substring : b +line : abc +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'refute_stderr_line() --index -e : enables regular expression matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 -e '^.b' + assert_test_pass +} + +@test 'refute_stderr_line() --index --regexp : enables regular expression matching' { + run --separate-stderr printf_err 'a\nb\nc' + run refute_stderr_line --index 1 --regexp '^.b' + assert_test_pass +} + +# Correctness +@test "refute_stderr_line() --index --regexp : returns 0 if does not match \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nabc\nc' + run refute_stderr_line --index 1 --regexp '.*d.*' + assert_test_pass +} + +@test "refute_stderr_line() --index --regexp : returns 1 and displays details if matches \`\${stderr_lines[]}'" { + run --separate-stderr printf_err 'a\nabc\nc' + run refute_stderr_line --index 1 --regexp '.*b.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression should not match line -- +index : 1 +regexp : .*b.* +line : abc +-- +ERR_MSG +} + + +############################################################################### +# Common +############################################################################### + +@test "refute_stderr_line(): \`--partial' and \`--regexp' are mutually exclusive" { + run refute_stderr_line --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_stderr_line -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test 'refute_stderr_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run refute_stderr_line --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_stderr_line -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + +@test "refute_stderr_line(): \`--' stops parsing options" { + run --separate-stderr printf_err 'a\n--\nc' + run refute_stderr_line -- '-p' + assert_test_pass +} From d7dd6fbd9c4e543f2fa6307aec4f89493c1769cf Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Fri, 21 Mar 2025 21:36:36 +0100 Subject: [PATCH 09/10] test: improve tests for refute_stderr and refute_stderr_line --- test/refute_stderr.bats | 2 +- test/refute_stderr_line.bats | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/refute_stderr.bats b/test/refute_stderr.bats index 4122179..d1ec134 100755 --- a/test/refute_stderr.bats +++ b/test/refute_stderr.bats @@ -159,7 +159,7 @@ ERR_MSG # Options @test 'refute_stderr() -e : enables regular expression matching' { - run --separate-stderr echo_err 'abc' + run --separate-stderr echo_err 'abc^d' run refute_stderr -e '^d' assert_test_pass } diff --git a/test/refute_stderr_line.bats b/test/refute_stderr_line.bats index 515a5f3..ecb210b 100755 --- a/test/refute_stderr_line.bats +++ b/test/refute_stderr_line.bats @@ -291,7 +291,7 @@ ERR_MSG # Options @test 'refute_stderr_line() --index -e : enables regular expression matching' { run --separate-stderr printf_err 'a\nb\nc' - run refute_stderr_line --index 1 -e '^.b' + run refute_stderr_line --index 1 -e '[^.b]' assert_test_pass } From 03ca0ba9ac202420ba11dfafaf63088769914c9b Mon Sep 17 00:00:00 2001 From: Max Hofer Date: Fri, 21 Mar 2025 22:41:48 +0100 Subject: [PATCH 10/10] feat(tests): add error handling for unexpected function calls Add tests for `__refute_stream_line`, `__assert_stream`, and `__assert_line` to ensure they produce appropriate error messages when called directly. Update the implementations to handle unexpected calls by providing clear guidance on correct usage. This improves user experience and debugging by preventing silent failures and clarifying intended function usage. --- src/assert_line.bash | 8 ++++++-- src/assert_output.bash | 8 ++++++-- src/refute_line.bash | 8 ++++++-- test/assert_line.bats | 11 +++++++++++ test/assert_output.bats | 11 +++++++++++ test/refute_line.bats | 11 +++++++++++ 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/assert_line.bash b/src/assert_line.bash index df1b81b..bf9140d 100644 --- a/src/assert_line.bash +++ b/src/assert_line.bash @@ -178,8 +178,12 @@ __assert_line() { local -ar stream_lines=("${stderr_lines[@]}") local -r stream_type=stderr else - # Coding error: unknown caller - : + # Unknown caller + echo "Unexpected call to \`${FUNCNAME[0]}\` +Did you mean to call \`assert_line\` or \`assert_stderr_line\`?" \ + | batslib_decorate "ERROR: ${FUNCNAME[0]}" \ + | fail + return $? fi # Handle options. diff --git a/src/assert_output.bash b/src/assert_output.bash index ec86531..168d246 100644 --- a/src/assert_output.bash +++ b/src/assert_output.bash @@ -170,8 +170,12 @@ __assert_stream() { elif [[ ${stream_type} == "stderr" ]]; then : "${stderr?}" else - # Not reachable: should be either output or stderr - : + # Unknown caller + echo "Unexpected call to \`${FUNCNAME[0]}\` +Did you mean to call \`assert_output\` or \`assert_stderr\`?" | + batslib_decorate "ERROR: ${FUNCNAME[0]}" | + fail + return $? fi local -r stream="${!stream_type}" diff --git a/src/refute_line.bash b/src/refute_line.bash index c3d6afd..bb7337d 100644 --- a/src/refute_line.bash +++ b/src/refute_line.bash @@ -175,8 +175,12 @@ __refute_stream_line() { local -ar stream_lines=("${stderr_lines[@]}") local -r stream_type=stderr else - # Coding error: unknown caller - : + # Unknown caller + echo "Unexpected call to \`${FUNCNAME[0]}\` +Did you mean to call \`refute_line\` or \`refute_stderr_line\`?" | + batslib_decorate "ERROR: ${FUNCNAME[0]}" | + fail + return $? fi # Handle options. diff --git a/test/assert_line.bats b/test/assert_line.bats index 151f688..b69ed59 100755 --- a/test/assert_line.bats +++ b/test/assert_line.bats @@ -349,3 +349,14 @@ ERR_MSG run assert_line -- '-p' assert_test_pass } + +@test "__assert_line(): call to __assert_line shows error" { + run __assert_line + assert_test_fail <<'ERR_MSG' + +-- ERROR: __assert_line -- +Unexpected call to `__assert_line` +Did you mean to call `assert_line` or `assert_stderr_line`? +-- +ERR_MSG +} diff --git a/test/assert_output.bats b/test/assert_output.bats index e9609e0..6671203 100755 --- a/test/assert_output.bats +++ b/test/assert_output.bats @@ -283,3 +283,14 @@ ERR_MSG run assert_output -- '-p' assert_test_pass } + +@test "__assert_stream(): call to __assert_stream shows error" { + run __assert_stream + assert_test_fail <<'ERR_MSG' + +-- ERROR: __assert_stream -- +Unexpected call to `__assert_stream` +Did you mean to call `assert_output` or `assert_stderr`? +-- +ERR_MSG +} diff --git a/test/refute_line.bats b/test/refute_line.bats index fd6221c..e0ad762 100755 --- a/test/refute_line.bats +++ b/test/refute_line.bats @@ -342,3 +342,14 @@ ERR_MSG run refute_line -- '-p' assert_test_pass } + +@test "__refute_stream_line(): call to __refute_stream_line shows error" { + run __refute_stream_line + assert_test_fail <<'ERR_MSG' + +-- ERROR: __refute_stream_line -- +Unexpected call to `__refute_stream_line` +Did you mean to call `refute_line` or `refute_stderr_line`? +-- +ERR_MSG +}