diff --git a/README.md b/README.md index 3327f5d..e3c3116 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) Assert stderr does 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. @@ -773,6 +775,219 @@ actions like calling `run`. Thus, it's good practice to avoid using the array contains is the matching part of the value which is printed in the failing test log, as mentioned above. +### `assert_stderr` + +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 echo_err 'have' + assert_stderr 'want' + } + + @test 'assert_stderr() with pipe' { + run echo_err 'hello' + echo_err 'hello' | assert_stderr - + } + + @test 'assert_stderr() with herestring' { + run 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 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 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 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 + -- + ``` + +### `assert_stderr_line` + +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. + +***Warning:*** +*Due to a [bug in Bats][bats-93], empty stderr_lines are discarded from `${stderr_lines[@]}`, causing line indices to change and preventing testing for empty stderr_lines.* + +[bats-93]: https://github.com/sstephenson/bats/pull/93 + +#### 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 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 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 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 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/load.bash b/load.bash index c67d9e8..de99236 100644 --- a/load.bash +++ b/load.bash @@ -31,3 +31,5 @@ source "$(dirname "${BASH_SOURCE[0]}")/src/assert_line.bash" source "$(dirname "${BASH_SOURCE[0]}")/src/refute_line.bash" source "$(dirname "${BASH_SOURCE[0]}")/src/assert_regex.bash" source "$(dirname "${BASH_SOURCE[0]}")/src/refute_regex.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_stderr.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_stderr_line.bash" diff --git a/src/assert_stderr.bash b/src/assert_stderr.bash new file mode 100644 index 0000000..ca970f0 --- /dev/null +++ b/src/assert_stderr.bash @@ -0,0 +1,202 @@ +# 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. +# +# ## 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 echo_err 'have' +# assert_stderr 'want' +# } +# +# @test 'assert_stderr() with pipe' { +# run echo_err 'hello' +# echo_err 'hello' | assert_stderr - +# } +# +# @test 'assert_stderr() with herestring' { +# run 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 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 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 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 +# -- +# ``` +assert_stderr() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_nonempty=0 + local -i use_stdin=0 + : "${stderr?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_nonempty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_stderr' \ + | fail + return $? + fi + + # Arguments. + local expected + if (( use_stdin )); then + expected="$(cat -)" + else + expected="${1-}" + fi + + # Matching. + if (( is_mode_nonempty )); then + if [ -z "$stderr" ]; then + echo 'expected non-empty stderr, but stderr was empty' \ + | batslib_decorate 'no stderr' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_stderr' \ + | fail + elif ! [[ $stderr =~ $expected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$expected" \ + 'stderr' "$stderr" \ + | batslib_decorate 'regular expression does not match stderr' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $stderr != *"$expected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$expected" \ + 'stderr' "$stderr" \ + | batslib_decorate 'stderr does not contain substring' \ + | fail + fi + else + if [[ $stderr != "$expected" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$expected" \ + 'actual' "$stderr" \ + | batslib_decorate 'stderr differs' \ + | fail + fi + fi +} + diff --git a/src/assert_stderr_line.bash b/src/assert_stderr_line.bash new file mode 100644 index 0000000..057cb46 --- /dev/null +++ b/src/assert_stderr_line.bash @@ -0,0 +1,252 @@ +# 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. +# +# *__Warning:__ +# Due to a [bug in Bats][bats-93], empty stderr_lines are discarded from `${stderr_lines[@]}`, +# causing line indices to change and preventing testing for empty stderr_lines.* +# +# [bats-93]: https://github.com/sstephenson/bats/pull/93 +# +# ## 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 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 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 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 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 +# -- +# ``` +# FIXME(ztombol): Display `${stderr_lines[@]}' instead of `$stderr'! +assert_stderr_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${stderr_lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: assert_stderr_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_stderr_line' \ + | fail + return $? + fi + + # Arguments. + local -r expected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_stderr_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if ! [[ ${stderr_lines[$idx]} =~ $expected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$expected" \ + 'line' "${stderr_lines[$idx]}" \ + | batslib_decorate 'regular expression does not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${stderr_lines[$idx]} != *"$expected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$expected" \ + 'line' "${stderr_lines[$idx]}" \ + | batslib_decorate 'line does not contain substring' \ + | fail + fi + else + if [[ ${stderr_lines[$idx]} != "$expected" ]]; then + batslib_print_kv_single 8 \ + 'index' "$idx" \ + 'expected' "$expected" \ + 'actual' "${stderr_lines[$idx]}" \ + | batslib_decorate 'line differs' \ + | fail + fi + fi + else + # Contained in stderr. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#stderr_lines[@]}; ++idx )); do + [[ ${stderr_lines[$idx]} =~ $expected ]] && return 0 + done + { local -ar single=( 'regexp' "$expected" ) + local -ar may_be_multi=( 'stderr' "$stderr" ) + 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 stderr line matches regular expression' \ + | fail + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#stderr_lines[@]}; ++idx )); do + [[ ${stderr_lines[$idx]} == *"$expected"* ]] && return 0 + done + { local -ar single=( 'substring' "$expected" ) + local -ar may_be_multi=( 'stderr' "$stderr" ) + 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 stderr line contains substring' \ + | fail + else + local -i idx + for (( idx = 0; idx < ${#stderr_lines[@]}; ++idx )); do + [[ ${stderr_lines[$idx]} == "$expected" ]] && return 0 + done + { local -ar single=( 'line' "$expected" ) + local -ar may_be_multi=( 'stderr' "$stderr" ) + 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 'stderr does not contain line' \ + | fail + fi + fi +} 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/test_helper.bash b/test/test_helper.bash index beb9298..d3ff3db 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -10,6 +10,8 @@ set -u : "${status:=}" : "${lines:=}" : "${output:=}" +: "${stderr:=}" +: "${stderr_lines:=}" assert_test_pass() { test "$status" -eq 0