diff --git a/README.md b/README.md index 6f1f80e..2c99433 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ 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) / [refute_stderr](#refute_stderr) Assert stderr does (or does not) 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. @@ -750,7 +752,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 +779,245 @@ 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 + -- + ``` + +### `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. + +### `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 + -- + ``` + +### `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/assert_line.bash b/src/assert_line.bash index ee05bb8..bf9140d 100644 --- a/src/assert_line.bash +++ b/src/assert_line.bash @@ -128,10 +128,63 @@ # ``` # FIXME(ztombol): Display `${lines[@]}' instead of `$output'! 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 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 + # 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. while (( $# > 0 )); do @@ -139,7 +192,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 +209,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 +219,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 +228,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/src/assert_output.bash b/src/assert_output.bash index 82d0a0c..168d246 100644 --- a/src/assert_output.bash +++ b/src/assert_output.bash @@ -122,11 +122,62 @@ # -- # ``` 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_/} 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 + # 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}" # Handle options. if (( $# == 0 )); then @@ -145,7 +196,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 +211,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 diff --git a/src/refute_line.bash b/src/refute_line.bash index 689bea1..bb7337d 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,63 @@ # ``` # FIXME(ztombol): Display `${lines[@]}' instead of `$output'! 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 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 + # 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. while (( $# > 0 )); do @@ -142,7 +189,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 +206,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 +216,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 +225,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 +275,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 +295,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 +308,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 diff --git a/src/refute_output.bash b/src/refute_output.bash index 92d7e8f..d656515 100644 --- a/src/refute_output.bash +++ b/src/refute_output.bash @@ -121,11 +121,58 @@ # -- # ``` 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_/} 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 +191,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 +206,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 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/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/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 +} 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 +} diff --git a/test/refute_stderr.bats b/test/refute_stderr.bats new file mode 100755 index 0000000..d1ec134 --- /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^d' + 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 +} diff --git a/test/refute_stderr_line.bats b/test/refute_stderr_line.bats new file mode 100755 index 0000000..ecb210b --- /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 +} diff --git a/test/test_helper.bash b/test/test_helper.bash index 8d476a8..af1ee0a 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -11,6 +11,8 @@ set -u : "${status:=}" : "${lines:=}" : "${output:=}" +: "${stderr:=}" +: "${stderr_lines:=}" assert_test_pass() { test "$status" -eq 0