├── .gitignore ├── .github └── workflows │ ├── test.yml │ ├── sync-default-branch.yml │ └── release.yml ├── test ├── assert.bats ├── refute.bats ├── test_helper.bash ├── assert_success.bats ├── assert_equal.bats ├── assert_not_equal.bats ├── assert_failure.bats ├── assert_regex.bats ├── refute_regex.bats ├── refute_output.bats ├── refute_stderr.bats ├── assert_output.bats ├── assert_stderr.bats ├── refute_line.bats ├── assert_line.bats ├── refute_stderr_line.bats └── assert_stderr_line.bats ├── install.sh ├── src ├── assert_equal.bash ├── assert_not_equal.bash ├── assert_success.bash ├── assert.bash ├── refute.bash ├── assert_regex.bash ├── refute_regex.bash ├── assert_failure.bash ├── refute_output.bash ├── assert_output.bash ├── assert_line.bash └── refute_line.bash ├── docs └── CONTRIBUTING.md ├── package.json ├── load.bash ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /yarn.lock 3 | /bats-assert-*.tgz 4 | test/.bats/run-logs/ 5 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | test: 9 | uses: bats-core/.github/.github/workflows/test.yml@v1 10 | -------------------------------------------------------------------------------- /test/assert.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test 'assert() : returns 0 if evaluates to TRUE' { 6 | run assert true 7 | assert_test_pass 8 | } 9 | 10 | @test 'assert() : returns 1 and displays if it evaluates to FALSE' { 11 | run assert false 12 | 13 | assert_test_fail <<'ERR_MSG' 14 | 15 | -- assertion failed -- 16 | expression : false 17 | -- 18 | ERR_MSG 19 | } 20 | -------------------------------------------------------------------------------- /test/refute.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test 'refute() : returns 0 if evaluates to FALSE' { 6 | run refute false 7 | assert_test_pass 8 | } 9 | 10 | @test 'refute() : returns 1 and displays if it evaluates to TRUE' { 11 | run refute true 12 | assert_test_fail <<'ERR_MSG' 13 | 14 | -- assertion succeeded, but it was expected to fail -- 15 | expression : true 16 | -- 17 | ERR_MSG 18 | } 19 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | BATS_ROOT="${0%/*}" 6 | PREFIX="${1%/}" 7 | LIBDIR="${2:-lib}" 8 | 9 | if [[ -z "$PREFIX" ]]; then 10 | printf '%s\n' \ 11 | "usage: $0 [base_libdir]" \ 12 | " e.g. $0 /usr/local" \ 13 | " $0 /usr/local lib64" >&2 14 | exit 1 15 | fi 16 | 17 | BATS_LIBDIR=$PREFIX/$LIBDIR/bats/bats-assert 18 | 19 | install -d -m 755 "$BATS_LIBDIR/src" 20 | install -m 755 "$BATS_ROOT/load.bash" "$BATS_LIBDIR" 21 | install -m 755 "$BATS_ROOT/src/"* "$BATS_LIBDIR/src" 22 | 23 | echo "Installed Bats Assert to $BATS_LIBDIR" 24 | -------------------------------------------------------------------------------- /test/test_helper.bash: -------------------------------------------------------------------------------- 1 | # Load dependencies. 2 | BATS_LIB_PATH=$PWD/node_modules:${BATS_LIB_PATH-} 3 | bats_load_library 'bats-support' 4 | 5 | # Load library. 6 | load '../load' 7 | 8 | # validate that bats-assert is safe to use under -u 9 | set -u 10 | 11 | : "${status:=}" 12 | : "${lines:=}" 13 | : "${output:=}" 14 | : "${stderr:=}" 15 | : "${stderr_lines:=}" 16 | 17 | assert_test_pass() { 18 | test "$status" -eq 0 19 | test "${#lines[@]}" -eq 0 20 | } 21 | 22 | assert_test_fail() { 23 | local err_msg="${1-$(cat -)}" 24 | local num_lines 25 | num_lines="$(printf '%s' "$err_msg" | wc -l)" 26 | 27 | test "$status" -eq 1 28 | test "${#lines[@]}" -eq "$num_lines" 29 | test "$output" == "$err_msg" 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/sync-default-branch.yml: -------------------------------------------------------------------------------- 1 | name: Sync Default Branch 2 | on: 3 | push: { branches: main } 4 | workflow_dispatch: 5 | permissions: {} 6 | 7 | jobs: 8 | sync-default-branch: 9 | if: github.ref_name == github.event.repository.default_branch 10 | permissions: {contents: write} 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 14 | with: {egress-policy: audit} 15 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 16 | - run: git push --force origin HEAD:refs/heads/master 17 | 18 | # One-time commands for users to switch-over: 19 | # 20 | # ```console 21 | # git branch -m master main 22 | # git fetch origin 23 | # git branch -u origin/main main 24 | # git remote set-head origin -a 25 | # ``` 26 | -------------------------------------------------------------------------------- /src/assert_equal.bash: -------------------------------------------------------------------------------- 1 | # assert_equal 2 | # ============ 3 | # 4 | # Summary: Fail if the actual and expected values are not equal. 5 | # 6 | # Usage: assert_equal 7 | # 8 | # Options: 9 | # The value being compared. 10 | # The value to compare against. 11 | # 12 | # ```bash 13 | # @test 'assert_equal()' { 14 | # assert_equal 'have' 'want' 15 | # } 16 | # ``` 17 | # 18 | # IO: 19 | # STDERR - expected and actual values, on failure 20 | # Globals: 21 | # none 22 | # Returns: 23 | # 0 - if values equal 24 | # 1 - otherwise 25 | # 26 | # On failure, the expected and actual values are displayed. 27 | # 28 | # ``` 29 | # -- values do not equal -- 30 | # expected : want 31 | # actual : have 32 | # -- 33 | # ``` 34 | assert_equal() { 35 | if [[ $1 != "$2" ]]; then 36 | batslib_print_kv_single_or_multi 8 \ 37 | 'expected' "$2" \ 38 | 'actual' "$1" \ 39 | | batslib_decorate 'values do not equal' \ 40 | | fail 41 | fi 42 | } 43 | -------------------------------------------------------------------------------- /src/assert_not_equal.bash: -------------------------------------------------------------------------------- 1 | # assert_not_equal 2 | # ============ 3 | # 4 | # Summary: Fail if the actual and unexpected values are equal. 5 | # 6 | # Usage: assert_not_equal 7 | # 8 | # Options: 9 | # The value being compared. 10 | # The value to compare against. 11 | # 12 | # ```bash 13 | # @test 'assert_not_equal()' { 14 | # assert_not_equal 'foo' 'foo' 15 | # } 16 | # ``` 17 | # 18 | # IO: 19 | # STDERR - expected and actual values, on failure 20 | # Globals: 21 | # none 22 | # Returns: 23 | # 0 - if actual does not equal unexpected 24 | # 1 - otherwise 25 | # 26 | # On failure, the unexpected and actual values are displayed. 27 | # 28 | # ``` 29 | # -- values should not be equal -- 30 | # unexpected : foo 31 | # actual : foo 32 | # -- 33 | # ``` 34 | assert_not_equal() { 35 | if [[ "$1" == "$2" ]]; then 36 | batslib_print_kv_single_or_multi 10 \ 37 | 'unexpected' "$2" \ 38 | 'actual' "$1" \ 39 | | batslib_decorate 'values should not be equal' \ 40 | | fail 41 | fi 42 | } 43 | -------------------------------------------------------------------------------- /src/assert_success.bash: -------------------------------------------------------------------------------- 1 | # assert_success 2 | # ============== 3 | # 4 | # Summary: Fail if `$status` is not 0. 5 | # 6 | # Usage: assert_success 7 | # 8 | # IO: 9 | # STDERR - `$status` and `$output`, on failure 10 | # Globals: 11 | # status 12 | # output 13 | # Returns: 14 | # 0 - if `$status' is 0 15 | # 1 - otherwise 16 | # 17 | # ```bash 18 | # @test 'assert_success() status only' { 19 | # run bash -c "echo 'Error!'; exit 1" 20 | # assert_success 21 | # } 22 | # ``` 23 | # 24 | # On failure, `$status` and `$output` are displayed. 25 | # 26 | # ``` 27 | # -- command failed -- 28 | # status : 1 29 | # output : Error! 30 | # -- 31 | # ``` 32 | assert_success() { 33 | : "${output?}" 34 | : "${status?}" 35 | 36 | if (( status != 0 )); then 37 | { local -ir width=6 38 | batslib_print_kv_single "$width" 'status' "$status" 39 | batslib_print_kv_single_or_multi "$width" 'output' "$output" 40 | if [[ -n "${stderr-}" ]]; then 41 | batslib_print_kv_single_or_multi "$width" 'stderr' "$stderr" 42 | fi 43 | } \ 44 | | batslib_decorate 'command failed' \ 45 | | fail 46 | fi 47 | } 48 | -------------------------------------------------------------------------------- /src/assert.bash: -------------------------------------------------------------------------------- 1 | # assert 2 | # ====== 3 | # 4 | # Summary: Fail if the given expression evaluates to false. 5 | # 6 | # Usage: assert 7 | 8 | # Options: 9 | # The expression to evaluate for truthiness. 10 | # *__Note:__ The expression must be a simple command. 11 | # [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands), 12 | # such as `[[`, can be used only when executed with `bash -c`.* 13 | # 14 | # IO: 15 | # STDERR - the failed expression, on failure 16 | # Globals: 17 | # none 18 | # Returns: 19 | # 0 - if expression evaluates to true 20 | # 1 - otherwise 21 | # 22 | # ```bash 23 | # @test 'assert()' { 24 | # touch '/var/log/test.log' 25 | # assert [ -e '/var/log/test.log' ] 26 | # } 27 | # ``` 28 | # 29 | # On failure, the failed expression is displayed. 30 | # 31 | # ``` 32 | # -- assertion failed -- 33 | # expression : [ -e /var/log/test.log ] 34 | # -- 35 | # ``` 36 | assert() { 37 | if ! "$@"; then 38 | batslib_print_kv_single 10 'expression' "$*" \ 39 | | batslib_decorate 'assertion failed' \ 40 | | fail 41 | fi 42 | } 43 | -------------------------------------------------------------------------------- /test/assert_success.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "assert_success(): returns 0 if \`\$status' is 0" { 6 | run true 7 | run assert_success 8 | 9 | assert_test_pass 10 | } 11 | 12 | @test "assert_success(): returns 1 and displays details if \`\$status' is not 0" { 13 | run bash -c 'echo "a" 14 | exit 1' 15 | run assert_success 16 | 17 | assert_test_fail <<'ERR_MSG' 18 | 19 | -- command failed -- 20 | status : 1 21 | output : a 22 | -- 23 | ERR_MSG 24 | } 25 | 26 | @test "assert_success(): displays \`\$output' in multi-line format if it is longer than one line" { 27 | run bash -c 'printf "a 0\na 1" 28 | exit 1' 29 | run assert_success 30 | 31 | assert_test_fail <<'ERR_MSG' 32 | 33 | -- command failed -- 34 | status : 1 35 | output (2 lines): 36 | a 0 37 | a 1 38 | -- 39 | ERR_MSG 40 | } 41 | 42 | @test "assert_success(): displays \`\$stderr' if it is set" { 43 | bats_require_minimum_version 1.5.0 44 | run --separate-stderr \ 45 | bash -c 'echo "a" 46 | echo "b" >&2 47 | exit 1' 48 | run assert_success 49 | 50 | assert_test_fail <<'ERR_MSG' 51 | 52 | -- command failed -- 53 | status : 1 54 | output : a 55 | stderr : b 56 | -- 57 | ERR_MSG 58 | } -------------------------------------------------------------------------------- /src/refute.bash: -------------------------------------------------------------------------------- 1 | # refute 2 | # ====== 3 | # 4 | # Summary: Fail if the given expression evaluates to true. 5 | # 6 | # Usage: refute 7 | # 8 | # Options: 9 | # The expression to evaluate for falsiness. 10 | # *__Note:__ The expression must be a simple command. 11 | # [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands), 12 | # such as `[[`, can be used only when executed with `bash -c`.* 13 | # 14 | # IO: 15 | # STDERR - the successful expression, on failure 16 | # Globals: 17 | # none 18 | # Returns: 19 | # 0 - if expression evaluates to false 20 | # 1 - otherwise 21 | # 22 | # ```bash 23 | # @test 'refute()' { 24 | # rm -f '/var/log/test.log' 25 | # refute [ -e '/var/log/test.log' ] 26 | # } 27 | # ``` 28 | # 29 | # On failure, the successful expression is displayed. 30 | # 31 | # ``` 32 | # -- assertion succeeded, but it was expected to fail -- 33 | # expression : [ -e /var/log/test.log ] 34 | # -- 35 | # ``` 36 | refute() { 37 | if "$@"; then 38 | batslib_print_kv_single 10 'expression' "$*" \ 39 | | batslib_decorate 'assertion succeeded, but it was expected to fail' \ 40 | | fail 41 | fi 42 | } 43 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Releasing 4 | 5 | From a clean working copy, run [`npm version major|minor|patch|VERSION`][npm-version]. 6 | This will bump the package version, commit, tag, and push. 7 | The tag-push event triggers the release workflow on GitHub. 8 | The workflow creates a GitHub Release from the tag and publishes to npm. 9 | 10 | It is preferred for these version commits and tags to be signed by git. This 11 | not only aids with provenance, but the act of signing the tag also ensures 12 | these release tags are [annotated tags][], not [lightweight tags][]. First be 13 | sure git is [configured for signing][git signing]. Then either tell git to 14 | sign _all_ tags with [`tag.gpgSign = true`][tag.gpgSign] (recommended), or 15 | configure npm to sign its tags with [`sign-git-tag = true`][sign-git-tag]. 16 | 17 | [npm-version]: https://docs.npmjs.com/cli/v11/commands/npm-version 18 | [annotated tags]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags 19 | [lightweight tags]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_lightweight_tags 20 | [git signing]: https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work 21 | [tag.gpgSign]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-taggpgSign 22 | [sign-git-tag]: https://docs.npmjs.com/cli/v11/using-npm/config#sign-git-tag 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: { tags: "v[0-9]+.[0-9]+.[0-9]+*" } 4 | 5 | permissions: {} 6 | jobs: 7 | github: 8 | permissions: { contents: write } 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 12 | with: { egress-policy: audit } 13 | - uses: actions/checkout@v4 14 | # TODO exit this job differently than success if release already exists 15 | - name: gh release create 16 | run: | 17 | # shellcheck disable=SC2086 18 | gh release view $tag || \ 19 | gh release create ${tag/*-*/"$tag" --prerelease} --generate-notes 20 | env: 21 | GH_TOKEN: ${{ github.token }} 22 | tag: ${{ github.ref_name }} 23 | 24 | npm: 25 | permissions: { id-token: write } 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 29 | with: { egress-policy: audit } 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | with: 33 | registry-url: https://registry.npmjs.org 34 | - run: npm ci 35 | - run: npm publish --provenance 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bats-assert", 3 | "version": "2.2.4", 4 | "description": "Common assertions for Bats", 5 | "homepage": "https://github.com/bats-core/bats-assert", 6 | "license": "CC0-1.0", 7 | "author": "Zoltán Tömböl (https://github.com/ztombol)", 8 | "contributors": [ 9 | "Sam Stephenson (http://sstephenson.us/)", 10 | "Jason Karns (http://jasonkarns.com)", 11 | "Mislav Marohnić (http://mislav.net/)", 12 | "Tim Pope (https://github.com/tpope)" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/bats-core/bats-assert.git" 17 | }, 18 | "bugs": "https://github.com/bats-core/bats-assert/issues", 19 | "directories": { 20 | "lib": "src", 21 | "test": "test" 22 | }, 23 | "files": [ 24 | "load.bash", 25 | "src" 26 | ], 27 | "scripts": { 28 | "test": "bats ${CI+-t} test", 29 | "postversion": "git push --follow-tags" 30 | }, 31 | "devDependencies": { 32 | "bats": "^1", 33 | "bats-support": "^0.3" 34 | }, 35 | "peerDependencies": { 36 | "bats": "0.4 || ^1", 37 | "bats-support": "^0.3" 38 | }, 39 | "keywords": [ 40 | "bats", 41 | "bash", 42 | "shell", 43 | "test", 44 | "unit", 45 | "assert", 46 | "assertion", 47 | "helper" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /test/assert_equal.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test 'assert_equal() : returns 0 if equals ' { 6 | run assert_equal 'a' 'a' 7 | assert_test_pass 8 | } 9 | 10 | @test 'assert_equal() : returns 1 and displays details if does not equal ' { 11 | run assert_equal 'a' 'b' 12 | 13 | assert_test_fail <<'ERR_MSG' 14 | 15 | -- values do not equal -- 16 | expected : b 17 | actual : a 18 | -- 19 | ERR_MSG 20 | } 21 | 22 | @test 'assert_equal() : displays details in multi-line format if is longer than one line' { 23 | run assert_equal $'a 0\na 1' 'b' 24 | 25 | assert_test_fail <<'ERR_MSG' 26 | 27 | -- values do not equal -- 28 | expected (1 lines): 29 | b 30 | actual (2 lines): 31 | a 0 32 | a 1 33 | -- 34 | ERR_MSG 35 | } 36 | 37 | @test 'assert_equal() : displays details in multi-line format if is longer than one line' { 38 | run assert_equal 'a' $'b 0\nb 1' 39 | 40 | assert_test_fail <<'ERR_MSG' 41 | 42 | -- values do not equal -- 43 | expected (2 lines): 44 | b 0 45 | b 1 46 | actual (1 lines): 47 | a 48 | -- 49 | ERR_MSG 50 | } 51 | 52 | @test 'assert_equal() : performs literal matching' { 53 | run assert_equal 'a' '*' 54 | 55 | assert_test_fail <<'ERR_MSG' 56 | 57 | -- values do not equal -- 58 | expected : * 59 | actual : a 60 | -- 61 | ERR_MSG 62 | } 63 | -------------------------------------------------------------------------------- /test/assert_not_equal.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test 'assert_not_equal() : returns 0 if does not equal ' { 6 | run assert_not_equal foo bar 7 | assert_test_pass 8 | 9 | run assert_not_equal "foo" "bar" 10 | assert_test_pass 11 | 12 | run assert_not_equal "foo" "" 13 | assert_test_pass 14 | 15 | run assert_not_equal "" "foo" 16 | assert_test_pass 17 | } 18 | 19 | @test 'assert_not_equal() : returns 1 and displays details if equals ' { 20 | run assert_not_equal 'foobar' 'foobar' 21 | assert_test_fail <<'ERR_MSG' 22 | 23 | -- values should not be equal -- 24 | unexpected : foobar 25 | actual : foobar 26 | -- 27 | ERR_MSG 28 | 29 | run assert_not_equal 1 1 30 | assert_test_fail <<'ERR_MSG' 31 | 32 | -- values should not be equal -- 33 | unexpected : 1 34 | actual : 1 35 | -- 36 | ERR_MSG 37 | } 38 | 39 | @test 'assert_not_equal() : displays details in multi-line format if and are longer than one line' { 40 | run assert_not_equal $'foo\nbar' $'foo\nbar' 41 | assert_test_fail <<'ERR_MSG' 42 | 43 | -- values should not be equal -- 44 | unexpected (2 lines): 45 | foo 46 | bar 47 | actual (2 lines): 48 | foo 49 | bar 50 | -- 51 | ERR_MSG 52 | } 53 | 54 | @test 'assert_not_equal() : performs literal matching' { 55 | run assert_not_equal 'a' '*' 56 | assert_test_pass 57 | } -------------------------------------------------------------------------------- /load.bash: -------------------------------------------------------------------------------- 1 | # bats-assert - Common assertions for Bats 2 | # 3 | # Written in 2016 by Zoltan Tombol 4 | # 5 | # To the extent possible under law, the author(s) have dedicated all 6 | # copyright and related and neighboring rights to this software to the 7 | # public domain worldwide. This software is distributed without any 8 | # warranty. 9 | # 10 | # You should have received a copy of the CC0 Public Domain Dedication 11 | # along with this software. If not, see 12 | # . 13 | # 14 | # Assertions are functions that perform a test and output relevant 15 | # information on failure to help debugging. They return 1 on failure 16 | # and 0 otherwise. 17 | # 18 | # All output is formatted for readability using the functions of 19 | # `output.bash' and sent to the standard error. 20 | 21 | # shellcheck disable=1090 22 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert.bash" 23 | source "$(dirname "${BASH_SOURCE[0]}")/src/refute.bash" 24 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_equal.bash" 25 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_not_equal.bash" 26 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_success.bash" 27 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_failure.bash" 28 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_output.bash" 29 | source "$(dirname "${BASH_SOURCE[0]}")/src/refute_output.bash" 30 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_line.bash" 31 | source "$(dirname "${BASH_SOURCE[0]}")/src/refute_line.bash" 32 | source "$(dirname "${BASH_SOURCE[0]}")/src/assert_regex.bash" 33 | source "$(dirname "${BASH_SOURCE[0]}")/src/refute_regex.bash" 34 | -------------------------------------------------------------------------------- /src/assert_regex.bash: -------------------------------------------------------------------------------- 1 | # `assert_regex` 2 | # 3 | # This function is similar to `assert_equal` but uses pattern matching instead 4 | # of equality, by wrapping `[[ value =~ pattern ]]`. 5 | # 6 | # Fail if the value (first parameter) does not match the pattern (second 7 | # parameter). 8 | # 9 | # ```bash 10 | # @test 'assert_regex()' { 11 | # assert_regex 'what' 'x$' 12 | # } 13 | # ``` 14 | # 15 | # On failure, the value and the pattern are displayed. 16 | # 17 | # ``` 18 | # -- values does not match regular expression -- 19 | # value : what 20 | # pattern : x$ 21 | # -- 22 | # ``` 23 | # 24 | # If the value is longer than one line then it is displayed in *multi-line* 25 | # format. 26 | # 27 | # An error is displayed if the specified extended regular expression is invalid. 28 | # 29 | # For description of the matching behavior, refer to the documentation of the 30 | # `=~` operator in the 31 | # [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html. 32 | # Note that the `BASH_REMATCH` array is available immediately after the 33 | # assertion succeeds but is fragile, i.e. prone to being overwritten as a side 34 | # effect of other actions. 35 | assert_regex() { 36 | local -r value="${1}" 37 | local -r pattern="${2}" 38 | 39 | if ! __check_is_valid_regex "$pattern" assert_regex; then 40 | return 1 41 | elif ! [[ "${value}" =~ ${pattern} ]]; then 42 | if shopt -p nocasematch &>/dev/null; then 43 | local case_sensitive=insensitive 44 | else 45 | local case_sensitive=sensitive 46 | fi 47 | batslib_print_kv_single_or_multi 8 \ 48 | 'value' "${value}" \ 49 | 'pattern' "${pattern}" \ 50 | 'case' "${case_sensitive}" \ 51 | | batslib_decorate 'value does not match regular expression' \ 52 | | fail 53 | fi 54 | } 55 | -------------------------------------------------------------------------------- /src/refute_regex.bash: -------------------------------------------------------------------------------- 1 | # `refute_regex` 2 | # 3 | # This function is similar to `refute_equal` but uses pattern matching instead 4 | # of equality, by wrapping `! [[ value =~ pattern ]]`. 5 | # 6 | # Fail if the value (first parameter) matches the pattern (second parameter). 7 | # 8 | # ```bash 9 | # @test 'refute_regex()' { 10 | # refute_regex 'WhatsApp' 'Threema' 11 | # } 12 | # ``` 13 | # 14 | # On failure, the value, the pattern and the match are displayed. 15 | # 16 | # ``` 17 | # @test 'refute_regex()' { 18 | # refute_regex 'WhatsApp' 'What.' 19 | # } 20 | # 21 | # -- value matches regular expression -- 22 | # value : WhatsApp 23 | # pattern : What. 24 | # match : Whats 25 | # case : sensitive 26 | # -- 27 | # ``` 28 | # 29 | # If the value or pattern is longer than one line then it is displayed in 30 | # *multi-line* format. 31 | # 32 | # An error is displayed if the specified extended regular expression is invalid. 33 | # 34 | # For description of the matching behavior, refer to the documentation of the 35 | # `=~` operator in the 36 | # [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html. 37 | # 38 | # Note that the `BASH_REMATCH` array is available immediately after the 39 | # assertion fails but is fragile, i.e. prone to being overwritten as a side 40 | # effect of other actions like calling `run`. Thus, it's good practice to avoid 41 | # using `BASH_REMATCH` in conjunction with `refute_regex()`. The valuable 42 | # information the array contains is the matching part of the value which is 43 | # printed in the failing test log, as mentioned above. 44 | refute_regex() { 45 | local -r value="${1}" 46 | local -r pattern="${2}" 47 | 48 | if ! __check_is_valid_regex "${pattern}" "${FUNCNAME[0]}"; then 49 | return 1 50 | elif [[ "${value}" =~ ${pattern} ]]; then 51 | if shopt -p nocasematch &>/dev/null; then 52 | local case_sensitive=insensitive 53 | else 54 | local case_sensitive=sensitive 55 | fi 56 | batslib_print_kv_single_or_multi 8 \ 57 | 'value' "${value}" \ 58 | 'pattern' "${pattern}" \ 59 | 'match' "${BASH_REMATCH[0]}" \ 60 | 'case' "${case_sensitive}" \ 61 | | batslib_decorate 'value matches regular expression' \ 62 | | fail 63 | fi 64 | } 65 | -------------------------------------------------------------------------------- /test/assert_failure.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "assert_failure(): returns 0 if \`\$status' is not 0" { 6 | run false 7 | run assert_failure 8 | assert_test_pass 9 | } 10 | 11 | @test "assert_failure(): returns 1 and displays details if \`\$status' is 0" { 12 | run bash -c 'echo "a" 13 | exit 0' 14 | run assert_failure 15 | 16 | assert_test_fail <<'ERR_MSG' 17 | 18 | -- command succeeded, but it was expected to fail -- 19 | output : a 20 | -- 21 | ERR_MSG 22 | } 23 | 24 | @test "assert_failure(): returns 1 and displays \`\$stderr' if it is set" { 25 | bats_require_minimum_version 1.5.0 26 | run --separate-stderr \ 27 | bash -c 'echo "a" 28 | echo "b" >&2 29 | exit 0' 30 | echo "Stderr: $stderr" >&3 31 | run assert_failure 32 | 33 | assert_test_fail <<'ERR_MSG' 34 | 35 | -- command succeeded, but it was expected to fail -- 36 | output : a 37 | stderr : b 38 | -- 39 | ERR_MSG 40 | } 41 | 42 | @test "assert_failure(): displays \`\$output' in multi-line format if it is longer then one line" { 43 | run bash -c 'printf "a 0\na 1" 44 | exit 0' 45 | run assert_failure 46 | 47 | assert_test_fail <<'ERR_MSG' 48 | 49 | -- command succeeded, but it was expected to fail -- 50 | output (2 lines): 51 | a 0 52 | a 1 53 | -- 54 | ERR_MSG 55 | } 56 | 57 | @test "assert_failure() : returns 0 if \`\$status' equals " { 58 | run bash -c 'exit 1' 59 | run assert_failure 1 60 | assert_test_pass 61 | } 62 | 63 | @test "assert_failure() : returns 1 and displays details if \`\$status' does not equal " { 64 | run bash -c 'echo "a" 65 | exit 1' 66 | run assert_failure 2 67 | 68 | assert_test_fail <<'ERR_MSG' 69 | 70 | -- command failed as expected, but status differs -- 71 | expected : 2 72 | actual : 1 73 | output : a 74 | -- 75 | ERR_MSG 76 | } 77 | 78 | @test "assert_failure() : displays \`\$output' in multi-line format if it is longer then one line" { 79 | run bash -c 'printf "a 0\na 1" 80 | exit 1' 81 | run assert_failure 2 82 | 83 | assert_test_fail <<'ERR_MSG' 84 | 85 | -- command failed as expected, but status differs -- 86 | expected : 2 87 | actual : 1 88 | output (2 lines): 89 | a 0 90 | a 1 91 | -- 92 | ERR_MSG 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/assert_failure.bash: -------------------------------------------------------------------------------- 1 | # assert_failure 2 | # ============== 3 | # 4 | # Summary: Fail if `$status` is 0; or is not equal to the optionally provided status. 5 | # 6 | # Usage: assert_failure [] 7 | # 8 | # Options: 9 | # The specific status code to check against. 10 | # If not provided, simply asserts status is != 0. 11 | # 12 | # IO: 13 | # STDERR - `$output`, on failure; 14 | # - also, `$status` and `expected_status`, if provided 15 | # Globals: 16 | # status 17 | # output 18 | # Returns: 19 | # 0 - if `$status' is 0, 20 | # or if expected_status is provided but does not equal `$status' 21 | # 1 - otherwise 22 | # 23 | # ```bash 24 | # @test 'assert_failure() status only' { 25 | # run echo 'Success!' 26 | # assert_failure 27 | # } 28 | # ``` 29 | # 30 | # On failure, `$output` is displayed. 31 | # 32 | # ``` 33 | # -- command succeeded, but it was expected to fail -- 34 | # output : Success! 35 | # -- 36 | # ``` 37 | # 38 | # ## Expected status 39 | # 40 | # When `expected_status` is provided, fail if `$status` does not equal the `expected_status`. 41 | # 42 | # ```bash 43 | # @test 'assert_failure() with expected status' { 44 | # run bash -c "echo 'Error!'; exit 1" 45 | # assert_failure 2 46 | # } 47 | # ``` 48 | # 49 | # On failure, both the expected and actual statuses, and `$output` are displayed. 50 | # 51 | # ``` 52 | # -- command failed as expected, but status differs -- 53 | # expected : 2 54 | # actual : 1 55 | # output : Error! 56 | # -- 57 | # ``` 58 | assert_failure() { 59 | : "${output?}" 60 | : "${status?}" 61 | 62 | (( $# > 0 )) && local -r expected="$1" 63 | if (( status == 0 )); then 64 | { local -ir width=6 65 | batslib_print_kv_single_or_multi "$width" 'output' "$output" 66 | if [[ -n "${stderr-}" ]]; then 67 | batslib_print_kv_single_or_multi "$width" 'stderr' "$stderr" 68 | fi 69 | } \ 70 | | batslib_decorate 'command succeeded, but it was expected to fail' \ 71 | | fail 72 | elif (( $# > 0 )) && (( status != expected )); then 73 | { local -ir width=8 74 | batslib_print_kv_single "$width" \ 75 | 'expected' "$expected" \ 76 | 'actual' "$status" 77 | batslib_print_kv_single_or_multi "$width" \ 78 | 'output' "$output" 79 | if [[ -n "${stderr-}" ]]; then 80 | batslib_print_kv_single_or_multi "$width" 'stderr' "$stderr" 81 | fi 82 | } \ 83 | | batslib_decorate 'command failed as expected, but status differs' \ 84 | | fail 85 | fi 86 | } 87 | -------------------------------------------------------------------------------- /test/assert_regex.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | # 6 | # Literal matching 7 | # 8 | 9 | # Correctness 10 | @test "assert_regex() : succeeds if a substring matches extended regular expression " { 11 | run assert_regex 'abc' '^[a-z]b[c-z]+' 12 | assert_test_pass 13 | } 14 | 15 | @test "assert_regex() : fails if no substring matches extended regular expression " { 16 | run assert_regex 'bcd' '^[a-z]b[c-z]+' 17 | assert_test_fail <<'ERR_MSG' 18 | 19 | -- value does not match regular expression -- 20 | value : bcd 21 | pattern : ^[a-z]b[c-z]+ 22 | case : sensitive 23 | -- 24 | ERR_MSG 25 | } 26 | 27 | @test "assert_regex() : provides results in BASH_REMATCH" { 28 | unset -v BASH_REMATCH 29 | 30 | assert_regex 'abcd' 'b.d' 31 | declare -p BASH_REMATCH 32 | [ "${BASH_REMATCH[0]}" = 'bcd' ] 33 | } 34 | 35 | @test "assert_regex() : matches case-insensitively when 'nocasematch' is set" { 36 | shopt -s nocasematch 37 | 38 | assert_regex 'aBc' 'ABC' 39 | } 40 | 41 | @test "assert_regex() : outputs multi-line nicely when it fails" { 42 | run assert_regex $'bcd\n123' '^[a-z]b[c-z]+' 43 | assert_test_fail <<'ERR_MSG' 44 | 45 | -- value does not match regular expression -- 46 | value (2 lines): 47 | bcd 48 | 123 49 | pattern (1 lines): 50 | ^[a-z]b[c-z]+ 51 | case (1 lines): 52 | sensitive 53 | -- 54 | ERR_MSG 55 | 56 | shopt -s nocasematch 57 | run assert_regex $'bcd\n123' '^[a-z]b[c-z]+' 58 | assert_test_fail <<'ERR_MSG' 59 | 60 | -- value does not match regular expression -- 61 | value (2 lines): 62 | bcd 63 | 123 64 | pattern (1 lines): 65 | ^[a-z]b[c-z]+ 66 | case (1 lines): 67 | insensitive 68 | -- 69 | ERR_MSG 70 | } 71 | 72 | # Error handling 73 | @test "assert_regex() : returns 1 and displays an error message if is not a valid extended regular expression" { 74 | run assert_regex value '[.*' 75 | 76 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 77 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 78 | assert_test_fail < : fails if a substring matches extended regular expression " { 11 | run refute_regex 'abc' '^[a-z]b' 12 | assert_test_fail <<'ERR_MSG' 13 | 14 | -- value matches regular expression -- 15 | value : abc 16 | pattern : ^[a-z]b 17 | match : ab 18 | case : sensitive 19 | -- 20 | ERR_MSG 21 | } 22 | 23 | @test "refute_regex() : succeeds if no substring matches extended regular expression " { 24 | run refute_regex 'bcd' '^[a-z]b[c-z]+' 25 | assert_test_pass 26 | } 27 | 28 | @test "refute_regex() : provides results in BASH_REMATCH on failure" { 29 | unset -v BASH_REMATCH 30 | 31 | refute_regex 'abcd' 'b.d' \ 32 | || { 33 | declare -p BASH_REMATCH && \ 34 | [ "${BASH_REMATCH[0]}" = 'bcd' ] 35 | } 36 | } 37 | 38 | @test "refute_regex() : matches case-insensitively when 'nocasematch' is set" { 39 | shopt -s nocasematch 40 | 41 | run refute_regex 'aBc' 'ABC' 42 | assert_test_fail <<'ERR_MSG' 43 | 44 | -- value matches regular expression -- 45 | value : aBc 46 | pattern : ABC 47 | match : aBc 48 | case : insensitive 49 | -- 50 | ERR_MSG 51 | } 52 | 53 | @test "refute_regex() : outputs multi-line nicely when it fails" { 54 | run refute_regex $'abc\n123' '^[a-z]b[c-z]+' 55 | assert_test_fail <<'ERR_MSG' 56 | 57 | -- value matches regular expression -- 58 | value (2 lines): 59 | abc 60 | 123 61 | pattern (1 lines): 62 | ^[a-z]b[c-z]+ 63 | match (1 lines): 64 | abc 65 | case (1 lines): 66 | sensitive 67 | -- 68 | ERR_MSG 69 | 70 | shopt -s nocasematch 71 | run refute_regex $'aBc\n123' '^[a-z]b[c-z]+' 72 | assert_test_fail <<'ERR_MSG' 73 | 74 | -- value matches regular expression -- 75 | value (2 lines): 76 | aBc 77 | 123 78 | pattern (1 lines): 79 | ^[a-z]b[c-z]+ 80 | match (1 lines): 81 | aBc 82 | case (1 lines): 83 | insensitive 84 | -- 85 | ERR_MSG 86 | } 87 | 88 | # Error handling 89 | @test "refute_regex() : returns 1 and displays an error message if is not a valid extended regular expression" { 90 | run refute_regex value '[.*' 91 | 92 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 93 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 94 | assert_test_fail <: returns 0 if does not equal \`\$output'" { 12 | run echo 'b' 13 | run refute_output 'a' 14 | assert_test_pass 15 | } 16 | 17 | @test "refute_output() : returns 1 and displays details if equals \`\$output'" { 18 | run echo 'a' 19 | run refute_output 'a' 20 | 21 | assert_test_fail <<'ERR_MSG' 22 | 23 | -- output equals, but it was expected to differ -- 24 | output : a 25 | -- 26 | ERR_MSG 27 | } 28 | 29 | @test 'refute_output(): succeeds if output is empty' { 30 | run echo '' 31 | run refute_output 32 | 33 | assert_test_pass 34 | } 35 | 36 | @test 'refute_output(): fails if output is non-empty' { 37 | run echo 'a' 38 | run refute_output 39 | 40 | assert_test_fail <<'ERR_MSG' 41 | 42 | -- output non-empty, but expected no output -- 43 | output : a 44 | -- 45 | ERR_MSG 46 | } 47 | 48 | @test 'refute_output() - : reads from STDIN' { 49 | run echo '-' 50 | run refute_output - < from STDIN' { 58 | run echo '--stdin' 59 | run refute_output --stdin <: displays details in multi-line format if necessary' { 68 | run printf 'a 0\na 1' 69 | run refute_output $'a 0\na 1' 70 | 71 | assert_test_fail <<'ERR_MSG' 72 | 73 | -- output equals, but it was expected to differ -- 74 | output (2 lines): 75 | a 0 76 | a 1 77 | -- 78 | ERR_MSG 79 | } 80 | 81 | # Options 82 | @test 'refute_output() : performs literal matching by default' { 83 | run echo 'a' 84 | run refute_output '*' 85 | assert_test_pass 86 | } 87 | 88 | 89 | # 90 | # Partial matching: `-p' and `--partial' 91 | # 92 | 93 | # Options 94 | @test 'refute_output() -p : enables partial matching' { 95 | run echo 'abc' 96 | run refute_output -p 'd' 97 | assert_test_pass 98 | } 99 | 100 | @test 'refute_output() --partial : enables partial matching' { 101 | run echo 'abc' 102 | run refute_output --partial 'd' 103 | assert_test_pass 104 | } 105 | 106 | # Correctness 107 | @test "refute_output() --partial : returns 0 if is not a substring in \`\$output'" { 108 | run printf 'a\nb\nc' 109 | run refute_output --partial 'd' 110 | assert_test_pass 111 | } 112 | 113 | @test "refute_output() --partial : returns 1 and displays details if is a substring in \`\$output'" { 114 | run echo 'a' 115 | run refute_output --partial 'a' 116 | 117 | assert_test_fail <<'ERR_MSG' 118 | 119 | -- output should not contain substring -- 120 | substring : a 121 | output : a 122 | -- 123 | ERR_MSG 124 | } 125 | 126 | # Output formatting 127 | @test 'refute_output() --partial : displays details in multi-line format if necessary' { 128 | run printf 'a 0\na 1' 129 | run refute_output --partial 'a' 130 | 131 | assert_test_fail <<'ERR_MSG' 132 | 133 | -- output should not contain substring -- 134 | substring (1 lines): 135 | a 136 | output (2 lines): 137 | a 0 138 | a 1 139 | -- 140 | ERR_MSG 141 | } 142 | 143 | 144 | # 145 | # Regular expression matching: `-e' and `--regexp' 146 | # 147 | 148 | # Options 149 | @test 'refute_output() -e : enables regular expression matching' { 150 | run echo 'abc' 151 | run refute_output -e '^d' 152 | assert_test_pass 153 | } 154 | 155 | @test 'refute_output() --regexp : enables regular expression matching' { 156 | run echo 'abc' 157 | run refute_output --regexp '^d' 158 | assert_test_pass 159 | } 160 | 161 | # Correctness 162 | @test "refute_output() --regexp : returns 0 if does not match \`\$output'" { 163 | run printf 'a\nb\nc' 164 | run refute_output --regexp '.*d.*' 165 | assert_test_pass 166 | } 167 | 168 | @test "refute_output() --regexp : returns 1 and displays details if matches \`\$output'" { 169 | run echo 'a' 170 | run refute_output --regexp '.*a.*' 171 | 172 | assert_test_fail <<'ERR_MSG' 173 | 174 | -- regular expression should not match output -- 175 | regexp : .*a.* 176 | output : a 177 | -- 178 | ERR_MSG 179 | } 180 | 181 | # Output formatting 182 | @test 'refute_output() --regexp : displays details in multi-line format if necessary' { 183 | run printf 'a 0\na 1' 184 | run refute_output --regexp '.*a.*' 185 | 186 | assert_test_fail <<'ERR_MSG' 187 | 188 | -- regular expression should not match output -- 189 | regexp (1 lines): 190 | .*a.* 191 | output (2 lines): 192 | a 0 193 | a 1 194 | -- 195 | ERR_MSG 196 | } 197 | 198 | # Error handling 199 | @test 'refute_output() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 200 | run refute_output --regexp '[.*' 201 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 202 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 203 | assert_test_fail <&2 11 | } 12 | 13 | printf_err() { 14 | # shellcheck disable=2059 15 | printf "$@" >&2 16 | } 17 | 18 | # 19 | # Literal matching 20 | # 21 | 22 | # Correctness 23 | @test "refute_stderr() : returns 0 if does not equal \`\$stderr'" { 24 | run --separate-stderr echo_err 'b' 25 | run refute_stderr 'a' 26 | assert_test_pass 27 | } 28 | 29 | @test "refute_stderr() : returns 1 and displays details if equals \`\$stderr'" { 30 | run --separate-stderr echo_err 'a' 31 | run refute_stderr 'a' 32 | 33 | assert_test_fail <<'ERR_MSG' 34 | 35 | -- stderr equals, but it was expected to differ -- 36 | stderr : a 37 | -- 38 | ERR_MSG 39 | } 40 | 41 | @test 'refute_stderr(): succeeds if stderr is empty' { 42 | run --separate-stderr echo_err '' 43 | run refute_stderr 44 | 45 | assert_test_pass 46 | } 47 | 48 | @test 'refute_stderr(): fails if stderr is non-empty' { 49 | run --separate-stderr echo_err 'a' 50 | run refute_stderr 51 | 52 | assert_test_fail <<'ERR_MSG' 53 | 54 | -- stderr non-empty, but expected no stderr -- 55 | stderr : a 56 | -- 57 | ERR_MSG 58 | } 59 | 60 | @test 'refute_stderr() - : reads from STDIN' { 61 | run --separate-stderr echo_err '-' 62 | run refute_stderr - < from STDIN' { 70 | run --separate-stderr echo_err '--stdin' 71 | run refute_stderr --stdin <: displays details in multi-line format if necessary' { 80 | run --separate-stderr printf_err 'a 0\na 1' 81 | run refute_stderr $'a 0\na 1' 82 | 83 | assert_test_fail <<'ERR_MSG' 84 | 85 | -- stderr equals, but it was expected to differ -- 86 | stderr (2 lines): 87 | a 0 88 | a 1 89 | -- 90 | ERR_MSG 91 | } 92 | 93 | # Options 94 | @test 'refute_stderr() : performs literal matching by default' { 95 | run --separate-stderr echo_err 'a' 96 | run refute_stderr '*' 97 | assert_test_pass 98 | } 99 | 100 | 101 | # 102 | # Partial matching: `-p' and `--partial' 103 | # 104 | 105 | # Options 106 | @test 'refute_stderr() -p : enables partial matching' { 107 | run --separate-stderr echo_err 'abc' 108 | run refute_stderr -p 'd' 109 | assert_test_pass 110 | } 111 | 112 | @test 'refute_stderr() --partial : enables partial matching' { 113 | run --separate-stderr echo_err 'abc' 114 | run refute_stderr --partial 'd' 115 | assert_test_pass 116 | } 117 | 118 | # Correctness 119 | @test "refute_stderr() --partial : returns 0 if is not a substring in \`\$stderr'" { 120 | run --separate-stderr printf_err 'a\nb\nc' 121 | run refute_stderr --partial 'd' 122 | assert_test_pass 123 | } 124 | 125 | @test "refute_stderr() --partial : returns 1 and displays details if is a substring in \`\$stderr'" { 126 | run --separate-stderr echo_err 'a' 127 | run refute_stderr --partial 'a' 128 | 129 | assert_test_fail <<'ERR_MSG' 130 | 131 | -- stderr should not contain substring -- 132 | substring : a 133 | stderr : a 134 | -- 135 | ERR_MSG 136 | } 137 | 138 | # Output formatting 139 | @test 'refute_stderr() --partial : displays details in multi-line format if necessary' { 140 | run --separate-stderr printf_err 'a 0\na 1' 141 | run refute_stderr --partial 'a' 142 | 143 | assert_test_fail <<'ERR_MSG' 144 | 145 | -- stderr should not contain substring -- 146 | substring (1 lines): 147 | a 148 | stderr (2 lines): 149 | a 0 150 | a 1 151 | -- 152 | ERR_MSG 153 | } 154 | 155 | 156 | # 157 | # Regular expression matching: `-e' and `--regexp' 158 | # 159 | 160 | # Options 161 | @test 'refute_stderr() -e : enables regular expression matching' { 162 | run --separate-stderr echo_err 'abc^d' 163 | run refute_stderr -e '^d' 164 | assert_test_pass 165 | } 166 | 167 | @test 'refute_stderr() --regexp : enables regular expression matching' { 168 | run --separate-stderr echo_err 'abc' 169 | run refute_stderr --regexp '^d' 170 | assert_test_pass 171 | } 172 | 173 | # Correctness 174 | @test "refute_stderr() --regexp : returns 0 if does not match \`\$stderr'" { 175 | run --separate-stderr printf_err 'a\nb\nc' 176 | run refute_stderr --regexp '.*d.*' 177 | assert_test_pass 178 | } 179 | 180 | @test "refute_stderr() --regexp : returns 1 and displays details if matches \`\$stderr'" { 181 | run --separate-stderr echo_err 'a' 182 | run refute_stderr --regexp '.*a.*' 183 | 184 | assert_test_fail <<'ERR_MSG' 185 | 186 | -- regular expression should not match stderr -- 187 | regexp : .*a.* 188 | stderr : a 189 | -- 190 | ERR_MSG 191 | } 192 | 193 | # Output formatting 194 | @test 'refute_stderr() --regexp : displays details in multi-line format if necessary' { 195 | run --separate-stderr printf_err 'a 0\na 1' 196 | run refute_stderr --regexp '.*a.*' 197 | 198 | assert_test_fail <<'ERR_MSG' 199 | 200 | -- regular expression should not match stderr -- 201 | regexp (1 lines): 202 | .*a.* 203 | stderr (2 lines): 204 | a 0 205 | a 1 206 | -- 207 | ERR_MSG 208 | } 209 | 210 | # Error handling 211 | @test 'refute_stderr() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 212 | run refute_stderr --regexp '[.*' 213 | 214 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 215 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 216 | assert_test_fail < 117 | -------------------------------------------------------------------------------- /test/assert_output.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | # 6 | # Literal matching 7 | # 8 | 9 | # Correctness 10 | @test "assert_output() : returns 0 if equals \`\$output'" { 11 | run echo 'a' 12 | run assert_output 'a' 13 | assert_test_pass 14 | } 15 | 16 | @test "assert_output() : returns 1 and displays details if does not equal \`\$output'" { 17 | run echo 'b' 18 | run assert_output 'a' 19 | 20 | assert_test_fail <<'ERR_MSG' 21 | 22 | -- output differs -- 23 | expected : a 24 | actual : b 25 | -- 26 | ERR_MSG 27 | } 28 | 29 | @test 'assert_output(): succeeds if output is non-empty' { 30 | run echo 'a' 31 | run assert_output 32 | 33 | assert_test_pass 34 | } 35 | 36 | @test 'assert_output(): fails if output is empty' { 37 | run echo '' 38 | run assert_output 39 | 40 | assert_test_fail <<'ERR_MSG' 41 | 42 | -- no output -- 43 | expected non-empty output, but output was empty 44 | -- 45 | ERR_MSG 46 | } 47 | 48 | @test 'assert_output() - : reads from STDIN' { 49 | run echo 'a' 50 | run assert_output - < from STDIN' { 58 | run echo 'a' 59 | run assert_output --stdin <: displays details in multi-line format if \`\$output' is longer than one line" { 68 | run printf 'b 0\nb 1' 69 | run assert_output 'a' 70 | 71 | assert_test_fail <<'ERR_MSG' 72 | 73 | -- output differs -- 74 | expected (1 lines): 75 | a 76 | actual (2 lines): 77 | b 0 78 | b 1 79 | -- 80 | ERR_MSG 81 | } 82 | 83 | @test 'assert_output() : displays details in multi-line format if is longer than one line' { 84 | run echo 'b' 85 | run assert_output $'a 0\na 1' 86 | 87 | assert_test_fail <<'ERR_MSG' 88 | 89 | -- output differs -- 90 | expected (2 lines): 91 | a 0 92 | a 1 93 | actual (1 lines): 94 | b 95 | -- 96 | ERR_MSG 97 | } 98 | 99 | # Options 100 | @test 'assert_output() : performs literal matching by default' { 101 | run echo 'a' 102 | run assert_output '*' 103 | 104 | assert_test_fail <<'ERR_MSG' 105 | 106 | -- output differs -- 107 | expected : * 108 | actual : a 109 | -- 110 | ERR_MSG 111 | } 112 | 113 | 114 | # 115 | # Partial matching: `-p' and `--partial' 116 | # 117 | 118 | @test 'assert_output() -p : enables partial matching' { 119 | run echo 'abc' 120 | run assert_output -p 'b' 121 | assert_test_pass 122 | } 123 | 124 | @test 'assert_output() --partial : enables partial matching' { 125 | run echo 'abc' 126 | run assert_output --partial 'b' 127 | assert_test_pass 128 | } 129 | 130 | # Correctness 131 | @test "assert_output() --partial : returns 0 if is a substring in \`\$output'" { 132 | run printf 'a\nb\nc' 133 | run assert_output --partial 'b' 134 | assert_test_pass 135 | } 136 | 137 | @test "assert_output() --partial : returns 1 and displays details if is not a substring in \`\$output'" { 138 | run echo 'b' 139 | run assert_output --partial 'a' 140 | 141 | assert_test_fail <<'ERR_MSG' 142 | 143 | -- output does not contain substring -- 144 | substring : a 145 | output : b 146 | -- 147 | ERR_MSG 148 | } 149 | 150 | # Output formatting 151 | @test "assert_output() --partial : displays details in multi-line format if \`\$output' is longer than one line" { 152 | run printf 'b 0\nb 1' 153 | run assert_output --partial 'a' 154 | 155 | assert_test_fail <<'ERR_MSG' 156 | 157 | -- output does not contain substring -- 158 | substring (1 lines): 159 | a 160 | output (2 lines): 161 | b 0 162 | b 1 163 | -- 164 | ERR_MSG 165 | } 166 | 167 | @test 'assert_output() --partial : displays details in multi-line format if is longer than one line' { 168 | run echo 'b' 169 | run assert_output --partial $'a 0\na 1' 170 | 171 | assert_test_fail <<'ERR_MSG' 172 | 173 | -- output does not contain substring -- 174 | substring (2 lines): 175 | a 0 176 | a 1 177 | output (1 lines): 178 | b 179 | -- 180 | ERR_MSG 181 | } 182 | 183 | 184 | # 185 | # Regular expression matching: `-e' and `--regexp' 186 | # 187 | 188 | @test 'assert_output() -e : enables regular expression matching' { 189 | run echo 'abc' 190 | run assert_output -e '^a' 191 | assert_test_pass 192 | } 193 | 194 | @test 'assert_output() --regexp : enables regular expression matching' { 195 | run echo 'abc' 196 | run assert_output --regexp '^a' 197 | assert_test_pass 198 | } 199 | 200 | # Correctness 201 | @test "assert_output() --regexp : returns 0 if matches \`\$output'" { 202 | run printf 'a\nb\nc' 203 | run assert_output --regexp '.*b.*' 204 | assert_test_pass 205 | } 206 | 207 | @test "assert_output() --regexp : returns 1 and displays details if does not match \`\$output'" { 208 | run echo 'b' 209 | run assert_output --regexp '.*a.*' 210 | 211 | assert_test_fail <<'ERR_MSG' 212 | 213 | -- regular expression does not match output -- 214 | regexp : .*a.* 215 | output : b 216 | -- 217 | ERR_MSG 218 | } 219 | 220 | # Output formatting 221 | @test "assert_output() --regexp : displays details in multi-line format if \`\$output' is longer than one line" { 222 | run printf 'b 0\nb 1' 223 | run assert_output --regexp '.*a.*' 224 | 225 | assert_test_fail <<'ERR_MSG' 226 | 227 | -- regular expression does not match output -- 228 | regexp (1 lines): 229 | .*a.* 230 | output (2 lines): 231 | b 0 232 | b 1 233 | -- 234 | ERR_MSG 235 | } 236 | 237 | @test 'assert_output() --regexp : displays details in multi-line format if is longer than one line' { 238 | run echo 'b' 239 | run assert_output --regexp $'.*a\nb.*' 240 | 241 | assert_test_fail <<'ERR_MSG' 242 | 243 | -- regular expression does not match output -- 244 | regexp (2 lines): 245 | .*a 246 | b.* 247 | output (1 lines): 248 | b 249 | -- 250 | ERR_MSG 251 | } 252 | 253 | # Error handling 254 | @test 'assert_output() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 255 | run assert_output --regexp '[.*' 256 | 257 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 258 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 259 | assert_test_fail <] 7 | # 8 | # Options: 9 | # -p, --partial Match if `unexpected` is a substring of `$output` 10 | # -e, --regexp Treat `unexpected` as an extended regular expression 11 | # -, --stdin Read `unexpected` value from STDIN 12 | # The unexpected value, substring, or regular expression 13 | # 14 | # IO: 15 | # STDIN - [=$1] unexpected output 16 | # STDERR - details, on failure 17 | # error message, on error 18 | # Globals: 19 | # output 20 | # Returns: 21 | # 0 - if output matches the unexpected value/partial/regexp 22 | # 1 - otherwise 23 | # 24 | # This function verifies that a command or function does not produce the unexpected output. 25 | # (It is the logical complement of `assert_output`.) 26 | # Output matching can be literal (the default), partial or by regular expression. 27 | # The unexpected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. 28 | # 29 | # ## Literal matching 30 | # 31 | # By default, literal matching is performed. 32 | # The assertion fails if `$output` equals the unexpected output. 33 | # 34 | # ```bash 35 | # @test 'refute_output()' { 36 | # run echo 'want' 37 | # refute_output 'want' 38 | # } 39 | # 40 | # @test 'refute_output() with pipe' { 41 | # run echo 'hello' 42 | # echo 'world' | refute_output - 43 | # } 44 | # 45 | # @test 'refute_output() with herestring' { 46 | # run echo 'hello' 47 | # refute_output - <<< world 48 | # } 49 | # ``` 50 | # 51 | # On failure, the output is displayed. 52 | # 53 | # ``` 54 | # -- output equals, but it was expected to differ -- 55 | # output : want 56 | # -- 57 | # ``` 58 | # 59 | # ## Existence 60 | # 61 | # To assert that there is no output at all, omit the matching argument. 62 | # 63 | # ```bash 64 | # @test 'refute_output()' { 65 | # run foo --silent 66 | # refute_output 67 | # } 68 | # ``` 69 | # 70 | # On failure, an error message is displayed. 71 | # 72 | # ``` 73 | # -- unexpected output -- 74 | # expected no output, but output was non-empty 75 | # -- 76 | # ``` 77 | # 78 | # ## Partial matching 79 | # 80 | # Partial matching can be enabled with the `--partial` option (`-p` for short). 81 | # When used, the assertion fails if the unexpected _substring_ is found in `$output`. 82 | # 83 | # ```bash 84 | # @test 'refute_output() partial matching' { 85 | # run echo 'ERROR: no such file or directory' 86 | # refute_output --partial 'ERROR' 87 | # } 88 | # ``` 89 | # 90 | # On failure, the substring and the output are displayed. 91 | # 92 | # ``` 93 | # -- output should not contain substring -- 94 | # substring : ERROR 95 | # output : ERROR: no such file or directory 96 | # -- 97 | # ``` 98 | # 99 | # ## Regular expression matching 100 | # 101 | # Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 102 | # When used, the assertion fails if the *extended regular expression* matches `$output`. 103 | # 104 | # *__Note__: 105 | # The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output; 106 | # not individual lines.* 107 | # 108 | # ```bash 109 | # @test 'refute_output() regular expression matching' { 110 | # run echo 'Foobar v0.1.0' 111 | # refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 112 | # } 113 | # ``` 114 | # 115 | # On failure, the regular expression and the output are displayed. 116 | # 117 | # ``` 118 | # -- regular expression should not match output -- 119 | # regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 120 | # output : Foobar v0.1.0 121 | # -- 122 | # ``` 123 | refute_output() { 124 | __refute_stream "$@" 125 | } 126 | 127 | # refute_stderr 128 | # ============= 129 | # 130 | # Summary: Fail if `$stderr' matches the unexpected output. 131 | # 132 | # Usage: refute_stderr [-p | -e] [- | [--] ] 133 | # 134 | # Options: 135 | # -p, --partial Match if `unexpected` is a substring of `$stderr` 136 | # -e, --regexp Treat `unexpected` as an extended regular expression 137 | # -, --stdin Read `unexpected` value from STDIN 138 | # The unexpected value, substring, or regular expression 139 | # 140 | # IO: 141 | # STDIN - [=$1] unexpected stderr 142 | # STDERR - details, on failure 143 | # error message, on error 144 | # Globals: 145 | # stderr 146 | # Returns: 147 | # 0 - if stderr matches the unexpected value/partial/regexp 148 | # 1 - otherwise 149 | # 150 | # Similar to `refute_output`, this function verifies that a command or function does not produce the unexpected stderr. 151 | # (It is the logical complement of `assert_stderr`.) 152 | # The stderr matching can be literal (the default), partial or by regular expression. 153 | # The unexpected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. 154 | # 155 | refute_stderr() { 156 | __refute_stream "$@" 157 | } 158 | 159 | __refute_stream() { 160 | local -r caller=${FUNCNAME[1]} 161 | local -r stream_type=${caller/refute_/} 162 | local -i is_mode_partial=0 163 | local -i is_mode_regexp=0 164 | local -i is_mode_empty=0 165 | local -i use_stdin=0 166 | 167 | if [[ ${stream_type} == "output" ]]; then 168 | : "${output?}" 169 | elif [[ ${stream_type} == "stderr" ]]; then 170 | : "${stderr?}" 171 | else 172 | # Not reachable: should be either output or stderr 173 | : 174 | fi 175 | local -r stream="${!stream_type}" 176 | 177 | # Handle options. 178 | if (( $# == 0 )); then 179 | is_mode_empty=1 180 | fi 181 | 182 | while (( $# > 0 )); do 183 | case "$1" in 184 | -p|--partial) is_mode_partial=1; shift ;; 185 | -e|--regexp) is_mode_regexp=1; shift ;; 186 | -|--stdin) use_stdin=1; shift ;; 187 | --) shift; break ;; 188 | *) break ;; 189 | esac 190 | done 191 | 192 | if (( is_mode_partial )) && (( is_mode_regexp )); then 193 | echo "\`--partial' and \`--regexp' are mutually exclusive" \ 194 | | batslib_decorate "ERROR: ${caller}" \ 195 | | fail 196 | return $? 197 | fi 198 | 199 | # Arguments. 200 | local unexpected 201 | if (( use_stdin )); then 202 | unexpected="$(cat -)" 203 | else 204 | unexpected="${1-}" 205 | fi 206 | 207 | if (( is_mode_regexp == 1 )); then 208 | __check_is_valid_regex "$unexpected" "$caller" || return 1 209 | fi 210 | 211 | # Matching. 212 | if (( is_mode_empty )); then 213 | if [ -n "${stream}" ]; then 214 | batslib_print_kv_single_or_multi 6 \ 215 | "${stream_type}" "${stream}" \ 216 | | batslib_decorate "${stream_type} non-empty, but expected no ${stream_type}" \ 217 | | fail 218 | fi 219 | elif (( is_mode_regexp )); then 220 | if [[ ${stream} =~ $unexpected ]]; then 221 | batslib_print_kv_single_or_multi 6 \ 222 | 'regexp' "$unexpected" \ 223 | "${stream_type}" "${stream}" \ 224 | | batslib_decorate "regular expression should not match ${stream_type}" \ 225 | | fail 226 | fi 227 | elif (( is_mode_partial )); then 228 | if [[ ${stream} == *"$unexpected"* ]]; then 229 | batslib_print_kv_single_or_multi 9 \ 230 | 'substring' "$unexpected" \ 231 | "${stream_type}" "${stream}" \ 232 | | batslib_decorate "${stream_type} should not contain substring" \ 233 | | fail 234 | fi 235 | else 236 | if [[ ${stream} == "$unexpected" ]]; then 237 | batslib_print_kv_single_or_multi 6 \ 238 | "${stream_type}" "${stream}" \ 239 | | batslib_decorate "${stream_type} equals, but it was expected to differ" \ 240 | | fail 241 | fi 242 | fi 243 | } 244 | -------------------------------------------------------------------------------- /src/assert_output.bash: -------------------------------------------------------------------------------- 1 | # assert_output 2 | # ============= 3 | # 4 | # Summary: Fail if `$output' does not match the expected output. 5 | # 6 | # Usage: assert_output [-p | -e] [- | [--] ] 7 | # 8 | # Options: 9 | # -p, --partial Match if `expected` is a substring of `$output` 10 | # -e, --regexp Treat `expected` as an extended regular expression 11 | # -, --stdin Read `expected` value from STDIN 12 | # The expected value, substring or regular expression 13 | # 14 | # IO: 15 | # STDIN - [=$1] expected output 16 | # STDERR - details, on failure 17 | # error message, on error 18 | # Globals: 19 | # output 20 | # Returns: 21 | # 0 - if output matches the expected value/partial/regexp 22 | # 1 - otherwise 23 | # 24 | # This function verifies that a command or function produces the expected output. 25 | # (It is the logical complement of `refute_output`.) 26 | # Output matching can be literal (the default), partial or by regular expression. 27 | # The expected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. 28 | # 29 | # ## Literal matching 30 | # 31 | # By default, literal matching is performed. 32 | # The assertion fails if `$output` does not equal the expected output. 33 | # 34 | # ```bash 35 | # @test 'assert_output()' { 36 | # run echo 'have' 37 | # assert_output 'want' 38 | # } 39 | # 40 | # @test 'assert_output() with pipe' { 41 | # run echo 'hello' 42 | # echo 'hello' | assert_output - 43 | # } 44 | # 45 | # @test 'assert_output() with herestring' { 46 | # run echo 'hello' 47 | # assert_output - <<< hello 48 | # } 49 | # ``` 50 | # 51 | # On failure, the expected and actual output are displayed. 52 | # 53 | # ``` 54 | # -- output differs -- 55 | # expected : want 56 | # actual : have 57 | # -- 58 | # ``` 59 | # 60 | # ## Existence 61 | # 62 | # To assert that any output exists at all, omit the `expected` argument. 63 | # 64 | # ```bash 65 | # @test 'assert_output()' { 66 | # run echo 'have' 67 | # assert_output 68 | # } 69 | # ``` 70 | # 71 | # On failure, an error message is displayed. 72 | # 73 | # ``` 74 | # -- no output -- 75 | # expected non-empty output, but output was empty 76 | # -- 77 | # ``` 78 | # 79 | # ## Partial matching 80 | # 81 | # Partial matching can be enabled with the `--partial` option (`-p` for short). 82 | # When used, the assertion fails if the expected _substring_ is not found in `$output`. 83 | # 84 | # ```bash 85 | # @test 'assert_output() partial matching' { 86 | # run echo 'ERROR: no such file or directory' 87 | # assert_output --partial 'SUCCESS' 88 | # } 89 | # ``` 90 | # 91 | # On failure, the substring and the output are displayed. 92 | # 93 | # ``` 94 | # -- output does not contain substring -- 95 | # substring : SUCCESS 96 | # output : ERROR: no such file or directory 97 | # -- 98 | # ``` 99 | # 100 | # ## Regular expression matching 101 | # 102 | # Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 103 | # When used, the assertion fails if the *extended regular expression* does not match `$output`. 104 | # 105 | # *__Note__: 106 | # The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output; 107 | # not individual lines.* 108 | # 109 | # ```bash 110 | # @test 'assert_output() regular expression matching' { 111 | # run echo 'Foobar 0.1.0' 112 | # assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 113 | # } 114 | # ``` 115 | # 116 | # On failure, the regular expression and the output are displayed. 117 | # 118 | # ``` 119 | # -- regular expression does not match output -- 120 | # regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 121 | # output : Foobar 0.1.0 122 | # -- 123 | # ``` 124 | assert_output() { 125 | __assert_stream "$@" 126 | } 127 | 128 | # assert_stderr 129 | # ============= 130 | # 131 | # Summary: Fail if `$stderr' does not match the expected stderr. 132 | # 133 | # Usage: assert_stderr [-p | -e] [- | [--] ] 134 | # 135 | # Options: 136 | # -p, --partial Match if `expected` is a substring of `$stderr` 137 | # -e, --regexp Treat `expected` as an extended regular expression 138 | # -, --stdin Read `expected` value from STDIN 139 | # The expected value, substring or regular expression 140 | # 141 | # IO: 142 | # STDIN - [=$1] expected stderr 143 | # STDERR - details, on failure 144 | # error message, on error 145 | # Globals: 146 | # stderr 147 | # Returns: 148 | # 0 - if stderr matches the expected value/partial/regexp 149 | # 1 - otherwise 150 | # 151 | # Similarly to `assert_output`, this function verifies that a command or function produces the expected stderr. 152 | # (It is the logical complement of `refute_stderr`.) 153 | # The stderr matching can be literal (the default), partial or by regular expression. 154 | # The expected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. 155 | # 156 | assert_stderr() { 157 | __assert_stream "$@" 158 | } 159 | 160 | __assert_stream() { 161 | local -r caller=${FUNCNAME[1]} 162 | local -r stream_type=${caller/assert_/} 163 | local -i is_mode_partial=0 164 | local -i is_mode_regexp=0 165 | local -i is_mode_nonempty=0 166 | local -i use_stdin=0 167 | 168 | if [[ ${stream_type} == "output" ]]; then 169 | : "${output?}" 170 | elif [[ ${stream_type} == "stderr" ]]; then 171 | : "${stderr?}" 172 | else 173 | # Unknown caller 174 | echo "Unexpected call to \`${FUNCNAME[0]}\` 175 | Did you mean to call \`assert_output\` or \`assert_stderr\`?" | 176 | batslib_decorate "ERROR: ${FUNCNAME[0]}" | 177 | fail 178 | return $? 179 | fi 180 | local -r stream="${!stream_type}" 181 | 182 | # Handle options. 183 | if (( $# == 0 )); then 184 | is_mode_nonempty=1 185 | fi 186 | 187 | while (( $# > 0 )); do 188 | case "$1" in 189 | -p|--partial) is_mode_partial=1; shift ;; 190 | -e|--regexp) is_mode_regexp=1; shift ;; 191 | -|--stdin) use_stdin=1; shift ;; 192 | --) shift; break ;; 193 | *) break ;; 194 | esac 195 | done 196 | 197 | if (( is_mode_partial )) && (( is_mode_regexp )); then 198 | echo "\`--partial' and \`--regexp' are mutually exclusive" \ 199 | | batslib_decorate "ERROR: ${caller}" \ 200 | | fail 201 | return $? 202 | fi 203 | 204 | # Arguments. 205 | local expected 206 | if (( use_stdin )); then 207 | expected="$(cat -)" 208 | else 209 | expected="${1-}" 210 | fi 211 | 212 | # Matching. 213 | if (( is_mode_nonempty )); then 214 | if [ -z "$stream" ]; then 215 | echo "expected non-empty $stream_type, but $stream_type was empty" \ 216 | | batslib_decorate "no $stream_type" \ 217 | | fail 218 | fi 219 | elif (( is_mode_regexp )); then 220 | # shellcheck disable=2319 221 | if ! __check_is_valid_regex "$expected" "$caller"; then 222 | return 1 223 | elif ! [[ $stream =~ $expected ]]; then 224 | batslib_print_kv_single_or_multi 6 \ 225 | 'regexp' "$expected" \ 226 | "$stream_type" "$stream" \ 227 | | batslib_decorate "regular expression does not match $stream_type" \ 228 | | fail 229 | fi 230 | elif (( is_mode_partial )); then 231 | if [[ $stream != *"$expected"* ]]; then 232 | batslib_print_kv_single_or_multi 9 \ 233 | 'substring' "$expected" \ 234 | "$stream_type" "$stream" \ 235 | | batslib_decorate "$stream_type does not contain substring" \ 236 | | fail 237 | fi 238 | else 239 | if [[ $stream != "$expected" ]]; then 240 | batslib_print_kv_single_or_multi 8 \ 241 | 'expected' "$expected" \ 242 | 'actual' "$stream" \ 243 | | batslib_decorate "$stream_type differs" \ 244 | | fail 245 | fi 246 | fi 247 | } 248 | -------------------------------------------------------------------------------- /test/assert_stderr.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | setup_file() { 6 | bats_require_minimum_version 1.5.0 7 | } 8 | 9 | echo_err() { 10 | echo "$@" >&2 11 | } 12 | 13 | printf_err() { 14 | # shellcheck disable=2059 15 | printf "$@" >&2 16 | } 17 | 18 | # 19 | # Literal matching 20 | # 21 | 22 | # Correctness 23 | @test "assert_stderr() : returns 0 if equals \`\$stderr'" { 24 | run --separate-stderr echo_err 'a' 25 | run assert_stderr 'a' 26 | assert_test_pass 27 | } 28 | 29 | @test "assert_stderr() : returns 1 and displays details if does not equal \`\$stderr'" { 30 | run --separate-stderr echo_err 'b' 31 | run assert_stderr 'a' 32 | 33 | assert_test_fail <<'ERR_MSG' 34 | 35 | -- stderr differs -- 36 | expected : a 37 | actual : b 38 | -- 39 | ERR_MSG 40 | } 41 | 42 | @test 'assert_stderr(): succeeds if stderr is non-empty' { 43 | run --separate-stderr echo_err 'a' 44 | run assert_stderr 45 | 46 | assert_test_pass 47 | } 48 | 49 | @test 'assert_stderr(): fails if stderr is empty' { 50 | run --separate-stderr echo_err '' 51 | run assert_stderr 52 | 53 | assert_test_fail <<'ERR_MSG' 54 | 55 | -- no stderr -- 56 | expected non-empty stderr, but stderr was empty 57 | -- 58 | ERR_MSG 59 | } 60 | 61 | @test 'assert_stderr() - : reads from STDIN' { 62 | run --separate-stderr echo_err 'a' 63 | run assert_stderr - < from STDIN' { 71 | run --separate-stderr echo_err 'a' 72 | run assert_stderr --stdin <: displays details in multi-line format if \`\$stderr' is longer than one line" { 81 | run --separate-stderr printf_err 'b 0\nb 1' 82 | run assert_stderr 'a' 83 | 84 | assert_test_fail <<'ERR_MSG' 85 | 86 | -- stderr differs -- 87 | expected (1 lines): 88 | a 89 | actual (2 lines): 90 | b 0 91 | b 1 92 | -- 93 | ERR_MSG 94 | } 95 | 96 | @test 'assert_stderr() : displays details in multi-line format if is longer than one line' { 97 | run --separate-stderr echo_err 'b' 98 | run assert_stderr $'a 0\na 1' 99 | 100 | assert_test_fail <<'ERR_MSG' 101 | 102 | -- stderr differs -- 103 | expected (2 lines): 104 | a 0 105 | a 1 106 | actual (1 lines): 107 | b 108 | -- 109 | ERR_MSG 110 | } 111 | 112 | # Options 113 | @test 'assert_stderr() : performs literal matching by default' { 114 | run --separate-stderr echo_err 'a' 115 | run assert_stderr '*' 116 | 117 | assert_test_fail <<'ERR_MSG' 118 | 119 | -- stderr differs -- 120 | expected : * 121 | actual : a 122 | -- 123 | ERR_MSG 124 | } 125 | 126 | 127 | # 128 | # Partial matching: `-p' and `--partial' 129 | # 130 | 131 | @test 'assert_stderr() -p : enables partial matching' { 132 | run --separate-stderr echo_err 'abc' 133 | run assert_stderr -p 'b' 134 | assert_test_pass 135 | } 136 | 137 | @test 'assert_stderr() --partial : enables partial matching' { 138 | run --separate-stderr echo_err 'abc' 139 | run assert_stderr --partial 'b' 140 | assert_test_pass 141 | } 142 | 143 | # Correctness 144 | @test "assert_stderr() --partial : returns 0 if is a substring in \`\$stderr'" { 145 | run --separate-stderr printf_err 'a\nb\nc' 146 | run assert_stderr --partial 'b' 147 | assert_test_pass 148 | } 149 | 150 | @test "assert_stderr() --partial : returns 1 and displays details if is not a substring in \`\$stderr'" { 151 | run --separate-stderr echo_err 'b' 152 | run assert_stderr --partial 'a' 153 | 154 | assert_test_fail <<'ERR_MSG' 155 | 156 | -- stderr does not contain substring -- 157 | substring : a 158 | stderr : b 159 | -- 160 | ERR_MSG 161 | } 162 | 163 | # stderr formatting 164 | @test "assert_stderr() --partial : displays details in multi-line format if \`\$stderr' is longer than one line" { 165 | run --separate-stderr printf_err 'b 0\nb 1' 166 | run assert_stderr --partial 'a' 167 | 168 | assert_test_fail <<'ERR_MSG' 169 | 170 | -- stderr does not contain substring -- 171 | substring (1 lines): 172 | a 173 | stderr (2 lines): 174 | b 0 175 | b 1 176 | -- 177 | ERR_MSG 178 | } 179 | 180 | @test 'assert_stderr() --partial : displays details in multi-line format if is longer than one line' { 181 | run --separate-stderr echo_err 'b' 182 | run assert_stderr --partial $'a 0\na 1' 183 | 184 | assert_test_fail <<'ERR_MSG' 185 | 186 | -- stderr does not contain substring -- 187 | substring (2 lines): 188 | a 0 189 | a 1 190 | stderr (1 lines): 191 | b 192 | -- 193 | ERR_MSG 194 | } 195 | 196 | 197 | # 198 | # Regular expression matching: `-e' and `--regexp' 199 | # 200 | 201 | @test 'assert_stderr() -e : enables regular expression matching' { 202 | run --separate-stderr echo_err 'abc' 203 | run assert_stderr -e '^a' 204 | assert_test_pass 205 | } 206 | 207 | @test 'assert_stderr() --regexp : enables regular expression matching' { 208 | run --separate-stderr echo_err 'abc' 209 | run assert_stderr --regexp '^a' 210 | assert_test_pass 211 | } 212 | 213 | # Correctness 214 | @test "assert_stderr() --regexp : returns 0 if matches \`\$stderr'" { 215 | run --separate-stderr printf_err 'a\nb\nc' 216 | run assert_stderr --regexp '.*b.*' 217 | assert_test_pass 218 | } 219 | 220 | @test "assert_stderr() --regexp : returns 1 and displays details if does not match \`\$stderr'" { 221 | run --separate-stderr echo_err 'b' 222 | run assert_stderr --regexp '.*a.*' 223 | 224 | assert_test_fail <<'ERR_MSG' 225 | 226 | -- regular expression does not match stderr -- 227 | regexp : .*a.* 228 | stderr : b 229 | -- 230 | ERR_MSG 231 | } 232 | 233 | # stderr formatting 234 | @test "assert_stderr() --regexp : displays details in multi-line format if \`\$stderr' is longer than one line" { 235 | run --separate-stderr printf_err 'b 0\nb 1' 236 | run assert_stderr --regexp '.*a.*' 237 | 238 | assert_test_fail <<'ERR_MSG' 239 | 240 | -- regular expression does not match stderr -- 241 | regexp (1 lines): 242 | .*a.* 243 | stderr (2 lines): 244 | b 0 245 | b 1 246 | -- 247 | ERR_MSG 248 | } 249 | 250 | @test 'assert_stderr() --regexp : displays details in multi-line format if is longer than one line' { 251 | run --separate-stderr echo_err 'b' 252 | run assert_stderr --regexp $'.*a\nb.*' 253 | 254 | assert_test_fail <<'ERR_MSG' 255 | 256 | -- regular expression does not match stderr -- 257 | regexp (2 lines): 258 | .*a 259 | b.* 260 | stderr (1 lines): 261 | b 262 | -- 263 | ERR_MSG 264 | } 265 | 266 | # Error handling 267 | @test 'assert_stderr() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 268 | run assert_stderr --regexp '[.*' 269 | 270 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 271 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 272 | assert_test_fail <: returns 0 if is not a line in \`\${lines[@]}'" { 16 | run printf 'a\nb\nc' 17 | run refute_line 'd' 18 | assert_test_pass 19 | } 20 | 21 | @test "refute_line() : returns 1 and displays details if is not a line in \`\${lines[@]}'" { 22 | run echo 'a' 23 | run refute_line 'a' 24 | 25 | assert_test_fail <<'ERR_MSG' 26 | 27 | -- line should not be in output -- 28 | line : a 29 | index : 0 30 | output : a 31 | -- 32 | ERR_MSG 33 | } 34 | 35 | # Output formatting 36 | @test "refute_line() : displays \`\$output' in multi-line format if it is longer than one line" { 37 | run printf 'a 0\na 1\na 2' 38 | run refute_line 'a 1' 39 | 40 | assert_test_fail <<'ERR_MSG' 41 | 42 | -- line should not be in output -- 43 | line : a 1 44 | index : 1 45 | output (3 lines): 46 | a 0 47 | > a 1 48 | a 2 49 | -- 50 | ERR_MSG 51 | } 52 | 53 | # Options 54 | @test 'refute_line() : performs literal matching by default' { 55 | run echo 'a' 56 | run refute_line '*' 57 | assert_test_pass 58 | } 59 | 60 | 61 | # 62 | # Partial matching: `-p' and `--partial' 63 | # 64 | 65 | # Options 66 | @test 'refute_line() -p : enables partial matching' { 67 | run printf 'a\nb\nc' 68 | run refute_line -p 'd' 69 | assert_test_pass 70 | } 71 | 72 | @test 'refute_line() --partial : enables partial matching' { 73 | run printf 'a\nb\nc' 74 | run refute_line --partial 'd' 75 | assert_test_pass 76 | } 77 | 78 | # Correctness 79 | @test "refute_line() --partial : returns 0 if is not a substring in any line in \`\${lines[@]}'" { 80 | run printf 'a\nb\nc' 81 | run refute_line --partial 'd' 82 | assert_test_pass 83 | } 84 | 85 | @test "refute_line() --partial : returns 1 and displays details if is a substring in any line in \`\${lines[@]}'" { 86 | run echo 'a' 87 | run refute_line --partial 'a' 88 | 89 | assert_test_fail <<'ERR_MSG' 90 | 91 | -- no line should contain substring -- 92 | substring : a 93 | index : 0 94 | output : a 95 | -- 96 | ERR_MSG 97 | } 98 | 99 | # Output formatting 100 | @test "refute_line() --partial : displays \`\$output' in multi-line format if it is longer than one line" { 101 | run printf 'a\nabc\nc' 102 | run refute_line --partial 'b' 103 | 104 | assert_test_fail <<'ERR_MSG' 105 | 106 | -- no line should contain substring -- 107 | substring : b 108 | index : 1 109 | output (3 lines): 110 | a 111 | > abc 112 | c 113 | -- 114 | ERR_MSG 115 | } 116 | 117 | 118 | # 119 | # Regular expression matching: `-e' and `--regexp' 120 | # 121 | 122 | # Options 123 | @test 'refute_line() -e : enables regular expression matching' { 124 | run printf 'a\nb\nc' 125 | run refute_line -e '^.d' 126 | assert_test_pass 127 | } 128 | 129 | @test 'refute_line() --regexp : enables regular expression matching' { 130 | run printf 'a\nb\nc' 131 | run refute_line --regexp '^.d' 132 | assert_test_pass 133 | } 134 | 135 | # Correctness 136 | @test "refute_line() --regexp : returns 0 if does not match any line in \`\${lines[@]}'" { 137 | run printf 'a\nb\nc' 138 | run refute_line --regexp '.*d.*' 139 | assert_test_pass 140 | } 141 | 142 | @test "refute_line() --regexp : returns 1 and displays details if matches any lines in \`\${lines[@]}'" { 143 | run echo 'a' 144 | run refute_line --regexp '.*a.*' 145 | 146 | assert_test_fail <<'ERR_MSG' 147 | 148 | -- no line should match the regular expression -- 149 | regexp : .*a.* 150 | index : 0 151 | output : a 152 | -- 153 | ERR_MSG 154 | } 155 | 156 | # Output formatting 157 | @test "refute_line() --regexp : displays \`\$output' in multi-line format if longer than one line" { 158 | run printf 'a\nabc\nc' 159 | run refute_line --regexp '.*b.*' 160 | 161 | assert_test_fail <<'ERR_MSG' 162 | 163 | -- no line should match the regular expression -- 164 | regexp : .*b.* 165 | index : 1 166 | output (3 lines): 167 | a 168 | > abc 169 | c 170 | -- 171 | ERR_MSG 172 | } 173 | 174 | 175 | ############################################################################### 176 | # Matching single line: `-n' and `--index' 177 | ############################################################################### 178 | 179 | # Options 180 | @test 'refute_line() -n : matches against the -th line only' { 181 | run printf 'a\nb\nc' 182 | run refute_line -n 1 'd' 183 | assert_test_pass 184 | } 185 | 186 | @test 'refute_line() --index : matches against the -th line only' { 187 | run printf 'a\nb\nc' 188 | run refute_line --index 1 'd' 189 | assert_test_pass 190 | } 191 | 192 | @test 'refute_line() --index : returns 1 and displays an error message if is not an integer' { 193 | run refute_line --index 1a 194 | 195 | assert_test_fail <<'ERR_MSG' 196 | 197 | -- ERROR: refute_line -- 198 | `--index' requires an integer argument: `1a' 199 | -- 200 | ERR_MSG 201 | } 202 | 203 | 204 | # 205 | # Literal matching 206 | # 207 | 208 | # Correctness 209 | @test "refute_line() --index : returns 0 if does not equal \`\${lines[]}'" { 210 | run printf 'a\nb\nc' 211 | run refute_line --index 1 'd' 212 | assert_test_pass 213 | } 214 | 215 | @test "refute_line() --index : returns 1 and displays details if equals \`\${lines[]}'" { 216 | run printf 'a\nb\nc' 217 | run refute_line --index 1 'b' 218 | 219 | assert_test_fail <<'ERR_MSG' 220 | 221 | -- line should differ -- 222 | index : 1 223 | line : b 224 | -- 225 | ERR_MSG 226 | } 227 | 228 | # Options 229 | @test 'refute_line() --index : performs literal matching by default' { 230 | run printf 'a\nb\nc' 231 | run refute_line --index 1 '*' 232 | assert_test_pass 233 | } 234 | 235 | 236 | # 237 | # Partial matching: `-p' and `--partial' 238 | # 239 | 240 | # Options 241 | @test 'refute_line() --index -p : enables partial matching' { 242 | run printf 'a\nb\nc' 243 | run refute_line --index 1 -p 'd' 244 | assert_test_pass 245 | } 246 | 247 | @test 'refute_line() --index --partial : enables partial matching' { 248 | run printf 'a\nb\nc' 249 | run refute_line --index 1 --partial 'd' 250 | assert_test_pass 251 | } 252 | 253 | # Correctness 254 | @test "refute_line() --index --partial : returns 0 if is not a substring in \`\${lines[]}'" { 255 | run printf 'a\nabc\nc' 256 | run refute_line --index 1 --partial 'd' 257 | assert_test_pass 258 | } 259 | 260 | @test "refute_line() --index --partial : returns 1 and displays details if is a substring in \`\${lines[]}'" { 261 | run printf 'a\nabc\nc' 262 | run refute_line --index 1 --partial 'b' 263 | 264 | assert_test_fail <<'ERR_MSG' 265 | 266 | -- line should not contain substring -- 267 | index : 1 268 | substring : b 269 | line : abc 270 | -- 271 | ERR_MSG 272 | } 273 | 274 | 275 | # 276 | # Regular expression matching: `-e' and `--regexp' 277 | # 278 | 279 | # Options 280 | @test 'refute_line() --index -e : enables regular expression matching' { 281 | run printf 'a\nb\nc' 282 | run refute_line --index 1 -e '^.b' 283 | assert_test_pass 284 | } 285 | 286 | @test 'refute_line() --index --regexp : enables regular expression matching' { 287 | run printf 'a\nb\nc' 288 | run refute_line --index 1 --regexp '^.b' 289 | assert_test_pass 290 | } 291 | 292 | # Correctness 293 | @test "refute_line() --index --regexp : returns 0 if does not match \`\${lines[]}'" { 294 | run printf 'a\nabc\nc' 295 | run refute_line --index 1 --regexp '.*d.*' 296 | assert_test_pass 297 | } 298 | 299 | @test "refute_line() --index --regexp : returns 1 and displays details if matches \`\${lines[]}'" { 300 | run printf 'a\nabc\nc' 301 | run refute_line --index 1 --regexp '.*b.*' 302 | 303 | assert_test_fail <<'ERR_MSG' 304 | 305 | -- regular expression should not match line -- 306 | index : 1 307 | regexp : .*b.* 308 | line : abc 309 | -- 310 | ERR_MSG 311 | } 312 | 313 | 314 | ############################################################################### 315 | # Common 316 | ############################################################################### 317 | 318 | @test "refute_line(): \`--partial' and \`--regexp' are mutually exclusive" { 319 | run refute_line --partial --regexp 320 | 321 | assert_test_fail <<'ERR_MSG' 322 | 323 | -- ERROR: refute_line -- 324 | `--partial' and `--regexp' are mutually exclusive 325 | -- 326 | ERR_MSG 327 | } 328 | 329 | @test 'refute_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 330 | run refute_line --regexp '[.*' 331 | 332 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 333 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 334 | assert_test_fail <: returns 0 if is a line in \`\${lines[@]}'" { 16 | run printf 'a\nb\nc' 17 | run assert_line 'b' 18 | assert_test_pass 19 | } 20 | 21 | @test "assert_line() : returns 1 and displays details if is not a line in \`\${lines[@]}'" { 22 | run echo 'b' 23 | run assert_line 'a' 24 | 25 | assert_test_fail <<'ERR_MSG' 26 | 27 | -- output does not contain line -- 28 | line : a 29 | output : b 30 | -- 31 | ERR_MSG 32 | } 33 | 34 | # Output formatting 35 | @test "assert_line() : displays \`\$output' in multi-line format if it is longer than one line" { 36 | run printf 'b 0\nb 1' 37 | run assert_line 'a' 38 | 39 | assert_test_fail <<'ERR_MSG' 40 | 41 | -- output does not contain line -- 42 | line : a 43 | output (2 lines): 44 | b 0 45 | b 1 46 | -- 47 | ERR_MSG 48 | } 49 | 50 | # Options 51 | @test 'assert_line() : performs literal matching by default' { 52 | run echo 'a' 53 | run assert_line '*' 54 | 55 | assert_test_fail <<'ERR_MSG' 56 | 57 | -- output does not contain line -- 58 | line : * 59 | output : a 60 | -- 61 | ERR_MSG 62 | } 63 | 64 | 65 | # 66 | # Partial matching: `-p' and `--partial' 67 | # 68 | 69 | # Options 70 | @test 'assert_line() -p : enables partial matching' { 71 | run printf 'a\n_b_\nc' 72 | run assert_line -p 'b' 73 | assert_test_pass 74 | } 75 | 76 | @test 'assert_line() --partial : enables partial matching' { 77 | run printf 'a\n_b_\nc' 78 | run assert_line --partial 'b' 79 | assert_test_pass 80 | } 81 | 82 | # Correctness 83 | @test "assert_line() --partial : returns 0 if is a substring in any line in \`\${lines[@]}'" { 84 | run printf 'a\n_b_\nc' 85 | run assert_line --partial 'b' 86 | assert_test_pass 87 | } 88 | 89 | @test "assert_line() --partial : returns 1 and displays details if is not a substring in any lines in \`\${lines[@]}'" { 90 | run echo 'b' 91 | run assert_line --partial 'a' 92 | 93 | assert_test_fail <<'ERR_MSG' 94 | 95 | -- no output line contains substring -- 96 | substring : a 97 | output : b 98 | -- 99 | ERR_MSG 100 | } 101 | 102 | # Output formatting 103 | @test "assert_line() --partial : displays \`\$output' in multi-line format if it is longer than one line" { 104 | run printf 'b 0\nb 1' 105 | run assert_line --partial 'a' 106 | 107 | assert_test_fail <<'ERR_MSG' 108 | 109 | -- no output line contains substring -- 110 | substring : a 111 | output (2 lines): 112 | b 0 113 | b 1 114 | -- 115 | ERR_MSG 116 | } 117 | 118 | 119 | # 120 | # Regular expression matching: `-e' and `--regexp' 121 | # 122 | 123 | # Options 124 | @test 'assert_line() -e : enables regular expression matching' { 125 | run printf 'a\n_b_\nc' 126 | run assert_line -e '^.b' 127 | assert_test_pass 128 | } 129 | 130 | @test 'assert_line() --regexp : enables regular expression matching' { 131 | run printf 'a\n_b_\nc' 132 | run assert_line --regexp '^.b' 133 | assert_test_pass 134 | } 135 | 136 | # Correctness 137 | @test "assert_line() --regexp : returns 0 if matches any line in \`\${lines[@]}'" { 138 | run printf 'a\n_b_\nc' 139 | run assert_line --regexp '^.b' 140 | assert_test_pass 141 | } 142 | 143 | @test "assert_line() --regexp : returns 1 and displays details if does not match any lines in \`\${lines[@]}'" { 144 | run echo 'b' 145 | run assert_line --regexp '^.a' 146 | 147 | assert_test_fail <<'ERR_MSG' 148 | 149 | -- no output line matches regular expression -- 150 | regexp : ^.a 151 | output : b 152 | -- 153 | ERR_MSG 154 | } 155 | 156 | # Output formatting 157 | @test "assert_line() --regexp : displays \`\$output' in multi-line format if longer than one line" { 158 | run printf 'b 0\nb 1' 159 | run assert_line --regexp '^.a' 160 | 161 | assert_test_fail <<'ERR_MSG' 162 | 163 | -- no output line matches regular expression -- 164 | regexp : ^.a 165 | output (2 lines): 166 | b 0 167 | b 1 168 | -- 169 | ERR_MSG 170 | } 171 | 172 | 173 | ############################################################################### 174 | # Matching single line: `-n' and `--index' 175 | ############################################################################### 176 | 177 | # Options 178 | @test 'assert_line() -n : matches against the -th line only' { 179 | run printf 'a\nb\nc' 180 | run assert_line -n 1 'b' 181 | assert_test_pass 182 | } 183 | 184 | @test 'assert_line() --index : matches against the -th line only' { 185 | run printf 'a\nb\nc' 186 | run assert_line --index 1 'b' 187 | assert_test_pass 188 | } 189 | 190 | @test 'assert_line() --index : returns 1 and displays an error message if is not an integer' { 191 | run assert_line --index 1a 192 | 193 | assert_test_fail <<'ERR_MSG' 194 | 195 | -- ERROR: assert_line -- 196 | `--index' requires an integer argument: `1a' 197 | -- 198 | ERR_MSG 199 | } 200 | 201 | 202 | # 203 | # Literal matching 204 | # 205 | 206 | # Correctness 207 | @test "assert_line() --index : returns 0 if equals \`\${lines[]}'" { 208 | run printf 'a\nb\nc' 209 | run assert_line --index 1 'b' 210 | assert_test_pass 211 | } 212 | 213 | @test "assert_line() --index : returns 1 and displays details if does not equal \`\${lines[]}'" { 214 | run printf 'a\nb\nc' 215 | run assert_line --index 1 'a' 216 | 217 | assert_test_fail <<'ERR_MSG' 218 | 219 | -- line differs -- 220 | index : 1 221 | expected : a 222 | actual : b 223 | -- 224 | ERR_MSG 225 | } 226 | 227 | # Options 228 | @test 'assert_line() --index : performs literal matching by default' { 229 | run printf 'a\nb\nc' 230 | run assert_line --index 1 '*' 231 | 232 | assert_test_fail <<'ERR_MSG' 233 | 234 | -- line differs -- 235 | index : 1 236 | expected : * 237 | actual : b 238 | -- 239 | ERR_MSG 240 | } 241 | 242 | 243 | # 244 | # Partial matching: `-p' and `--partial' 245 | # 246 | 247 | # Options 248 | @test 'assert_line() --index -p : enables partial matching' { 249 | run printf 'a\n_b_\nc' 250 | run assert_line --index 1 -p 'b' 251 | assert_test_pass 252 | } 253 | 254 | @test 'assert_line() --index --partial : enables partial matching' { 255 | run printf 'a\n_b_\nc' 256 | run assert_line --index 1 --partial 'b' 257 | assert_test_pass 258 | } 259 | 260 | # Correctness 261 | @test "assert_line() --index --partial : returns 0 if is a substring in \`\${lines[]}'" { 262 | run printf 'a\n_b_\nc' 263 | run assert_line --index 1 --partial 'b' 264 | assert_test_pass 265 | } 266 | 267 | @test "assert_line() --index --partial : returns 1 and displays details if is not a substring in \`\${lines[]}'" { 268 | run printf 'b 0\nb 1' 269 | run assert_line --index 1 --partial 'a' 270 | 271 | assert_test_fail <<'ERR_MSG' 272 | 273 | -- line does not contain substring -- 274 | index : 1 275 | substring : a 276 | line : b 1 277 | -- 278 | ERR_MSG 279 | } 280 | 281 | 282 | # 283 | # Regular expression matching: `-e' and `--regexp' 284 | # 285 | 286 | # Options 287 | @test 'assert_line() --index -e : enables regular expression matching' { 288 | run printf 'a\n_b_\nc' 289 | run assert_line --index 1 -e '^.b' 290 | assert_test_pass 291 | } 292 | 293 | @test 'assert_line() --index --regexp : enables regular expression matching' { 294 | run printf 'a\n_b_\nc' 295 | run assert_line --index 1 --regexp '^.b' 296 | assert_test_pass 297 | } 298 | 299 | # Correctness 300 | @test "assert_line() --index --regexp : returns 0 if matches \`\${lines[]}'" { 301 | run printf 'a\n_b_\nc' 302 | run assert_line --index 1 --regexp '^.b' 303 | assert_test_pass 304 | } 305 | 306 | @test "assert_line() --index --regexp : returns 1 and displays details if does not match \`\${lines[]}'" { 307 | run printf 'a\nb\nc' 308 | run assert_line --index 1 --regexp '^.a' 309 | 310 | assert_test_fail <<'ERR_MSG' 311 | 312 | -- regular expression does not match line -- 313 | index : 1 314 | regexp : ^.a 315 | line : b 316 | -- 317 | ERR_MSG 318 | } 319 | 320 | 321 | ############################################################################### 322 | # Common 323 | ############################################################################### 324 | 325 | @test "assert_line(): \`--partial' and \`--regexp' are mutually exclusive" { 326 | run assert_line --partial --regexp 327 | 328 | assert_test_fail <<'ERR_MSG' 329 | 330 | -- ERROR: assert_line -- 331 | `--partial' and `--regexp' are mutually exclusive 332 | -- 333 | ERR_MSG 334 | } 335 | 336 | @test 'assert_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 337 | run assert_line --regexp '[.*' 338 | 339 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 340 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 341 | cat < 7 | # 8 | # Options: 9 | # -n, --index Match the th line 10 | # -p, --partial Match if `expected` is a substring of `$output` or line 11 | # -e, --regexp Treat `expected` as an extended regular expression 12 | # The expected line string, substring, or regular expression 13 | # 14 | # IO: 15 | # STDERR - details, on failure 16 | # error message, on error 17 | # Globals: 18 | # output 19 | # lines 20 | # Returns: 21 | # 0 - if matching line found 22 | # 1 - otherwise 23 | # 24 | # Similarly to `assert_output`, this function verifies that a command or function produces the expected output. 25 | # (It is the logical complement of `refute_line`.) 26 | # It checks that the expected line appears in the output (default) or at a specific line number. 27 | # Matching can be literal (default), partial or regular expression. 28 | # 29 | # *__Warning:__ 30 | # Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, 31 | # causing line indices to change and preventing testing for empty lines.* 32 | # 33 | # [bats-93]: https://github.com/sstephenson/bats/pull/93 34 | # 35 | # ## Looking for a line in the output 36 | # 37 | # By default, the entire output is searched for the expected line. 38 | # The assertion fails if the expected line is not found in `${lines[@]}`. 39 | # 40 | # ```bash 41 | # @test 'assert_line() looking for line' { 42 | # run echo $'have-0\nhave-1\nhave-2' 43 | # assert_line 'want' 44 | # } 45 | # ``` 46 | # 47 | # On failure, the expected line and the output are displayed. 48 | # 49 | # ``` 50 | # -- output does not contain line -- 51 | # line : want 52 | # output (3 lines): 53 | # have-0 54 | # have-1 55 | # have-2 56 | # -- 57 | # ``` 58 | # 59 | # ## Matching a specific line 60 | # 61 | # When the `--index ` option is used (`-n ` for short), the expected line is matched only against the line identified by the given index. 62 | # The assertion fails if the expected line does not equal `${lines[]}`. 63 | # 64 | # ```bash 65 | # @test 'assert_line() specific line' { 66 | # run echo $'have-0\nhave-1\nhave-2' 67 | # assert_line --index 1 'want-1' 68 | # } 69 | # ``` 70 | # 71 | # On failure, the index and the compared lines are displayed. 72 | # 73 | # ``` 74 | # -- line differs -- 75 | # index : 1 76 | # expected : want-1 77 | # actual : have-1 78 | # -- 79 | # ``` 80 | # 81 | # ## Partial matching 82 | # 83 | # Partial matching can be enabled with the `--partial` option (`-p` for short). 84 | # When used, a match fails if the expected *substring* is not found in the matched line. 85 | # 86 | # ```bash 87 | # @test 'assert_line() partial matching' { 88 | # run echo $'have 1\nhave 2\nhave 3' 89 | # assert_line --partial 'want' 90 | # } 91 | # ``` 92 | # 93 | # On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line. 94 | # 95 | # ``` 96 | # -- no output line contains substring -- 97 | # substring : want 98 | # output (3 lines): 99 | # have 1 100 | # have 2 101 | # have 3 102 | # -- 103 | # ``` 104 | # 105 | # ## Regular expression matching 106 | # 107 | # Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 108 | # When used, a match fails if the *extended regular expression* does not match the line being tested. 109 | # 110 | # *__Note__: 111 | # As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.* 112 | # 113 | # ```bash 114 | # @test 'assert_line() regular expression matching' { 115 | # run echo $'have-0\nhave-1\nhave-2' 116 | # assert_line --index 1 --regexp '^want-[0-9]$' 117 | # } 118 | # ``` 119 | # 120 | # On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line. 121 | # 122 | # ``` 123 | # -- regular expression does not match line -- 124 | # index : 1 125 | # regexp : ^want-[0-9]$ 126 | # line : have-1 127 | # -- 128 | # ``` 129 | # FIXME(ztombol): Display `${lines[@]}' instead of `$output'! 130 | assert_line() { 131 | __assert_line "$@" 132 | } 133 | 134 | # assert_stderr_line 135 | # =========== 136 | # 137 | # Summary: Fail if the expected line is not found in the stderr (default) or at a specific line number. 138 | # 139 | # Usage: assert_stderr_line [-n index] [-p | -e] [--] 140 | # 141 | # Options: 142 | # -n, --index Match the th line 143 | # -p, --partial Match if `expected` is a substring of `$stderr` or line 144 | # -e, --regexp Treat `expected` as an extended regular expression 145 | # The expected line string, substring, or regular expression 146 | # 147 | # IO: 148 | # STDERR - details, on failure 149 | # error message, on error 150 | # Globals: 151 | # stderr 152 | # stderr_lines 153 | # Returns: 154 | # 0 - if matching line found 155 | # 1 - otherwise 156 | # 157 | # Similarly to `assert_stderr`, this function verifies that a command or function produces the expected stderr. 158 | # (It is the logical complement of `refute_stderr_line`.) 159 | # It checks that the expected line appears in the stderr (default) or at a specific line number. 160 | # Matching can be literal (default), partial or regular expression. 161 | # 162 | assert_stderr_line() { 163 | __assert_line "$@" 164 | } 165 | 166 | __assert_line() { 167 | local -r caller=${FUNCNAME[1]} 168 | local -i is_match_line=0 169 | local -i is_mode_partial=0 170 | local -i is_mode_regexp=0 171 | 172 | if [[ "${caller}" == "assert_line" ]]; then 173 | : "${lines?}" 174 | local -ar stream_lines=("${lines[@]}") 175 | local -r stream_type=output 176 | elif [[ "${caller}" == "assert_stderr_line" ]]; then 177 | : "${stderr_lines?}" 178 | local -ar stream_lines=("${stderr_lines[@]}") 179 | local -r stream_type=stderr 180 | else 181 | # Unknown caller 182 | echo "Unexpected call to \`${FUNCNAME[0]}\` 183 | Did you mean to call \`assert_line\` or \`assert_stderr_line\`?" \ 184 | | batslib_decorate "ERROR: ${FUNCNAME[0]}" \ 185 | | fail 186 | return $? 187 | fi 188 | 189 | # Handle options. 190 | while (( $# > 0 )); do 191 | case "$1" in 192 | -n|--index) 193 | if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then 194 | echo "\`--index' requires an integer argument: \`$2'" \ 195 | | batslib_decorate "ERROR: ${caller}" \ 196 | | fail 197 | return $? 198 | fi 199 | is_match_line=1 200 | local -ri idx="$2" 201 | shift 2 202 | ;; 203 | -p|--partial) is_mode_partial=1; shift ;; 204 | -e|--regexp) is_mode_regexp=1; shift ;; 205 | --) shift; break ;; 206 | *) break ;; 207 | esac 208 | done 209 | 210 | if (( is_mode_partial )) && (( is_mode_regexp )); then 211 | echo "\`--partial' and \`--regexp' are mutually exclusive" \ 212 | | batslib_decorate "ERROR: ${caller}" \ 213 | | fail 214 | return $? 215 | fi 216 | 217 | # Arguments. 218 | local -r expected="$1" 219 | 220 | if (( is_mode_regexp == 1 )); then 221 | __check_is_valid_regex "$expected" "$caller" || return 1 222 | fi 223 | 224 | # Matching. 225 | if (( is_match_line )); then 226 | # Specific line. 227 | if (( is_mode_regexp )); then 228 | if ! [[ ${stream_lines[$idx]} =~ $expected ]]; then 229 | batslib_print_kv_single 6 \ 230 | 'index' "$idx" \ 231 | 'regexp' "$expected" \ 232 | 'line' "${stream_lines[$idx]}" \ 233 | | batslib_decorate 'regular expression does not match line' \ 234 | | fail 235 | fi 236 | elif (( is_mode_partial )); then 237 | if [[ ${stream_lines[$idx]} != *"$expected"* ]]; then 238 | batslib_print_kv_single 9 \ 239 | 'index' "$idx" \ 240 | 'substring' "$expected" \ 241 | 'line' "${stream_lines[$idx]}" \ 242 | | batslib_decorate 'line does not contain substring' \ 243 | | fail 244 | fi 245 | else 246 | if [[ ${stream_lines[$idx]} != "$expected" ]]; then 247 | batslib_print_kv_single 8 \ 248 | 'index' "$idx" \ 249 | 'expected' "$expected" \ 250 | 'actual' "${stream_lines[$idx]}" \ 251 | | batslib_decorate 'line differs' \ 252 | | fail 253 | fi 254 | fi 255 | else 256 | # Contained in output/error stream. 257 | if (( is_mode_regexp )); then 258 | local -i idx 259 | for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do 260 | [[ ${stream_lines[$idx]} =~ $expected ]] && return 0 261 | done 262 | { local -ar single=( 'regexp' "$expected" ) 263 | local -ar may_be_multi=( "${stream_type}" "${!stream_type}" ) 264 | local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" 265 | batslib_print_kv_single "$width" "${single[@]}" 266 | batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" 267 | } \ 268 | | batslib_decorate "no ${stream_type} line matches regular expression" \ 269 | | fail 270 | elif (( is_mode_partial )); then 271 | local -i idx 272 | for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do 273 | [[ ${stream_lines[$idx]} == *"$expected"* ]] && return 0 274 | done 275 | { local -ar single=( 'substring' "$expected" ) 276 | local -ar may_be_multi=( "${stream_type}" "${!stream_type}" ) 277 | local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" 278 | batslib_print_kv_single "$width" "${single[@]}" 279 | batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" 280 | } \ 281 | | batslib_decorate "no ${stream_type} line contains substring" \ 282 | | fail 283 | else 284 | local -i idx 285 | for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do 286 | [[ ${stream_lines[$idx]} == "$expected" ]] && return 0 287 | done 288 | { local -ar single=( 'line' "$expected" ) 289 | local -ar may_be_multi=( "${stream_type}" "${!stream_type}" ) 290 | local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" 291 | batslib_print_kv_single "$width" "${single[@]}" 292 | batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" 293 | } \ 294 | | batslib_decorate "${stream_type} does not contain line" \ 295 | | fail 296 | fi 297 | fi 298 | } 299 | -------------------------------------------------------------------------------- /test/refute_stderr_line.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | setup_file() { 6 | bats_require_minimum_version 1.5.0 7 | } 8 | 9 | echo_err() { 10 | echo "$@" >&2 11 | } 12 | 13 | printf_err() { 14 | # shellcheck disable=2059 15 | printf "$@" >&2 16 | } 17 | 18 | ############################################################################### 19 | # Containing a line 20 | ############################################################################### 21 | 22 | # 23 | # Literal matching 24 | # 25 | 26 | # Correctness 27 | @test "refute_stderr_line() : returns 0 if is not a line in \`\${stderr_lines[@]}'" { 28 | run --separate-stderr printf_err 'a\nb\nc' 29 | run refute_stderr_line 'd' 30 | assert_test_pass 31 | } 32 | 33 | @test "refute_stderr_line() : returns 1 and displays details if is not a line in \`\${stderr_lines[@]}'" { 34 | run --separate-stderr echo_err 'a' 35 | run refute_stderr_line 'a' 36 | 37 | assert_test_fail <<'ERR_MSG' 38 | 39 | -- line should not be in stderr -- 40 | line : a 41 | index : 0 42 | stderr : a 43 | -- 44 | ERR_MSG 45 | } 46 | 47 | # Output formatting 48 | @test "refute_stderr_line() : displays \`\$stderr' in multi-line format if it is longer than one line" { 49 | run --separate-stderr printf_err 'a 0\na 1\na 2' 50 | run refute_stderr_line 'a 1' 51 | 52 | assert_test_fail <<'ERR_MSG' 53 | 54 | -- line should not be in stderr -- 55 | line : a 1 56 | index : 1 57 | stderr (3 lines): 58 | a 0 59 | > a 1 60 | a 2 61 | -- 62 | ERR_MSG 63 | } 64 | 65 | # Options 66 | @test 'refute_stderr_line() : performs literal matching by default' { 67 | run --separate-stderr echo_err 'a' 68 | run refute_stderr_line '*' 69 | assert_test_pass 70 | } 71 | 72 | 73 | # 74 | # Partial matching: `-p' and `--partial' 75 | # 76 | 77 | # Options 78 | @test 'refute_stderr_line() -p : enables partial matching' { 79 | run --separate-stderr printf_err 'a\nb\nc' 80 | run refute_stderr_line -p 'd' 81 | assert_test_pass 82 | } 83 | 84 | @test 'refute_stderr_line() --partial : enables partial matching' { 85 | run --separate-stderr printf_err 'a\nb\nc' 86 | run refute_stderr_line --partial 'd' 87 | assert_test_pass 88 | } 89 | 90 | # Correctness 91 | @test "refute_stderr_line() --partial : returns 0 if is not a substring in any line in \`\${stderr_lines[@]}'" { 92 | run --separate-stderr printf_err 'a\nb\nc' 93 | run refute_stderr_line --partial 'd' 94 | assert_test_pass 95 | } 96 | 97 | @test "refute_stderr_line() --partial : returns 1 and displays details if is a substring in any line in \`\${stderr_lines[@]}'" { 98 | run --separate-stderr echo_err 'a' 99 | run refute_stderr_line --partial 'a' 100 | 101 | assert_test_fail <<'ERR_MSG' 102 | 103 | -- no line should contain substring -- 104 | substring : a 105 | index : 0 106 | stderr : a 107 | -- 108 | ERR_MSG 109 | } 110 | 111 | # Output formatting 112 | @test "refute_stderr_line() --partial : displays \`\$stderr' in multi-line format if it is longer than one line" { 113 | run --separate-stderr printf_err 'a\nabc\nc' 114 | run refute_stderr_line --partial 'b' 115 | 116 | assert_test_fail <<'ERR_MSG' 117 | 118 | -- no line should contain substring -- 119 | substring : b 120 | index : 1 121 | stderr (3 lines): 122 | a 123 | > abc 124 | c 125 | -- 126 | ERR_MSG 127 | } 128 | 129 | 130 | # 131 | # Regular expression matching: `-e' and `--regexp' 132 | # 133 | 134 | # Options 135 | @test 'refute_stderr_line() -e : enables regular expression matching' { 136 | run --separate-stderr printf_err 'a\nb\nc' 137 | run refute_stderr_line -e '^.d' 138 | assert_test_pass 139 | } 140 | 141 | @test 'refute_stderr_line() --regexp : enables regular expression matching' { 142 | run --separate-stderr printf_err 'a\nb\nc' 143 | run refute_stderr_line --regexp '^.d' 144 | assert_test_pass 145 | } 146 | 147 | # Correctness 148 | @test "refute_stderr_line() --regexp : returns 0 if does not match any line in \`\${stderr_lines[@]}'" { 149 | run --separate-stderr printf_err 'a\nb\nc' 150 | run refute_stderr_line --regexp '.*d.*' 151 | assert_test_pass 152 | } 153 | 154 | @test "refute_stderr_line() --regexp : returns 1 and displays details if matches any lines in \`\${stderr_lines[@]}'" { 155 | run --separate-stderr echo_err 'a' 156 | run refute_stderr_line --regexp '.*a.*' 157 | 158 | assert_test_fail <<'ERR_MSG' 159 | 160 | -- no line should match the regular expression -- 161 | regexp : .*a.* 162 | index : 0 163 | stderr : a 164 | -- 165 | ERR_MSG 166 | } 167 | 168 | # Output formatting 169 | @test "refute_stderr_line() --regexp : displays \`\$stderr' in multi-line format if longer than one line" { 170 | run --separate-stderr printf_err 'a\nabc\nc' 171 | run refute_stderr_line --regexp '.*b.*' 172 | 173 | assert_test_fail <<'ERR_MSG' 174 | 175 | -- no line should match the regular expression -- 176 | regexp : .*b.* 177 | index : 1 178 | stderr (3 lines): 179 | a 180 | > abc 181 | c 182 | -- 183 | ERR_MSG 184 | } 185 | 186 | 187 | ############################################################################### 188 | # Matching single line: `-n' and `--index' 189 | ############################################################################### 190 | 191 | # Options 192 | @test 'refute_stderr_line() -n : matches against the -th line only' { 193 | run --separate-stderr printf_err 'a\nb\nc' 194 | run refute_stderr_line -n 1 'd' 195 | assert_test_pass 196 | } 197 | 198 | @test 'refute_stderr_line() --index : matches against the -th line only' { 199 | run --separate-stderr printf_err 'a\nb\nc' 200 | run refute_stderr_line --index 1 'd' 201 | assert_test_pass 202 | } 203 | 204 | @test 'refute_stderr_line() --index : returns 1 and displays an error message if is not an integer' { 205 | run refute_stderr_line --index 1a 206 | 207 | assert_test_fail <<'ERR_MSG' 208 | 209 | -- ERROR: refute_stderr_line -- 210 | `--index' requires an integer argument: `1a' 211 | -- 212 | ERR_MSG 213 | } 214 | 215 | 216 | # 217 | # Literal matching 218 | # 219 | 220 | # Correctness 221 | @test "refute_stderr_line() --index : returns 0 if does not equal \`\${stderr_lines[]}'" { 222 | run --separate-stderr printf_err 'a\nb\nc' 223 | run refute_stderr_line --index 1 'd' 224 | assert_test_pass 225 | } 226 | 227 | @test "refute_stderr_line() --index : returns 1 and displays details if equals \`\${stderr_lines[]}'" { 228 | run --separate-stderr printf_err 'a\nb\nc' 229 | run refute_stderr_line --index 1 'b' 230 | 231 | assert_test_fail <<'ERR_MSG' 232 | 233 | -- line should differ -- 234 | index : 1 235 | line : b 236 | -- 237 | ERR_MSG 238 | } 239 | 240 | # Options 241 | @test 'refute_stderr_line() --index : performs literal matching by default' { 242 | run --separate-stderr printf_err 'a\nb\nc' 243 | run refute_stderr_line --index 1 '*' 244 | assert_test_pass 245 | } 246 | 247 | 248 | # 249 | # Partial matching: `-p' and `--partial' 250 | # 251 | 252 | # Options 253 | @test 'refute_stderr_line() --index -p : enables partial matching' { 254 | run --separate-stderr printf_err 'a\nb\nc' 255 | run refute_stderr_line --index 1 -p 'd' 256 | assert_test_pass 257 | } 258 | 259 | @test 'refute_stderr_line() --index --partial : enables partial matching' { 260 | run --separate-stderr printf_err 'a\nb\nc' 261 | run refute_stderr_line --index 1 --partial 'd' 262 | assert_test_pass 263 | } 264 | 265 | # Correctness 266 | @test "refute_stderr_line() --index --partial : returns 0 if is not a substring in \`\${stderr_lines[]}'" { 267 | run --separate-stderr printf_err 'a\nabc\nc' 268 | run refute_stderr_line --index 1 --partial 'd' 269 | assert_test_pass 270 | } 271 | 272 | @test "refute_stderr_line() --index --partial : returns 1 and displays details if is a substring in \`\${stderr_lines[]}'" { 273 | run --separate-stderr printf_err 'a\nabc\nc' 274 | run refute_stderr_line --index 1 --partial 'b' 275 | 276 | assert_test_fail <<'ERR_MSG' 277 | 278 | -- line should not contain substring -- 279 | index : 1 280 | substring : b 281 | line : abc 282 | -- 283 | ERR_MSG 284 | } 285 | 286 | 287 | # 288 | # Regular expression matching: `-e' and `--regexp' 289 | # 290 | 291 | # Options 292 | @test 'refute_stderr_line() --index -e : enables regular expression matching' { 293 | run --separate-stderr printf_err 'a\nb\nc' 294 | run refute_stderr_line --index 1 -e '[^.b]' 295 | assert_test_pass 296 | } 297 | 298 | @test 'refute_stderr_line() --index --regexp : enables regular expression matching' { 299 | run --separate-stderr printf_err 'a\nb\nc' 300 | run refute_stderr_line --index 1 --regexp '^.b' 301 | assert_test_pass 302 | } 303 | 304 | # Correctness 305 | @test "refute_stderr_line() --index --regexp : returns 0 if does not match \`\${stderr_lines[]}'" { 306 | run --separate-stderr printf_err 'a\nabc\nc' 307 | run refute_stderr_line --index 1 --regexp '.*d.*' 308 | assert_test_pass 309 | } 310 | 311 | @test "refute_stderr_line() --index --regexp : returns 1 and displays details if matches \`\${stderr_lines[]}'" { 312 | run --separate-stderr printf_err 'a\nabc\nc' 313 | run refute_stderr_line --index 1 --regexp '.*b.*' 314 | 315 | assert_test_fail <<'ERR_MSG' 316 | 317 | -- regular expression should not match line -- 318 | index : 1 319 | regexp : .*b.* 320 | line : abc 321 | -- 322 | ERR_MSG 323 | } 324 | 325 | 326 | ############################################################################### 327 | # Common 328 | ############################################################################### 329 | 330 | @test "refute_stderr_line(): \`--partial' and \`--regexp' are mutually exclusive" { 331 | run refute_stderr_line --partial --regexp 332 | 333 | assert_test_fail <<'ERR_MSG' 334 | 335 | -- ERROR: refute_stderr_line -- 336 | `--partial' and `--regexp' are mutually exclusive 337 | -- 338 | ERR_MSG 339 | } 340 | 341 | @test 'refute_stderr_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 342 | run refute_stderr_line --regexp '[.*' 343 | 344 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 345 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 346 | assert_test_fail <&2 11 | } 12 | 13 | printf_err() { 14 | # shellcheck disable=2059 15 | printf "$@" >&2 16 | } 17 | 18 | 19 | ############################################################################### 20 | # Containing a line 21 | ############################################################################### 22 | 23 | # 24 | # Literal matching 25 | # 26 | 27 | # Correctness 28 | @test "assert_stderr_line() : returns 0 if is a line in \`\${stderr_lines[@]}'" { 29 | run --separate-stderr printf_err 'a\nb\nc' 30 | run assert_stderr_line 'b' 31 | assert_test_pass 32 | } 33 | 34 | @test "assert_stderr_line() : returns 1 and displays details if is not a line in \`\${stderr_lines[@]}'" { 35 | run --separate-stderr echo_err 'b' 36 | run assert_stderr_line 'a' 37 | 38 | assert_test_fail <<'ERR_MSG' 39 | 40 | -- stderr does not contain line -- 41 | line : a 42 | stderr : b 43 | -- 44 | ERR_MSG 45 | } 46 | 47 | # stderr formatting 48 | @test "assert_stderr_line() : displays \`\$stderr' in multi-line format if it is longer than one line" { 49 | run --separate-stderr printf_err 'b 0\nb 1' 50 | run assert_stderr_line 'a' 51 | 52 | assert_test_fail <<'ERR_MSG' 53 | 54 | -- stderr does not contain line -- 55 | line : a 56 | stderr (2 lines): 57 | b 0 58 | b 1 59 | -- 60 | ERR_MSG 61 | } 62 | 63 | # Options 64 | @test 'assert_stderr_line() : performs literal matching by default' { 65 | run --separate-stderr echo_err 'a' 66 | run assert_stderr_line '*' 67 | 68 | assert_test_fail <<'ERR_MSG' 69 | 70 | -- stderr does not contain line -- 71 | line : * 72 | stderr : a 73 | -- 74 | ERR_MSG 75 | } 76 | 77 | 78 | # 79 | # Partial matching: `-p' and `--partial' 80 | # 81 | 82 | # Options 83 | @test 'assert_stderr_line() -p : enables partial matching' { 84 | run --separate-stderr printf_err 'a\n_b_\nc' 85 | run assert_stderr_line -p 'b' 86 | assert_test_pass 87 | } 88 | 89 | @test 'assert_stderr_line() --partial : enables partial matching' { 90 | run --separate-stderr printf_err 'a\n_b_\nc' 91 | run assert_stderr_line --partial 'b' 92 | assert_test_pass 93 | } 94 | 95 | # Correctness 96 | @test "assert_stderr_line() --partial : returns 0 if is a substring in any line in \`\${stderr_lines[@]}'" { 97 | run --separate-stderr printf_err 'a\n_b_\nc' 98 | run assert_stderr_line --partial 'b' 99 | assert_test_pass 100 | } 101 | 102 | @test "assert_stderr_line() --partial : returns 1 and displays details if is not a substring in any lines in \`\${stderr_lines[@]}'" { 103 | run --separate-stderr echo_err 'b' 104 | run assert_stderr_line --partial 'a' 105 | 106 | assert_test_fail <<'ERR_MSG' 107 | 108 | -- no stderr line contains substring -- 109 | substring : a 110 | stderr : b 111 | -- 112 | ERR_MSG 113 | } 114 | 115 | # stderr formatting 116 | @test "assert_stderr_line() --partial : displays \`\$stderr' in multi-line format if it is longer than one line" { 117 | run --separate-stderr printf_err 'b 0\nb 1' 118 | run assert_stderr_line --partial 'a' 119 | 120 | assert_test_fail <<'ERR_MSG' 121 | 122 | -- no stderr line contains substring -- 123 | substring : a 124 | stderr (2 lines): 125 | b 0 126 | b 1 127 | -- 128 | ERR_MSG 129 | } 130 | 131 | 132 | # 133 | # Regular expression matching: `-e' and `--regexp' 134 | # 135 | 136 | # Options 137 | @test 'assert_stderr_line() -e : enables regular expression matching' { 138 | run --separate-stderr printf_err 'a\n_b_\nc' 139 | run assert_stderr_line -e '^.b' 140 | assert_test_pass 141 | } 142 | 143 | @test 'assert_stderr_line() --regexp : enables regular expression matching' { 144 | run --separate-stderr printf_err 'a\n_b_\nc' 145 | run assert_stderr_line --regexp '^.b' 146 | assert_test_pass 147 | } 148 | 149 | # Correctness 150 | @test "assert_stderr_line() --regexp : returns 0 if matches any line in \`\${stderr_lines[@]}'" { 151 | run --separate-stderr printf_err 'a\n_b_\nc' 152 | run assert_stderr_line --regexp '^.b' 153 | assert_test_pass 154 | } 155 | 156 | @test "assert_stderr_line() --regexp : returns 1 and displays details if does not match any lines in \`\${stderr_lines[@]}'" { 157 | run --separate-stderr echo_err 'b' 158 | run assert_stderr_line --regexp '^.a' 159 | 160 | assert_test_fail <<'ERR_MSG' 161 | 162 | -- no stderr line matches regular expression -- 163 | regexp : ^.a 164 | stderr : b 165 | -- 166 | ERR_MSG 167 | } 168 | 169 | # stderr formatting 170 | @test "assert_stderr_line() --regexp : displays \`\$stderr' in multi-line format if longer than one line" { 171 | run --separate-stderr printf_err 'b 0\nb 1' 172 | run assert_stderr_line --regexp '^.a' 173 | 174 | assert_test_fail <<'ERR_MSG' 175 | 176 | -- no stderr line matches regular expression -- 177 | regexp : ^.a 178 | stderr (2 lines): 179 | b 0 180 | b 1 181 | -- 182 | ERR_MSG 183 | } 184 | 185 | 186 | ############################################################################### 187 | # Matching single line: `-n' and `--index' 188 | ############################################################################### 189 | 190 | # Options 191 | @test 'assert_stderr_line() -n : matches against the -th line only' { 192 | run --separate-stderr printf_err 'a\nb\nc' 193 | run assert_stderr_line -n 1 'b' 194 | assert_test_pass 195 | } 196 | 197 | @test 'assert_stderr_line() --index : matches against the -th line only' { 198 | run --separate-stderr printf_err 'a\nb\nc' 199 | run assert_stderr_line --index 1 'b' 200 | assert_test_pass 201 | } 202 | 203 | @test 'assert_stderr_line() --index : returns 1 and displays an error message if is not an integer' { 204 | run assert_stderr_line --index 1a 205 | 206 | assert_test_fail <<'ERR_MSG' 207 | 208 | -- ERROR: assert_stderr_line -- 209 | `--index' requires an integer argument: `1a' 210 | -- 211 | ERR_MSG 212 | } 213 | 214 | 215 | # 216 | # Literal matching 217 | # 218 | 219 | # Correctness 220 | @test "assert_stderr_line() --index : returns 0 if equals \`\${stderr_lines[]}'" { 221 | run --separate-stderr printf_err 'a\nb\nc' 222 | run assert_stderr_line --index 1 'b' 223 | assert_test_pass 224 | } 225 | 226 | @test "assert_stderr_line() --index : returns 1 and displays details if does not equal \`\${stderr_lines[]}'" { 227 | run --separate-stderr printf_err 'a\nb\nc' 228 | run assert_stderr_line --index 1 'a' 229 | 230 | assert_test_fail <<'ERR_MSG' 231 | 232 | -- line differs -- 233 | index : 1 234 | expected : a 235 | actual : b 236 | -- 237 | ERR_MSG 238 | } 239 | 240 | # Options 241 | @test 'assert_stderr_line() --index : performs literal matching by default' { 242 | run --separate-stderr printf_err 'a\nb\nc' 243 | run assert_stderr_line --index 1 '*' 244 | 245 | assert_test_fail <<'ERR_MSG' 246 | 247 | -- line differs -- 248 | index : 1 249 | expected : * 250 | actual : b 251 | -- 252 | ERR_MSG 253 | } 254 | 255 | 256 | # 257 | # Partial matching: `-p' and `--partial' 258 | # 259 | 260 | # Options 261 | @test 'assert_stderr_line() --index -p : enables partial matching' { 262 | run --separate-stderr printf_err 'a\n_b_\nc' 263 | run assert_stderr_line --index 1 -p 'b' 264 | assert_test_pass 265 | } 266 | 267 | @test 'assert_stderr_line() --index --partial : enables partial matching' { 268 | run --separate-stderr printf_err 'a\n_b_\nc' 269 | run assert_stderr_line --index 1 --partial 'b' 270 | assert_test_pass 271 | } 272 | 273 | # Correctness 274 | @test "assert_stderr_line() --index --partial : returns 0 if is a substring in \`\${stderr_lines[]}'" { 275 | run --separate-stderr printf_err 'a\n_b_\nc' 276 | run assert_stderr_line --index 1 --partial 'b' 277 | assert_test_pass 278 | } 279 | 280 | @test "assert_stderr_line() --index --partial : returns 1 and displays details if is not a substring in \`\${stderr_lines[]}'" { 281 | run --separate-stderr printf_err 'b 0\nb 1' 282 | run assert_stderr_line --index 1 --partial 'a' 283 | 284 | assert_test_fail <<'ERR_MSG' 285 | 286 | -- line does not contain substring -- 287 | index : 1 288 | substring : a 289 | line : b 1 290 | -- 291 | ERR_MSG 292 | } 293 | 294 | 295 | # 296 | # Regular expression matching: `-e' and `--regexp' 297 | # 298 | 299 | # Options 300 | @test 'assert_stderr_line() --index -e : enables regular expression matching' { 301 | run --separate-stderr printf_err 'a\n_b_\nc' 302 | run assert_stderr_line --index 1 -e '^.b' 303 | assert_test_pass 304 | } 305 | 306 | @test 'assert_stderr_line() --index --regexp : enables regular expression matching' { 307 | run --separate-stderr printf_err 'a\n_b_\nc' 308 | run assert_stderr_line --index 1 --regexp '^.b' 309 | assert_test_pass 310 | } 311 | 312 | # Correctness 313 | @test "assert_stderr_line() --index --regexp : returns 0 if matches \`\${stderr_lines[]}'" { 314 | run --separate-stderr printf_err 'a\n_b_\nc' 315 | run assert_stderr_line --index 1 --regexp '^.b' 316 | assert_test_pass 317 | } 318 | 319 | @test "assert_stderr_line() --index --regexp : returns 1 and displays details if does not match \`\${stderr_lines[]}'" { 320 | run --separate-stderr printf_err 'a\nb\nc' 321 | run assert_stderr_line --index 1 --regexp '^.a' 322 | 323 | assert_test_fail <<'ERR_MSG' 324 | 325 | -- regular expression does not match line -- 326 | index : 1 327 | regexp : ^.a 328 | line : b 329 | -- 330 | ERR_MSG 331 | } 332 | 333 | 334 | ############################################################################### 335 | # Common 336 | ############################################################################### 337 | 338 | @test "assert_stderr_line(): \`--partial' and \`--regexp' are mutually exclusive" { 339 | run assert_stderr_line --partial --regexp 340 | 341 | assert_test_fail <<'ERR_MSG' 342 | 343 | -- ERROR: assert_stderr_line -- 344 | `--partial' and `--regexp' are mutually exclusive 345 | -- 346 | ERR_MSG 347 | } 348 | 349 | @test 'assert_stderr_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { 350 | run assert_stderr_line --regexp '[.*' 351 | 352 | if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >=3) )); then 353 | [[ "$output" =~ "invalid regular expression "([^$'\n']+) ]] 354 | assert_test_fail < 7 | # 8 | # Options: 9 | # -n, --index Match the th line 10 | # -p, --partial Match if `unexpected` is a substring of `$output` or line 11 | # -e, --regexp Treat `unexpected` as an extended regular expression 12 | # The unexpected line string, substring, or regular expression. 13 | # 14 | # IO: 15 | # STDERR - details, on failure 16 | # error message, on error 17 | # Globals: 18 | # output 19 | # lines 20 | # Returns: 21 | # 0 - if match not found 22 | # 1 - otherwise 23 | # 24 | # Similarly to `refute_output`, this function verifies that a command or function does not produce the unexpected output. 25 | # (It is the logical complement of `assert_line`.) 26 | # It checks that the unexpected line does not appear in the output (default) or at a specific line number. 27 | # Matching can be literal (default), partial or regular expression. 28 | # 29 | # ## Looking for a line in the output 30 | # 31 | # By default, the entire output is searched for the unexpected line. 32 | # The assertion fails if the unexpected line is found in `${lines[@]}`. 33 | # 34 | # ```bash 35 | # @test 'refute_line() looking for line' { 36 | # run echo $'have-0\nwant\nhave-2' 37 | # refute_line 'want' 38 | # } 39 | # ``` 40 | # 41 | # On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed. 42 | # 43 | # ``` 44 | # -- line should not be in output -- 45 | # line : want 46 | # index : 1 47 | # output (3 lines): 48 | # have-0 49 | # > want 50 | # have-2 51 | # -- 52 | # ``` 53 | # 54 | # ## Matching a specific line 55 | # 56 | # When the `--index ` option is used (`-n ` for short), the unexpected line is matched only against the line identified by the given index. 57 | # The assertion fails if the unexpected line equals `${lines[]}`. 58 | # 59 | # ```bash 60 | # @test 'refute_line() specific line' { 61 | # run echo $'have-0\nwant-1\nhave-2' 62 | # refute_line --index 1 'want-1' 63 | # } 64 | # ``` 65 | # 66 | # On failure, the index and the unexpected line are displayed. 67 | # 68 | # ``` 69 | # -- line should differ -- 70 | # index : 1 71 | # line : want-1 72 | # -- 73 | # ``` 74 | # 75 | # ## Partial matching 76 | # 77 | # Partial matching can be enabled with the `--partial` option (`-p` for short). 78 | # When used, a match fails if the unexpected *substring* is found in the matched line. 79 | # 80 | # ```bash 81 | # @test 'refute_line() partial matching' { 82 | # run echo $'have 1\nwant 2\nhave 3' 83 | # refute_line --partial 'want' 84 | # } 85 | # ``` 86 | # 87 | # On failure, in addition to the details of literal matching, the substring is also displayed. 88 | # When used with `--index ` the substring replaces the unexpected line. 89 | # 90 | # ``` 91 | # -- no line should contain substring -- 92 | # substring : want 93 | # index : 1 94 | # output (3 lines): 95 | # have 1 96 | # > want 2 97 | # have 3 98 | # -- 99 | # ``` 100 | # 101 | # ## Regular expression matching 102 | # 103 | # Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 104 | # When used, a match fails if the *extended regular expression* matches the line being tested. 105 | # 106 | # *__Note__: 107 | # As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.* 108 | # 109 | # ```bash 110 | # @test 'refute_line() regular expression matching' { 111 | # run echo $'Foobar v0.1.0\nRelease date: 2015-11-29' 112 | # refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 113 | # } 114 | # ``` 115 | # 116 | # On failure, in addition to the details of literal matching, the regular expression is also displayed. 117 | # When used with `--index ` the regular expression replaces the unexpected line. 118 | # 119 | # ``` 120 | # -- regular expression should not match line -- 121 | # index : 0 122 | # regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 123 | # line : Foobar v0.1.0 124 | # -- 125 | # ``` 126 | # FIXME(ztombol): Display `${lines[@]}' instead of `$output'! 127 | refute_line() { 128 | __refute_stream_line "$@" 129 | } 130 | 131 | # refute_stderr_line 132 | # ================== 133 | # 134 | # Summary: Fail if the unexpected line is found in the stderr (default) or at a specific line number. 135 | # 136 | # Usage: refute_stderr_line [-n index] [-p | -e] [--] 137 | # 138 | # Options: 139 | # -n, --index Match the th line 140 | # -p, --partial Match if `unexpected` is a substring of `$stderr` or line 141 | # -e, --regexp Treat `unexpected` as an extended regular expression 142 | # The unexpected line string, substring, or regular expression. 143 | # 144 | # IO: 145 | # STDERR - details, on failure 146 | # error message, on error 147 | # Globals: 148 | # stderr 149 | # stderr_lines 150 | # Returns: 151 | # 0 - if match not found 152 | # 1 - otherwise 153 | # 154 | # Similarly to `refute_stderr`, this function verifies that a command or function does not produce the unexpected stderr. 155 | # (It is the logical complement of `assert_stderr_line`.) 156 | # It checks that the unexpected line does not appear in the stderr (default) or at a specific line number. 157 | # Matching can be literal (default), partial or regular expression. 158 | # 159 | refute_stderr_line() { 160 | __refute_stream_line "$@" 161 | } 162 | 163 | # __check_is_vvalid_regex 164 | # ======================= 165 | # 166 | # Summary: checks if the regex in unexpected is valid, also prints an error message if not. 167 | # IO: 168 | # STDERR - details, on error 169 | # Globals: 170 | # caller - readonly 171 | # Returns: 172 | # 0 - if regex is valid 173 | # 1 - otherwise 174 | __check_is_valid_regex() { # 175 | local -r regex="$1" caller="$2" 176 | local error 177 | error=$([[ '' =~ $regex ]] 2>&1) # capture the detailed error message on Bash >=5.3 178 | if [[ $? == 2 ]]; then 179 | local err_msg="Invalid extended regular expression: \`$regex'" 180 | if [[ $error =~ (invalid regular expression .*) ]]; then 181 | err_msg="${BASH_REMATCH[1]}" 182 | fi 183 | echo "$err_msg" \ 184 | | batslib_decorate "ERROR: ${caller}" \ 185 | | fail 186 | fi 187 | } 188 | 189 | __refute_stream_line() { 190 | local -r caller=${FUNCNAME[1]} 191 | local -i is_match_line=0 192 | local -i is_mode_partial=0 193 | local -i is_mode_regexp=0 194 | 195 | if [[ "${caller}" == "refute_line" ]]; then 196 | : "${lines?}" 197 | local -ar stream_lines=("${lines[@]}") 198 | local -r stream_type=output 199 | elif [[ "${caller}" == "refute_stderr_line" ]]; then 200 | : "${stderr_lines?}" 201 | local -ar stream_lines=("${stderr_lines[@]}") 202 | local -r stream_type=stderr 203 | else 204 | # Unknown caller 205 | echo "Unexpected call to \`${FUNCNAME[0]}\` 206 | Did you mean to call \`refute_line\` or \`refute_stderr_line\`?" | 207 | batslib_decorate "ERROR: ${FUNCNAME[0]}" | 208 | fail 209 | return $? 210 | fi 211 | 212 | # Handle options. 213 | while (( $# > 0 )); do 214 | case "$1" in 215 | -n|--index) 216 | if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then 217 | echo "\`--index' requires an integer argument: \`$2'" \ 218 | | batslib_decorate "ERROR: ${caller}" \ 219 | | fail 220 | return $? 221 | fi 222 | is_match_line=1 223 | local -ri idx="$2" 224 | shift 2 225 | ;; 226 | -p|--partial) is_mode_partial=1; shift ;; 227 | -e|--regexp) is_mode_regexp=1; shift ;; 228 | --) shift; break ;; 229 | *) break ;; 230 | esac 231 | done 232 | 233 | if (( is_mode_partial )) && (( is_mode_regexp )); then 234 | echo "\`--partial' and \`--regexp' are mutually exclusive" \ 235 | | batslib_decorate "ERROR: ${caller}" \ 236 | | fail 237 | return $? 238 | fi 239 | 240 | # Arguments. 241 | local -r unexpected="$1" 242 | 243 | if (( is_mode_regexp == 1 )); then 244 | __check_is_valid_regex "$unexpected" "$caller" || return $? 245 | fi 246 | 247 | # Matching. 248 | if (( is_match_line )); then 249 | # Specific line. 250 | if (( is_mode_regexp )); then 251 | if [[ ${stream_lines[$idx]} =~ $unexpected ]]; then 252 | batslib_print_kv_single 6 \ 253 | 'index' "$idx" \ 254 | 'regexp' "$unexpected" \ 255 | 'line' "${stream_lines[$idx]}" \ 256 | | batslib_decorate 'regular expression should not match line' \ 257 | | fail 258 | fi 259 | elif (( is_mode_partial )); then 260 | if [[ ${stream_lines[$idx]} == *"$unexpected"* ]]; then 261 | batslib_print_kv_single 9 \ 262 | 'index' "$idx" \ 263 | 'substring' "$unexpected" \ 264 | 'line' "${stream_lines[$idx]}" \ 265 | | batslib_decorate 'line should not contain substring' \ 266 | | fail 267 | fi 268 | else 269 | if [[ ${stream_lines[$idx]} == "$unexpected" ]]; then 270 | batslib_print_kv_single 5 \ 271 | 'index' "$idx" \ 272 | 'line' "${stream_lines[$idx]}" \ 273 | | batslib_decorate 'line should differ' \ 274 | | fail 275 | fi 276 | fi 277 | else 278 | # Line contained in output/error stream. 279 | if (( is_mode_regexp )); then 280 | local -i idx 281 | for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do 282 | if [[ ${stream_lines[$idx]} =~ $unexpected ]]; then 283 | { local -ar single=( 'regexp' "$unexpected" 'index' "$idx" ) 284 | local -a may_be_multi=( "${stream_type}" "${!stream_type}" ) 285 | local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" 286 | batslib_print_kv_single "$width" "${single[@]}" 287 | if batslib_is_single_line "${may_be_multi[1]}"; then 288 | batslib_print_kv_single "$width" "${may_be_multi[@]}" 289 | else 290 | may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" 291 | batslib_print_kv_multi "${may_be_multi[@]}" 292 | fi 293 | } \ 294 | | batslib_decorate 'no line should match the regular expression' \ 295 | | fail 296 | return $? 297 | fi 298 | done 299 | elif (( is_mode_partial )); then 300 | local -i idx 301 | for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do 302 | if [[ ${stream_lines[$idx]} == *"$unexpected"* ]]; then 303 | { local -ar single=( 'substring' "$unexpected" 'index' "$idx" ) 304 | local -a may_be_multi=( "${stream_type}" "${!stream_type}" ) 305 | local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" 306 | batslib_print_kv_single "$width" "${single[@]}" 307 | if batslib_is_single_line "${may_be_multi[1]}"; then 308 | batslib_print_kv_single "$width" "${may_be_multi[@]}" 309 | else 310 | may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" 311 | batslib_print_kv_multi "${may_be_multi[@]}" 312 | fi 313 | } \ 314 | | batslib_decorate 'no line should contain substring' \ 315 | | fail 316 | return $? 317 | fi 318 | done 319 | else 320 | local -i idx 321 | for (( idx = 0; idx < ${#stream_lines[@]}; ++idx )); do 322 | if [[ ${stream_lines[$idx]} == "$unexpected" ]]; then 323 | { local -ar single=( 'line' "$unexpected" 'index' "$idx" ) 324 | local -a may_be_multi=( "${stream_type}" "${!stream_type}" ) 325 | local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" 326 | batslib_print_kv_single "$width" "${single[@]}" 327 | if batslib_is_single_line "${may_be_multi[1]}"; then 328 | batslib_print_kv_single "$width" "${may_be_multi[@]}" 329 | else 330 | may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" 331 | batslib_print_kv_multi "${may_be_multi[@]}" 332 | fi 333 | } \ 334 | | batslib_decorate "line should not be in ${stream_type}" \ 335 | | fail 336 | return $? 337 | fi 338 | done 339 | fi 340 | fi 341 | } 342 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bats-assert 2 | 3 | [![License](https://img.shields.io/npm/l/bats-assert.svg)](https://github.com/bats-core/bats-assert/blob/master/LICENSE) 4 | [![GitHub release](https://img.shields.io/github/release/bats-core/bats-assert.svg)](https://github.com/bats-core/bats-assert/releases/latest) 5 | [![npm release](https://img.shields.io/npm/v/bats-assert.svg)](https://www.npmjs.com/package/bats-assert) 6 | [![Tests](https://github.com/bats-core/bats-assert/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/bats-core/bats-assert/actions/workflows/test.yml) 7 | 8 | `bats-assert` is a helper library providing common assertions for [Bats][bats]. 9 | 10 | - [Install](#install) 11 | - [Usage](#usage) 12 | - [Options](#options) 13 | - [Full Assertion API](#full-assertion-api) 14 | 15 | In the context of this project, an [assertion][wikipedia-assertions] is a function that perform a test and returns `1` on failure or `0` on success. 16 | To make debugging easier, the assertion also outputs relevant information on failure. 17 | The output is [formatted][bats-support-output] for readability. 18 | To make assertions usable outside of `@test` blocks, the output is sent to [stderr][wikipedia-stderr]. 19 | 20 | The most recent invocation of Bats' `run` function is used for testing assertions on output and status code. 21 | 22 | [wikipedia-assertions]: https://en.wikipedia.org/wiki/Assertion_(software_development) 23 | [wikipedia-stderr]: https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr) 24 | 25 | ## Install 26 | 27 | This project has one dependency, for output formatting: [`bats-support`][bats-support] 28 | 29 | Read the [shared documentation][bats-docs] to learn how to install and load both libraries. 30 | 31 | ## Usage 32 | 33 | This project provides the following functions: 34 | 35 | - [assert](#assert) / [refute](#refute) Assert a given expression evaluates to `true` or `false`. 36 | - [assert_equal](#assert_equal) Assert two parameters are equal. 37 | - [assert_not_equal](#assert_not_equal) Assert two parameters are not equal. 38 | - [assert_success](#assert_success) / [assert_failure](#assert_failure) Assert exit status is `0` or `1`. 39 | - [assert_output](#assert_output) / [refute_output](#refute_output) Assert output does (or does not) contain given content. 40 | - [assert_line](#assert_line) / [refute_line](#refute_line) Assert a specific line of output does (or does not) contain given content. 41 | - [assert_regex](#assert_regex) / [refute_regex](#refute_regex) Assert a parameter does (or does not) match given pattern. 42 | - [assert_stderr](#assert_stderr) / [refute_stderr](#refute_stderr) Assert stderr does (or does not) contain given content. 43 | - [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. 44 | 45 | These commands are described in more detail below. 46 | 47 | ## Options 48 | 49 | For functions that have options, `--` disables option parsing for the remaining arguments to allow using arguments identical to one of the allowed options. 50 | 51 | ```bash 52 | assert_output -- '-p' 53 | ``` 54 | 55 | Specifying `--` as an argument is similarly simple. 56 | 57 | ```bash 58 | refute_line -- '--' 59 | ``` 60 | 61 | 62 | ## Full Assertion API 63 | 64 | ### `assert` 65 | 66 | Fail if the given expression evaluates to false. 67 | 68 | > _**Note**: 69 | > The expression must be a simple command. 70 | > [Compound commands][bash-comp-cmd], such as `[[`, can be used only when executed with `bash -c`._ 71 | 72 | ```bash 73 | @test 'assert()' { 74 | assert [ 1 -lt 0 ] 75 | } 76 | ``` 77 | 78 | On failure, the failed expression is displayed. 79 | 80 | ``` 81 | -- assertion failed -- 82 | expression : [ 1 -lt 0 ] 83 | -- 84 | ``` 85 | 86 | 87 | ### `refute` 88 | 89 | Fail if the given expression evaluates to true. 90 | 91 | > _**Note** 92 | > The expression must be a simple command. 93 | > [Compound commands][bash-comp-cmd], such as `[[`, can be used only when executed with `bash -c`._ 94 | 95 | ```bash 96 | @test 'refute()' { 97 | refute [ 1 -gt 0 ] 98 | } 99 | ``` 100 | 101 | On failure, the successful expression is displayed. 102 | 103 | ``` 104 | -- assertion succeeded, but it was expected to fail -- 105 | expression : [ 1 -gt 0 ] 106 | -- 107 | ``` 108 | 109 | 110 | ### `assert_equal` 111 | 112 | Fail if the two parameters, actual and expected value respectively, do not equal. 113 | 114 | ```bash 115 | @test 'assert_equal()' { 116 | assert_equal 'have' 'want' 117 | } 118 | ``` 119 | 120 | On failure, the expected and actual values are displayed. 121 | 122 | ``` 123 | -- values do not equal -- 124 | expected : want 125 | actual : have 126 | -- 127 | ``` 128 | 129 | If either value is longer than one line both are displayed in *multi-line* format. 130 | 131 | 132 | ### `assert_not_equal` 133 | 134 | Fail if the two parameters, actual and unexpected value respectively, are equal. 135 | 136 | ```bash 137 | @test 'assert_not_equal()' { 138 | assert_not_equal 'foobar' 'foobar' 139 | } 140 | ``` 141 | 142 | On failure, the expected and actual values are displayed. 143 | 144 | ``` 145 | -- values should not be equal -- 146 | unexpected : foobar 147 | actual : foobar 148 | -- 149 | ``` 150 | 151 | If either value is longer than one line both are displayed in *multi-line* format. 152 | 153 | 154 | ### `assert_success` 155 | 156 | Fail if `$status` is not 0. 157 | 158 | ```bash 159 | @test 'assert_success() status only' { 160 | run bash -c "echo 'Error!'; exit 1" 161 | assert_success 162 | } 163 | ``` 164 | 165 | On failure, `$status` and `$output` are displayed. 166 | 167 | ``` 168 | -- command failed -- 169 | status : 1 170 | output : Error! 171 | -- 172 | ``` 173 | 174 | If `$output` is longer than one line, it is displayed in *multi-line* format. 175 | 176 | 177 | ### `assert_failure` 178 | 179 | Fail if `$status` is 0. 180 | 181 | ```bash 182 | @test 'assert_failure() status only' { 183 | run echo 'Success!' 184 | assert_failure 185 | } 186 | ``` 187 | 188 | On failure, `$output` is displayed. 189 | 190 | ``` 191 | -- command succeeded, but it was expected to fail -- 192 | output : Success! 193 | -- 194 | ``` 195 | 196 | If `$output` is longer than one line, it is displayed in *multi-line* format. 197 | 198 | #### Expected status 199 | 200 | When one parameter is specified, fail if `$status` does not equal the expected status specified by the parameter. 201 | 202 | ```bash 203 | @test 'assert_failure() with expected status' { 204 | run bash -c "echo 'Error!'; exit 1" 205 | assert_failure 2 206 | } 207 | ``` 208 | 209 | On failure, the expected and actual status, and `$output` are displayed. 210 | 211 | ``` 212 | -- command failed as expected, but status differs -- 213 | expected : 2 214 | actual : 1 215 | output : Error! 216 | -- 217 | ``` 218 | 219 | If `$output` is longer than one line, it is displayed in *multi-line* format. 220 | 221 | 222 | ### `assert_output` 223 | 224 | This function helps to verify that a command or function produces the correct output by checking that the specified expected output matches the actual output. 225 | Matching can be literal (default), partial or regular expression. 226 | This function is the logical complement of `refute_output`. 227 | 228 | #### Literal matching 229 | 230 | By default, literal matching is performed. 231 | The assertion fails if `$output` does not equal the expected output. 232 | 233 | ```bash 234 | @test 'assert_output()' { 235 | run echo 'have' 236 | assert_output 'want' 237 | } 238 | ``` 239 | 240 | On failure, the expected and actual output are displayed. 241 | 242 | ``` 243 | -- output differs -- 244 | expected : want 245 | actual : have 246 | -- 247 | ``` 248 | 249 | If either value is longer than one line both are displayed in *multi-line* format. 250 | 251 | #### Existence 252 | 253 | To assert that any (non-empty) output exists at all, simply omit the matching argument. 254 | 255 | ```bash 256 | @test 'assert_output()' { 257 | run echo 'have' 258 | assert_output 259 | } 260 | ``` 261 | 262 | On failure, an error message is displayed. 263 | 264 | ``` 265 | -- no output -- 266 | expected non-empty output, but output was empty 267 | -- 268 | ``` 269 | 270 | #### Partial matching 271 | 272 | Partial matching can be enabled with the `--partial` option (`-p` for short). 273 | When used, the assertion fails if the expected *substring* is not found in `$output`. 274 | 275 | ```bash 276 | @test 'assert_output() partial matching' { 277 | run echo 'ERROR: no such file or directory' 278 | assert_output --partial 'SUCCESS' 279 | } 280 | ``` 281 | 282 | On failure, the substring and the output are displayed. 283 | 284 | ``` 285 | -- output does not contain substring -- 286 | substring : SUCCESS 287 | output : ERROR: no such file or directory 288 | -- 289 | ``` 290 | 291 | This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. 292 | An error is displayed when used simultaneously. 293 | 294 | #### Regular expression matching 295 | 296 | Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 297 | When used, the assertion fails if the *[extended regular expression]* does not match `$output`. 298 | 299 | [extended regular expression]: https://en.wikibooks.org/wiki/Regular_Expressions/POSIX-Extended_Regular_Expressions 300 | 301 | > [!IMPORTANT] 302 | > Bash [doesn't support](https://stackoverflow.com/a/48898886/5432315) certain parts of regular expressions you may be used to: 303 | > * `\d` `\D` `\s` `\S` `\w` `\W` — these can be replaced with POSIX character class equivalents `[[:digit:]]`, `[^[:digit:]]`, `[[:space:]]`, `[^[:space:]]`, `[_[:alnum:]]`, and `[^_[:alnum:]]`, respectively. (Notice the last case, where the `[:alnum:]` POSIX character class is augmented with underscore to be exactly equivalent to the Perl `\w` shorthand.) 304 | > * Non-greedy matching. You can sometimes replace `a.*?b` with something like `a[^ab]*b` to get a similar effect in practice, though the two are not exactly equivalent. 305 | > * Non-capturing parentheses `(?:...)`. In the trivial case, just use capturing parentheses `(...)` instead; though of course, if you use capture groups and/or backreferences, this will renumber your capture groups. 306 | > * Lookarounds like `(?<=before)` or `(?!after)`. (In fact anything with `(?` is a Perl extension.) There is no simple general workaround for these, though you can sometimes rephrase your problem into one where lookarounds can be avoided. 307 | 308 | > _**Note**: 309 | > The anchors `^` and `$` bind to the beginning and the end of the entire output (not individual lines), respectively._ 310 | 311 | ```bash 312 | @test 'assert_output() regular expression matching' { 313 | run echo 'Foobar 0.1.0' 314 | assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 315 | } 316 | ``` 317 | 318 | On failure, the regular expression and the output are displayed. 319 | 320 | ``` 321 | -- regular expression does not match output -- 322 | regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 323 | output : Foobar 0.1.0 324 | -- 325 | ``` 326 | 327 | An error is displayed if the specified extended regular expression is invalid. 328 | 329 | This option and partial matching (`--partial` or `-p`) are mutually exclusive. 330 | An error is displayed when used simultaneously. 331 | 332 | #### Standard Input, HereDocs and HereStrings 333 | 334 | The expected output can be specified via standard input (also heredoc/herestring) with the `-`/`--stdin` option. 335 | 336 | ```bash 337 | @test 'assert_output() with pipe' { 338 | run echo 'hello' 339 | echo 'hello' | assert_output - 340 | } 341 | 342 | @test 'assert_output() with herestring' { 343 | run echo 'hello' 344 | assert_output - <<< hello 345 | } 346 | ``` 347 | 348 | 349 | ### `refute_output` 350 | 351 | This function helps to verify that a command or function produces the correct output by checking that the specified unexpected output does not match the actual output. 352 | Matching can be literal (default), partial or regular expression. 353 | This function is the logical complement of `assert_output`. 354 | 355 | #### Literal matching 356 | 357 | By default, literal matching is performed. 358 | The assertion fails if `$output` equals the unexpected output. 359 | 360 | ```bash 361 | @test 'refute_output()' { 362 | run echo 'want' 363 | refute_output 'want' 364 | } 365 | ``` 366 | 367 | On failure, the output is displayed. 368 | 369 | ``` 370 | -- output equals, but it was expected to differ -- 371 | output : want 372 | -- 373 | ``` 374 | 375 | If output is longer than one line it is displayed in *multi-line* format. 376 | 377 | #### Existence 378 | 379 | To assert that there is no output at all, simply omit the matching argument. 380 | 381 | ```bash 382 | @test 'refute_output()' { 383 | run foo --silent 384 | refute_output 385 | } 386 | ``` 387 | 388 | On failure, an error message is displayed. 389 | 390 | ``` 391 | -- unexpected output -- 392 | expected no output, but output was non-empty 393 | -- 394 | ``` 395 | 396 | #### Partial matching 397 | 398 | Partial matching can be enabled with the `--partial` option (`-p` for short). 399 | When used, the assertion fails if the unexpected *substring* is found in `$output`. 400 | 401 | ```bash 402 | @test 'refute_output() partial matching' { 403 | run echo 'ERROR: no such file or directory' 404 | refute_output --partial 'ERROR' 405 | } 406 | ``` 407 | 408 | On failure, the substring and the output are displayed. 409 | 410 | ``` 411 | -- output should not contain substring -- 412 | substring : ERROR 413 | output : ERROR: no such file or directory 414 | -- 415 | ``` 416 | 417 | This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. 418 | An error is displayed when used simultaneously. 419 | 420 | #### Regular expression matching 421 | 422 | Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 423 | When used, the assertion fails if the *extended regular expression* matches `$output`. 424 | 425 | > _**Note**: 426 | > The anchors `^` and `$` bind to the beginning and the end of the entire output (not individual lines), respectively._ 427 | 428 | ```bash 429 | @test 'refute_output() regular expression matching' { 430 | run echo 'Foobar v0.1.0' 431 | refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 432 | } 433 | ``` 434 | 435 | On failure, the regular expression and the output are displayed. 436 | 437 | ``` 438 | -- regular expression should not match output -- 439 | regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 440 | output : Foobar v0.1.0 441 | -- 442 | ``` 443 | 444 | An error is displayed if the specified extended regular expression is invalid. 445 | 446 | This option and partial matching (`--partial` or `-p`) are mutually exclusive. 447 | An error is displayed when used simultaneously. 448 | 449 | #### Standard Input, HereDocs and HereStrings 450 | 451 | The unexpected output can be specified via standard input (also heredoc/herestring) with the `-`/`--stdin` option. 452 | 453 | ```bash 454 | @test 'refute_output() with pipe' { 455 | run echo 'hello' 456 | echo 'world' | refute_output - 457 | } 458 | 459 | @test 'refute_output() with herestring' { 460 | run echo 'hello' 461 | refute_output - <<< world 462 | } 463 | ``` 464 | 465 | 466 | ### `assert_line` 467 | 468 | Similarly to `assert_output`, this function helps to verify that a command or function produces the correct output. 469 | It checks that the expected line appears in the output (default) or in a specific line of it. 470 | Matching can be literal (default), partial or regular expression. 471 | This function is the logical complement of `refute_line`. 472 | 473 | > _**Warning**: 474 | > Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, 475 | > causing line indices to change and preventing testing for empty lines._ 476 | 477 | [bats-93]: https://github.com/sstephenson/bats/pull/93 478 | 479 | #### Looking for a line in the output 480 | 481 | By default, the entire output is searched for the expected line. 482 | The assertion fails if the expected line is not found in `${lines[@]}`. 483 | 484 | ```bash 485 | @test 'assert_line() looking for line' { 486 | run echo $'have-0\nhave-1\nhave-2' 487 | assert_line 'want' 488 | } 489 | ``` 490 | 491 | On failure, the expected line and the output are displayed. 492 | 493 | > _**Warning**: 494 | > The output displayed does not contain empty lines. 495 | > See the Warning above for more._ 496 | 497 | ``` 498 | -- output does not contain line -- 499 | line : want 500 | output (3 lines): 501 | have-0 502 | have-1 503 | have-2 504 | -- 505 | ``` 506 | 507 | If output is not longer than one line, it is displayed in *two-column* format. 508 | 509 | #### Matching a specific line 510 | 511 | When the `--index ` option is used (`-n ` for short), the expected line is matched only against the line identified by the given index. 512 | The assertion fails if the expected line does not equal `${lines[]}`. 513 | 514 | ```bash 515 | @test 'assert_line() specific line' { 516 | run echo $'have-0\nhave-1\nhave-2' 517 | assert_line --index 1 'want-1' 518 | } 519 | ``` 520 | 521 | On failure, the index and the compared lines are displayed. 522 | 523 | ``` 524 | -- line differs -- 525 | index : 1 526 | expected : want-1 527 | actual : have-1 528 | -- 529 | ``` 530 | 531 | #### Partial matching 532 | 533 | Partial matching can be enabled with the `--partial` option (`-p` for short). 534 | When used, a match fails if the expected *substring* is not found in the matched line. 535 | 536 | ```bash 537 | @test 'assert_line() partial matching' { 538 | run echo $'have 1\nhave 2\nhave 3' 539 | assert_line --partial 'want' 540 | } 541 | ``` 542 | 543 | On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line. 544 | 545 | ``` 546 | -- no output line contains substring -- 547 | substring : want 548 | output (3 lines): 549 | have 1 550 | have 2 551 | have 3 552 | -- 553 | ``` 554 | 555 | This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. 556 | An error is displayed when used simultaneously. 557 | 558 | #### Regular expression matching 559 | 560 | Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 561 | When used, a match fails if the *extended regular expression* does not match the line being tested. 562 | 563 | > _**Note**: 564 | > As expected, the anchors `^` and `$` bind to the beginning and the end of the matched line, respectively._ 565 | 566 | ```bash 567 | @test 'assert_line() regular expression matching' { 568 | run echo $'have-0\nhave-1\nhave-2' 569 | assert_line --index 1 --regexp '^want-[0-9]$' 570 | } 571 | ``` 572 | 573 | On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line. 574 | 575 | ``` 576 | -- regular expression does not match line -- 577 | index : 1 578 | regexp : ^want-[0-9]$ 579 | line : have-1 580 | -- 581 | ``` 582 | 583 | An error is displayed if the specified extended regular expression is invalid. 584 | 585 | This option and partial matching (`--partial` or `-p`) are mutually exclusive. 586 | An error is displayed when used simultaneously. 587 | 588 | 589 | ### `refute_line` 590 | 591 | Similarly to `refute_output`, this function helps to verify that a command or function produces the correct output. 592 | It checks that the unexpected line does not appear in the output (default) or in a specific line of it. 593 | Matching can be literal (default), partial or regular expression. 594 | This function is the logical complement of `assert_line`. 595 | 596 | > _**Warning**: 597 | > Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, 598 | > causing line indices to change and preventing testing for empty lines._ 599 | 600 | [bats-93]: https://github.com/sstephenson/bats/pull/93 601 | 602 | #### Looking for a line in the output 603 | 604 | By default, the entire output is searched for the unexpected line. 605 | The assertion fails if the unexpected line is found in `${lines[@]}`. 606 | 607 | ```bash 608 | @test 'refute_line() looking for line' { 609 | run echo $'have-0\nwant\nhave-2' 610 | refute_line 'want' 611 | } 612 | ``` 613 | 614 | On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed. 615 | 616 | > _**Warning**: 617 | > The output displayed does not contain empty lines. 618 | > See the Warning above for more._ 619 | 620 | ``` 621 | -- line should not be in output -- 622 | line : want 623 | index : 1 624 | output (3 lines): 625 | have-0 626 | > want 627 | have-2 628 | -- 629 | ``` 630 | 631 | If output is not longer than one line, it is displayed in *two-column* format. 632 | 633 | #### Matching a specific line 634 | 635 | When the `--index ` option is used (`-n ` for short), the unexpected line is matched only against the line identified by the given index. 636 | The assertion fails if the unexpected line equals `${lines[]}`. 637 | 638 | ```bash 639 | @test 'refute_line() specific line' { 640 | run echo $'have-0\nwant-1\nhave-2' 641 | refute_line --index 1 'want-1' 642 | } 643 | ``` 644 | 645 | On failure, the index and the unexpected line are displayed. 646 | 647 | ``` 648 | -- line should differ -- 649 | index : 1 650 | line : want-1 651 | -- 652 | ``` 653 | 654 | #### Partial matching 655 | 656 | Partial matching can be enabled with the `--partial` option (`-p` for short). 657 | When used, a match fails if the unexpected *substring* is found in the matched line. 658 | 659 | ```bash 660 | @test 'refute_line() partial matching' { 661 | run echo $'have 1\nwant 2\nhave 3' 662 | refute_line --partial 'want' 663 | } 664 | ``` 665 | 666 | On failure, in addition to the details of literal matching, the substring is also displayed. 667 | When used with `--index ` the substring replaces the unexpected line. 668 | 669 | ``` 670 | -- no line should contain substring -- 671 | substring : want 672 | index : 1 673 | output (3 lines): 674 | have 1 675 | > want 2 676 | have 3 677 | -- 678 | ``` 679 | 680 | This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. 681 | An error is displayed when used simultaneously. 682 | 683 | #### Regular expression matching 684 | 685 | Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 686 | When used, a match fails if the *extended regular expression* matches the line being tested. 687 | 688 | > _**Note**: 689 | > As expected, the anchors `^` and `$` bind to the beginning and the end of the matched line, respectively._ 690 | 691 | ```bash 692 | @test 'refute_line() regular expression matching' { 693 | run echo $'Foobar v0.1.0\nRelease date: 2015-11-29' 694 | refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 695 | } 696 | ``` 697 | 698 | On failure, in addition to the details of literal matching, the regular expression is also displayed. 699 | When used with `--index ` the regular expression replaces the unexpected line. 700 | 701 | ``` 702 | -- regular expression should not match line -- 703 | index : 0 704 | regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 705 | line : Foobar v0.1.0 706 | -- 707 | ``` 708 | 709 | An error is displayed if the specified extended regular expression is invalid. 710 | 711 | This option and partial matching (`--partial` or `-p`) are mutually exclusive. 712 | An error is displayed when used simultaneously. 713 | 714 | ### `assert_regex` 715 | 716 | This function is similar to `assert_equal` but uses pattern matching instead of 717 | equality, by wrapping `[[ value =~ pattern ]]`. 718 | 719 | Fail if the value (first parameter) does not match the pattern (second 720 | parameter). 721 | 722 | ```bash 723 | @test 'assert_regex()' { 724 | assert_regex 'what' 'x$' 725 | } 726 | ``` 727 | 728 | On failure, the value and the pattern are displayed. 729 | 730 | ``` 731 | -- values does not match regular expression -- 732 | value : what 733 | pattern : x$ 734 | -- 735 | ``` 736 | 737 | If the value is longer than one line then it is displayed in *multi-line* 738 | format. 739 | 740 | An error is displayed if the specified extended regular expression is invalid. 741 | 742 | For description of the matching behavior, refer to the documentation of the 743 | `=~` operator in the [Bash manual][bash-conditional]. 744 | 745 | > _**Note**: 746 | > the `BASH_REMATCH` array is available immediately after the assertion succeeds but is fragile; 747 | > i.e. prone to being overwritten as a side effect of other actions._ 748 | 749 | ### `refute_regex` 750 | 751 | This function is similar to `refute_equal` but uses pattern matching instead of 752 | equality, by wrapping `! [[ value =~ pattern ]]`. 753 | 754 | Fail if the value (first parameter) matches the pattern (second parameter). 755 | 756 | ```bash 757 | @test 'refute_regex()' { 758 | refute_regex 'WhatsApp' 'Threema' 759 | } 760 | ``` 761 | 762 | On failure, the value, the pattern and the match are displayed. 763 | 764 | ```bash 765 | @test 'refute_regex()' { 766 | refute_regex 'WhatsApp' 'What.' 767 | } 768 | 769 | -- value matches regular expression -- 770 | value : WhatsApp 771 | pattern : What. 772 | match : Whats 773 | case : sensitive 774 | -- 775 | ``` 776 | 777 | If the value or pattern is longer than one line then it is displayed in 778 | *multi-line* format. 779 | 780 | An error is displayed if the specified extended regular expression is invalid. 781 | 782 | For description of the matching behavior, refer to the documentation of the 783 | `=~` operator in the [Bash manual][bash-conditional]. 784 | 785 | > _**Note**: 786 | > the `BASH_REMATCH` array is available immediately after the assertion fails but is fragile; 787 | > i.e. prone to being overwritten as a side effect of other actions like calling `run`. 788 | > Thus, it's good practice to avoid using `BASH_REMATCH` in conjunction with `refute_regex()`. 789 | > The valuable information the array contains is the matching part of the value which is printed in the failing test log, as mentioned above._ 790 | 791 | ### `assert_stderr` 792 | 793 | > _**Note**: 794 | > `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. 795 | > If not, `$stderr` will be empty, causing `assert_stderr` to always fail. 796 | 797 | Similarly to `assert_output`, this function verifies that a command or function produces the expected stderr. 798 | The stderr matching can be literal (the default), partial or by regular expression. 799 | The expected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. 800 | 801 | #### Literal matching 802 | 803 | By default, literal matching is performed. 804 | The assertion fails if `$stderr` does not equal the expected stderr. 805 | 806 | ```bash 807 | echo_err() { 808 | echo "$@" >&2 809 | } 810 | 811 | @test 'assert_stderr()' { 812 | run --separate-stderr echo_err 'have' 813 | assert_stderr 'want' 814 | } 815 | 816 | @test 'assert_stderr() with pipe' { 817 | run --separate-stderr echo_err 'hello' 818 | echo_err 'hello' | assert_stderr - 819 | } 820 | 821 | @test 'assert_stderr() with herestring' { 822 | run --separate-stderr echo_err 'hello' 823 | assert_stderr - <<< hello 824 | } 825 | ``` 826 | 827 | On failure, the expected and actual stderr are displayed. 828 | 829 | ``` 830 | -- stderr differs -- 831 | expected : want 832 | actual : have 833 | -- 834 | ``` 835 | 836 | #### Existence 837 | 838 | To assert that any stderr exists at all, omit the `expected` argument. 839 | 840 | ```bash 841 | @test 'assert_stderr()' { 842 | run --separate-stderr echo_err 'have' 843 | assert_stderr 844 | } 845 | ``` 846 | 847 | On failure, an error message is displayed. 848 | 849 | ``` 850 | -- no stderr -- 851 | expected non-empty stderr, but stderr was empty 852 | -- 853 | ``` 854 | 855 | #### Partial matching 856 | 857 | Partial matching can be enabled with the `--partial` option (`-p` for short). 858 | When used, the assertion fails if the expected _substring_ is not found in `$stderr`. 859 | 860 | ```bash 861 | @test 'assert_stderr() partial matching' { 862 | run --separate-stderr echo_err 'ERROR: no such file or directory' 863 | assert_stderr --partial 'SUCCESS' 864 | } 865 | ``` 866 | 867 | On failure, the substring and the stderr are displayed. 868 | 869 | ``` 870 | -- stderr does not contain substring -- 871 | substring : SUCCESS 872 | stderr : ERROR: no such file or directory 873 | -- 874 | ``` 875 | 876 | #### Regular expression matching 877 | 878 | Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 879 | When used, the assertion fails if the *extended regular expression* does not match `$stderr`. 880 | 881 | *Note: The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire stderr; not individual lines.* 882 | 883 | ```bash 884 | @test 'assert_stderr() regular expression matching' { 885 | run --separate-stderr echo_err 'Foobar 0.1.0' 886 | assert_stderr --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' 887 | } 888 | ``` 889 | 890 | On failure, the regular expression and the stderr are displayed. 891 | 892 | ``` 893 | -- regular expression does not match stderr -- 894 | regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ 895 | stderr : Foobar 0.1.0 896 | -- 897 | ``` 898 | 899 | ### `refute_stderr` 900 | 901 | > _**Note**: 902 | > `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. 903 | > If not, `$stderr` will be empty, causing `refute_stderr` to always pass. 904 | 905 | Similar to `refute_output`, this function verifies that a command or function does not produce the unexpected stderr. 906 | (It is the logical complement of `assert_stderr`.) 907 | The stderr matching can be literal (the default), partial or by regular expression. 908 | The unexpected stderr can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. 909 | 910 | ### `assert_stderr_line` 911 | 912 | > _**Note**: 913 | > `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. 914 | > If not, `$stderr` will be empty, causing `assert_stderr_line` to always fail. 915 | 916 | Similarly to `assert_stderr`, this function verifies that a command or function produces the expected stderr. 917 | It checks that the expected line appears in the stderr (default) or at a specific line number. 918 | Matching can be literal (default), partial or regular expression. 919 | This function is the logical complement of `refute_stderr_line`. 920 | 921 | #### Looking for a line in the stderr 922 | 923 | By default, the entire stderr is searched for the expected line. 924 | The assertion fails if the expected line is not found in `${stderr_lines[@]}`. 925 | 926 | ```bash 927 | echo_err() { 928 | echo "$@" >&2 929 | } 930 | 931 | @test 'assert_stderr_line() looking for line' { 932 | run --separate-stderr echo_err $'have-0\nhave-1\nhave-2' 933 | assert_stderr_line 'want' 934 | } 935 | ``` 936 | 937 | On failure, the expected line and the stderr are displayed. 938 | 939 | ``` 940 | -- stderr does not contain line -- 941 | line : want 942 | stderr (3 lines): 943 | have-0 944 | have-1 945 | have-2 946 | -- 947 | ``` 948 | 949 | #### Matching a specific line 950 | 951 | When the `--index ` option is used (`-n ` for short), the expected line is matched only against the line identified by the given index. 952 | The assertion fails if the expected line does not equal `${stderr_lines[]}`. 953 | 954 | ```bash 955 | @test 'assert_stderr_line() specific line' { 956 | run --separate-stderr echo_err $'have-0\nhave-1\nhave-2' 957 | assert_stderr_line --index 1 'want-1' 958 | } 959 | ``` 960 | 961 | On failure, the index and the compared stderr_lines are displayed. 962 | 963 | ``` 964 | -- line differs -- 965 | index : 1 966 | expected : want-1 967 | actual : have-1 968 | -- 969 | ``` 970 | 971 | #### Partial matching 972 | 973 | Partial matching can be enabled with the `--partial` option (`-p` for short). 974 | When used, a match fails if the expected *substring* is not found in the matched line. 975 | 976 | ```bash 977 | @test 'assert_stderr_line() partial matching' { 978 | run --separate-stderr echo_err $'have 1\nhave 2\nhave 3' 979 | assert_stderr_line --partial 'want' 980 | } 981 | ``` 982 | 983 | On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line. 984 | 985 | ``` 986 | -- no stderr line contains substring -- 987 | substring : want 988 | stderr (3 lines): 989 | have 1 990 | have 2 991 | have 3 992 | -- 993 | ``` 994 | 995 | #### Regular expression matching 996 | 997 | Regular expression matching can be enabled with the `--regexp` option (`-e` for short). 998 | When used, a match fails if the *extended regular expression* does not match the line being tested. 999 | 1000 | *Note: As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.* 1001 | 1002 | ```bash 1003 | @test 'assert_stderr_line() regular expression matching' { 1004 | run --separate-stderr echo_err $'have-0\nhave-1\nhave-2' 1005 | assert_stderr_line --index 1 --regexp '^want-[0-9]$' 1006 | } 1007 | ``` 1008 | 1009 | On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line. 1010 | 1011 | ``` 1012 | -- regular expression does not match line -- 1013 | index : 1 1014 | regexp : ^want-[0-9]$ 1015 | line : have-1 1016 | -- 1017 | ``` 1018 | 1019 | ### `refute_stderr_line` 1020 | 1021 | > _**Note**: 1022 | > `run` has to be called with `--separate-stderr` to separate stdout and stderr into `$output` and `$stderr`. 1023 | > If not, `$stderr` will be empty, causing `refute_stderr_line` to always pass. 1024 | 1025 | Similarly to `refute_stderr`, this function helps to verify that a command or function produces the correct stderr. 1026 | It checks that the unexpected line does not appear in the stderr (default) or in a specific line of it. 1027 | Matching can be literal (default), partial or regular expression. 1028 | This function is the logical complement of `assert_stderr_line`. 1029 | 1030 | 1031 | 1032 | [bats]: https://github.com/bats-core/bats-core 1033 | [bash-comp-cmd]: https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands 1034 | [bash-conditional]: https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs 1035 | 1036 | [bats-docs]: https://bats-core.readthedocs.io/ 1037 | [bats-support-output]: https://github.com/bats-core/bats-support#output-formatting 1038 | [bats-support]: https://github.com/bats-core/bats-support 1039 | --------------------------------------------------------------------------------