Vendoring bats libraries.

Signed-off-by: Chad Metcalf <chad@docker.com>
This commit is contained in:
Chad Metcalf
2020-08-08 12:46:22 -07:00
committed by Chad Metcalf
parent 5185cedf4e
commit 6565a1f745
48 changed files with 4646 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
# assert
# ======
#
# Summary: Fail if the given expression evaluates to false.
#
# Usage: assert <expression>
# Options:
# <expression> The expression to evaluate for truthiness.
# *__Note:__ The expression must be a simple command.
# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands),
# such as `[[`, can be used only when executed with `bash -c`.*
#
# IO:
# STDERR - the failed expression, on failure
# Globals:
# none
# Returns:
# 0 - if expression evaluates to true
# 1 - otherwise
#
# ```bash
# @test 'assert()' {
# touch '/var/log/test.log'
# assert [ -e '/var/log/test.log' ]
# }
# ```
#
# On failure, the failed expression is displayed.
#
# ```
# -- assertion failed --
# expression : [ -e /var/log/test.log ]
# --
# ```
assert() {
if ! "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion failed' \
| fail
fi
}

View File

@@ -0,0 +1,42 @@
# assert_equal
# ============
#
# Summary: Fail if the actual and expected values are not equal.
#
# Usage: assert_equal <actual> <expected>
#
# Options:
# <actual> The value being compared.
# <expected> The value to compare against.
#
# ```bash
# @test 'assert_equal()' {
# assert_equal 'have' 'want'
# }
# ```
#
# IO:
# STDERR - expected and actual values, on failure
# Globals:
# none
# Returns:
# 0 - if values equal
# 1 - otherwise
#
# On failure, the expected and actual values are displayed.
#
# ```
# -- values do not equal --
# expected : want
# actual : have
# --
# ```
assert_equal() {
if [[ $1 != "$2" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$2" \
'actual' "$1" \
| batslib_decorate 'values do not equal' \
| fail
fi
}

View File

@@ -0,0 +1,78 @@
# assert_failure
# ==============
#
# Summary: Fail if `$status` is 0; or is not equal to the optionally provided status.
#
# Usage: assert_failure [<expected_status>]
#
# Options:
# <expected_status> The specific status code to check against.
# If not provided, simply asserts status is != 0.
#
# IO:
# STDERR - `$output`, on failure;
# - also, `$status` and `expected_status`, if provided
# Globals:
# status
# output
# Returns:
# 0 - if `$status' is 0,
# or if expected_status is provided but does not equal `$status'
# 1 - otherwise
#
# ```bash
# @test 'assert_failure() status only' {
# run echo 'Success!'
# assert_failure
# }
# ```
#
# On failure, `$output` is displayed.
#
# ```
# -- command succeeded, but it was expected to fail --
# output : Success!
# --
# ```
#
# ## Expected status
#
# When `expected_status` is provided, fail if `$status` does not equal the `expected_status`.
#
# ```bash
# @test 'assert_failure() with expected status' {
# run bash -c "echo 'Error!'; exit 1"
# assert_failure 2
# }
# ```
#
# On failure, both the expected and actual statuses, and `$output` are displayed.
#
# ```
# -- command failed as expected, but status differs --
# expected : 2
# actual : 1
# output : Error!
# --
# ```
assert_failure() {
: "${output?}"
: "${status?}"
(( $# > 0 )) && local -r expected="$1"
if (( status == 0 )); then
batslib_print_kv_single_or_multi 6 'output' "$output" \
| batslib_decorate 'command succeeded, but it was expected to fail' \
| fail
elif (( $# > 0 )) && (( status != expected )); then
{ local -ir width=8
batslib_print_kv_single "$width" \
'expected' "$expected" \
'actual' "$status"
batslib_print_kv_single_or_multi "$width" \
'output' "$output"
} \
| batslib_decorate 'command failed as expected, but status differs' \
| fail
fi
}

View File

@@ -0,0 +1,248 @@
# assert_line
# ===========
#
# Summary: Fail if the expected line is not found in the output (default) or at a specific line number.
#
# Usage: assert_line [-n index] [-p | -e] [--] <expected>
#
# Options:
# -n, --index <idx> Match the <idx>th line
# -p, --partial Match if `expected` is a substring of `$output` or line <idx>
# -e, --regexp Treat `expected` as an extended regular expression
# <expected> The expected line string, substring, or regular expression
#
# IO:
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# lines
# Returns:
# 0 - if matching line found
# 1 - otherwise
#
# Similarly to `assert_output`, this function verifies that a command or function produces the expected output.
# (It is the logical complement of `refute_line`.)
# It checks that the expected line appears 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 expected line.
# The assertion fails if the expected line is not found in `${lines[@]}`.
#
# ```bash
# @test 'assert_line() looking for line' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line 'want'
# }
# ```
#
# On failure, the expected line and the output are displayed.
#
# ```
# -- output does not contain line --
# line : want
# output (3 lines):
# have-0
# have-1
# have-2
# --
# ```
#
# ## Matching a specific line
#
# When the `--index <idx>` option is used (`-n <idx>` 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 `${lines[<idx>]}`.
#
# ```bash
# @test 'assert_line() specific line' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line --index 1 'want-1'
# }
# ```
#
# On failure, the index and the compared 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_line() partial matching' {
# run echo $'have 1\nhave 2\nhave 3'
# assert_line --partial 'want'
# }
# ```
#
# On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line.
#
# ```
# -- no output line contains substring --
# substring : want
# output (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_line() regular expression matching' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_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 `${lines[@]}' instead of `$output'!
assert_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${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_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_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_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if ! [[ ${lines[$idx]} =~ $expected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression does not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} != *"$expected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line does not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} != "$expected" ]]; then
batslib_print_kv_single 8 \
'index' "$idx" \
'expected' "$expected" \
'actual' "${lines[$idx]}" \
| batslib_decorate 'line differs' \
| fail
fi
fi
else
# Contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} =~ $expected ]] && return 0
done
{ local -ar single=( 'regexp' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
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' \
| fail
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
done
{ local -ar single=( 'substring' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
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' \
| fail
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == "$expected" ]] && return 0
done
{ local -ar single=( 'line' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
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' \
| fail
fi
fi
}

View File

@@ -0,0 +1,197 @@
# assert_output
# =============
#
# Summary: Fail if `$output' does not match the expected output.
#
# Usage: assert_output [-p | -e] [- | [--] <expected>]
#
# Options:
# -p, --partial Match if `expected` is a substring of `$output`
# -e, --regexp Treat `expected` as an extended regular expression
# -, --stdin Read `expected` value from STDIN
# <expected> The expected value, substring or regular expression
#
# IO:
# STDIN - [=$1] expected output
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# Returns:
# 0 - if output matches the expected value/partial/regexp
# 1 - otherwise
#
# This function verifies that a command or function produces the expected output.
# (It is the logical complement of `refute_output`.)
# Output matching can be literal (the default), partial or by regular expression.
# The expected output 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 `$output` does not equal the expected output.
#
# ```bash
# @test 'assert_output()' {
# run echo 'have'
# assert_output 'want'
# }
#
# @test 'assert_output() with pipe' {
# run echo 'hello'
# echo 'hello' | assert_output -
# }
#
# @test 'assert_output() with herestring' {
# run echo 'hello'
# assert_output - <<< hello
# }
# ```
#
# On failure, the expected and actual output are displayed.
#
# ```
# -- output differs --
# expected : want
# actual : have
# --
# ```
#
# ## Existence
#
# To assert that any output exists at all, omit the `expected` argument.
#
# ```bash
# @test 'assert_output()' {
# run echo 'have'
# assert_output
# }
# ```
#
# On failure, an error message is displayed.
#
# ```
# -- no output --
# expected non-empty output, but output 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 `$output`.
#
# ```bash
# @test 'assert_output() partial matching' {
# run echo 'ERROR: no such file or directory'
# assert_output --partial 'SUCCESS'
# }
# ```
#
# On failure, the substring and the output are displayed.
#
# ```
# -- output does not contain substring --
# substring : SUCCESS
# output : 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 `$output`.
#
# *__Note__:
# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output;
# not individual lines.*
#
# ```bash
# @test 'assert_output() regular expression matching' {
# run echo 'Foobar 0.1.0'
# assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, the regular expression and the output are displayed.
#
# ```
# -- regular expression does not match output --
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# output : Foobar 0.1.0
# --
# ```
assert_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_nonempty=0
local -i use_stdin=0
: "${output?}"
# 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_output' \
| fail
return $?
fi
# Arguments.
local expected
if (( use_stdin )); then
expected="$(cat -)"
else
expected="${1-}"
fi
# Matching.
if (( is_mode_nonempty )); then
if [ -z "$output" ]; then
echo 'expected non-empty output, but output was empty' \
| batslib_decorate 'no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_output' \
| fail
elif ! [[ $output =~ $expected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$expected" \
'output' "$output" \
| batslib_decorate 'regular expression does not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output != *"$expected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$expected" \
'output' "$output" \
| batslib_decorate 'output does not contain substring' \
| fail
fi
else
if [[ $output != "$expected" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$expected" \
'actual' "$output" \
| batslib_decorate 'output differs' \
| fail
fi
fi
}

View File

@@ -0,0 +1,44 @@
# assert_success
# ==============
#
# Summary: Fail if `$status` is not 0.
#
# Usage: assert_success
#
# IO:
# STDERR - `$status` and `$output`, on failure
# Globals:
# status
# output
# Returns:
# 0 - if `$status' is 0
# 1 - otherwise
#
# ```bash
# @test 'assert_success() status only' {
# run bash -c "echo 'Error!'; exit 1"
# assert_success
# }
# ```
#
# On failure, `$status` and `$output` are displayed.
#
# ```
# -- command failed --
# status : 1
# output : Error!
# --
# ```
assert_success() {
: "${output?}"
: "${status?}"
if (( status != 0 )); then
{ local -ir width=6
batslib_print_kv_single "$width" 'status' "$status"
batslib_print_kv_single_or_multi "$width" 'output' "$output"
} \
| batslib_decorate 'command failed' \
| fail
fi
}

View File

@@ -0,0 +1,42 @@
# refute
# ======
#
# Summary: Fail if the given expression evaluates to true.
#
# Usage: refute <expression>
#
# Options:
# <expression> The expression to evaluate for falsiness.
# *__Note:__ The expression must be a simple command.
# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands),
# such as `[[`, can be used only when executed with `bash -c`.*
#
# IO:
# STDERR - the successful expression, on failure
# Globals:
# none
# Returns:
# 0 - if expression evaluates to false
# 1 - otherwise
#
# ```bash
# @test 'refute()' {
# rm -f '/var/log/test.log'
# refute [ -e '/var/log/test.log' ]
# }
# ```
#
# On failure, the successful expression is displayed.
#
# ```
# -- assertion succeeded, but it was expected to fail --
# expression : [ -e /var/log/test.log ]
# --
# ```
refute() {
if "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
| fail
fi
}

View File

@@ -0,0 +1,271 @@
# refute_line
# ===========
#
# Summary: Fail if the unexpected line is found in the output (default) or at a specific line number.
#
# Usage: refute_line [-n index] [-p | -e] [--] <unexpected>
#
# Options:
# -n, --index <idx> Match the <idx>th line
# -p, --partial Match if `unexpected` is a substring of `$output` or line <idx>
# -e, --regexp Treat `unexpected` as an extended regular expression
# <unexpected> The unexpected line string, substring, or regular expression.
#
# IO:
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# lines
# Returns:
# 0 - if match not found
# 1 - otherwise
#
# Similarly to `refute_output`, this function verifies that a command or function does not produce the unexpected output.
# (It is the logical complement of `assert_line`.)
# 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.
# The assertion fails if the unexpected line is found in `${lines[@]}`.
#
# ```bash
# @test 'refute_line() looking for line' {
# run echo $'have-0\nwant\nhave-2'
# refute_line 'want'
# }
# ```
#
# On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed.
#
# ```
# -- line should not be in output --
# line : want
# index : 1
# output (3 lines):
# have-0
# > want
# have-2
# --
# ```
#
# ## Matching a specific line
#
# When the `--index <idx>` option is used (`-n <idx>` for short), the unexpected line is matched only against the line identified by the given index.
# The assertion fails if the unexpected line equals `${lines[<idx>]}`.
#
# ```bash
# @test 'refute_line() specific line' {
# run echo $'have-0\nwant-1\nhave-2'
# refute_line --index 1 'want-1'
# }
# ```
#
# On failure, the index and the unexpected line are displayed.
#
# ```
# -- line should differ --
# index : 1
# line : want-1
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, a match fails if the unexpected *substring* is found in the matched line.
#
# ```bash
# @test 'refute_line() partial matching' {
# run echo $'have 1\nwant 2\nhave 3'
# refute_line --partial 'want'
# }
# ```
#
# On failure, in addition to the details of literal matching, the substring is also displayed.
# When used with `--index <idx>` the substring replaces the unexpected line.
#
# ```
# -- no line should contain substring --
# substring : want
# index : 1
# output (3 lines):
# have 1
# > want 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* matches the line being tested.
#
# *__Note__:
# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.*
#
# ```bash
# @test 'refute_line() regular expression matching' {
# run echo $'Foobar v0.1.0\nRelease date: 2015-11-29'
# refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, in addition to the details of literal matching, the regular expression is also displayed.
# When used with `--index <idx>` the regular expression replaces the unexpected line.
#
# ```
# -- regular expression should not match line --
# index : 0
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# line : Foobar v0.1.0
# --
# ```
# FIXME(ztombol): Display `${lines[@]}' instead of `$output'!
refute_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${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: refute_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: refute_line' \
| fail
return $?
fi
# Arguments.
local -r unexpected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if [[ ${lines[$idx]} =~ $unexpected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression should not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} == "$unexpected" ]]; then
batslib_print_kv_single 5 \
'index' "$idx" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should differ' \
| fail
fi
fi
else
# Line contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} =~ $unexpected ]]; then
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
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
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should match the regular expression' \
| fail
return $?
fi
done
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
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
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should contain substring' \
| fail
return $?
fi
done
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == "$unexpected" ]]; then
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
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
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'line should not be in output' \
| fail
return $?
fi
done
fi
fi
}

View File

@@ -0,0 +1,199 @@
# refute_output
# =============
#
# Summary: Fail if `$output' matches the unexpected output.
#
# Usage: refute_output [-p | -e] [- | [--] <unexpected>]
#
# Options:
# -p, --partial Match if `unexpected` is a substring of `$output`
# -e, --regexp Treat `unexpected` as an extended regular expression
# -, --stdin Read `unexpected` value from STDIN
# <unexpected> The unexpected value, substring, or regular expression
#
# IO:
# STDIN - [=$1] unexpected output
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# Returns:
# 0 - if output matches the unexpected value/partial/regexp
# 1 - otherwise
#
# This function verifies that a command or function does not produce the unexpected output.
# (It is the logical complement of `assert_output`.)
# Output matching can be literal (the default), partial or by regular expression.
# The unexpected output 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 `$output` equals the unexpected output.
#
# ```bash
# @test 'refute_output()' {
# run echo 'want'
# refute_output 'want'
# }
#
# @test 'refute_output() with pipe' {
# run echo 'hello'
# echo 'world' | refute_output -
# }
#
# @test 'refute_output() with herestring' {
# run echo 'hello'
# refute_output - <<< world
# }
# ```
#
# On failure, the output is displayed.
#
# ```
# -- output equals, but it was expected to differ --
# output : want
# --
# ```
#
# ## Existence
#
# To assert that there is no output at all, omit the matching argument.
#
# ```bash
# @test 'refute_output()' {
# run foo --silent
# refute_output
# }
# ```
#
# On failure, an error message is displayed.
#
# ```
# -- unexpected output --
# expected no output, but output was non-empty
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, the assertion fails if the unexpected _substring_ is found in `$output`.
#
# ```bash
# @test 'refute_output() partial matching' {
# run echo 'ERROR: no such file or directory'
# refute_output --partial 'ERROR'
# }
# ```
#
# On failure, the substring and the output are displayed.
#
# ```
# -- output should not contain substring --
# substring : ERROR
# output : 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* matches `$output`.
#
# *__Note__:
# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output;
# not individual lines.*
#
# ```bash
# @test 'refute_output() regular expression matching' {
# run echo 'Foobar v0.1.0'
# refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, the regular expression and the output are displayed.
#
# ```
# -- regular expression should not match output --
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# output : Foobar v0.1.0
# --
# ```
refute_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_empty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_empty=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: refute_output' \
| fail
return $?
fi
# Arguments.
local unexpected
if (( use_stdin )); then
unexpected="$(cat -)"
else
unexpected="${1-}"
fi
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Matching.
if (( is_mode_empty )); then
if [ -n "$output" ]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output non-empty, but expected no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ $output =~ $unexpected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$unexpected" \
'output' "$output" \
| batslib_decorate 'regular expression should not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output == *"$unexpected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$unexpected" \
'output' "$output" \
| batslib_decorate 'output should not contain substring' \
| fail
fi
else
if [[ $output == "$unexpected" ]]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output equals, but it was expected to differ' \
| fail
fi
fi
}