#!/usr/bin/env bash set -euo pipefail curr_dir="$(cd "$(dirname "$BASH_SOURCE")"; pwd -P)" source "${curr_dir}"/../bach.sh # export BACH_DISABLED=false # export BACH_COLOR=auto # export BACH_DEBUG=false # export BACH_ASSERT_DIFF=diff # export BACH_ASSERT_IGNORE_COMMENT=true # declare -a BACH_ASSERT_DIFF_OPTS=(-u) test-rm--rf() { set +u @do-not-panic project_log_path=/tmp/project/logs rm -rf "$project_log_ptah/" # Typo here! } test-rm--rf-assert() { @do-not-panic rm -rf / # This is the actual command to run on your host! # DO NOT PANIC! By using Bach Testing Framework it won't actually run. } test-rm-your-dot-git() { @do-not-panic # Mock `find` command with certain parameters, will output two directories @mock find ~ -type d -name .git === @stdout ~/src/your-awesome-project/.git \ ~/src/code/.git # Do it, remove all .git directories find ~ -type d -name .git | xargs -- rm -rf } test-rm-your-dot-git-assert() { @do-not-panic # Verify the actual command rm -rf ~/src/your-awesome-project/.git ~/src/code/.git } test-learn-bash:-no-double-quote() { function foo() { no-double-quote $@ } # We passed ONE parameter to this function foo "a b c d" } test-learn-bash:-no-double-quote-assert() { # But the command 'no-double-quote' received FOUR parameters! no-double-quote a b c d } test-learn-bash:-double-quotes() { function foo() { double-quotes "$@" } # We passed ONE parameter to this function foo "a b c d" } test-learn-bash:-double-quotes-assert() { # Yes, the command 'double-quotes' received the correct parameter double-quotes "a b c d" } test-learn-bash:-no-double-quote-star() { @touch bar1 bar2 bar3 "bar*" function cleanup() { rm -rf $1 } # We want to remove the file "bar*", not the others cleanup "bar*" } test-learn-bash:-no-double-quote-star-assert() { # Without double quotes, all bar files are removed! rm -rf "bar*" bar1 bar2 bar3 } test-learn-bash:-double-quote-star() { @touch bar1 bar2 bar3 "bar*" function cleanup() { rm -rf "$1" } # We want to remove the file "bar*", not the others cleanup "bar*" } test-learn-bash:-double-quote-star-assert() { # Yes, with double quotes, only the file "bar*" is removed rm -rf "bar*" } test-output-function-@out() { @out out1 @echo out2 | @out @out "out3 " < usr/bin/sha1sum @chmod +x usr/bin/sha1sum } test-bach-real-path-typical() { setup-bach-real-path @rm usr/bin/sha1sum @echo '#!/bin/bash' > bin/shasum @chmod +x bin/shasum .bach.real-path 'shasum|sha1sum' } test-bach-real-path-typical-assert() { @out bin/shasum } test-bach-real-path-fallback() { setup-bach-real-path .bach.real-path sha1sum .bach.real-path 'shasum|sha1sum' } test-bach-real-path-fallback-assert() { @out usr/bin/sha1sum @out usr/bin/sha1sum } test-bach-real-path-not-found() { setup-bach-real-path .bach.real-path foobar } test-bach-real-path-not-found-assert() { @fail } test-@real-function() { @unset -f .bach.real-path @mock .bach.real-path md5sum === fake-md5sum @real md5sum @real md5sum --version } test-@real-function-assert() { fake-md5sum fake-md5sum --version } test-@real-function-command-not-found() { @unset -f .bach.real-path @mock .bach.real-path md5sum === @stdout "" @real md5sum file.txt } test-@real-function-command-not-found-assert() { @dryrun md5sum_not_found file.txt } test-@real-builtin-command() { @real command -v builtin } test-@real-builtin-command-assert() { @echo builtin } test-@real-builtin-declare() { @real declare answer=42 @echo "answer is $answer" } test-@real-builtin-declare-assert() { @echo answer is 42 } test-@real-builtin-builtin() { @real builtin type -t @command } test-@real-builtin-builtin-assert() { @echo function } test-@real-builtin-type() { @real type -t @command } test-@real-builtin-type-assert() { @echo function } test-@real-builtin-printf() { @real printf "Hello, %s" world } test-@real-builtin-printf-assert() { @echo -n "Hello, world" } test-@real-should-not-invoke-these-builtin-commands() { # bg|fc|fg|jobs|read|ulimit|umask|wait @real bg 42 @real fc -l @real fg 42 @real jobs -l @real ulimit -a @real umask 022 @real wait 42 } test-@real-should-not-invoke-these-builtin-commands-assert() { @echo bg 42 @echo fc -l @echo fg 42 @echo jobs -l @echo ulimit -a @echo umask 022 @echo wait 42 } test-@real-should-not-invoke-bach-api-@command() { @real @command -v builtin } test-@real-should-not-invoke-bach-api-@command-assert() { @command_not_found -v builtin } this_variable_exists="" this_variable_exists_in_test="" this_variable_exists_in_assert="" @setup { set -euo pipefail declare -g this_variable_exists=in_test_and_assert declare -g this_variable_exists_in_test="" declare -g this_variable_exists_in_assert="" } @setup-test { @mock git config --get branch.master.remote === @stdout "remote-master" @mock git rev-parse --abbrev-ref HEAD <<-MOCK @stdout branch-name MOCK declare -g this_variable_exists_in_test=in_test } @setup-assert { declare -g this_variable_exists_in_assert=in_assert } test-bach-framework-setup-functions() { if [[ -n "$this_variable_exists" ]]; then @echo "$this_variable_exists"; else @echo TEST setup fail; fi if [[ -n "$this_variable_exists_in_test" ]]; then @echo setup-test; else @echo TEST setup setup-test fail; fi if [[ -z "$this_variable_exists_in_assert" ]]; then @echo setup-assert; else @echo TEST should NOT setup setup-assert; fi } test-bach-framework-setup-functions-assert() { if [[ -n "$this_variable_exists" ]]; then @echo "$this_variable_exists"; else @echo ASSERT setup fail; fi if [[ -z "$this_variable_exists_in_test" ]]; then @echo setup-test; else @echo ASSERT should NOT setup setup-assert; fi if [[ -n "$this_variable_exists_in_assert" ]]; then @echo setup-assert; else @echo ASSERT setup pre-assert fail; fi } test-bach-framework-mock-commands() { @mock find . -name fn === @stdout file1 file2 ls $(find . -name fn) @mock ls file1 file2 === @stdout file2 file1 ls $(find . -name fn) | xargs -n1 -- do-something } test-bach-framework-mock-commands-assert() { ls file1 file2 do-something file2 do-something file1 } test-bach-framework-error-output() { project_path=/src/project cd "${project_path%/*}" rm -rf $project_path @err no error 2>/dev/null @err error 2>&1 1>/dev/null @mockfalse ls /bin ls /bin &>/dev/null || @stdout "ls /foo: No such file or directory" } test-bach-framework-error-output-assert() { cd /src rm -rf /src/project @echo error @echo "ls /foo: No such file or directory" } function load-gp() { @load_function "${curr_dir}/example-functions" gp } test-gp-running-inside-a-git-repo-and-the-branch-has-upstream() { @ignore echo load-gp @mocktrue git rev-parse --abbrev-ref --symbolic-full-name '@{u}' gp gp another-remote gp another-remote another-branch gp -f } test-gp-running-inside-a-git-repo-and-the-branch-has-upstream-assert() { git push remote-master branch-name git push another-remote branch-name git push another-remote another-branch git push -f remote-master branch-name } test-gp-running-inside-a-git-repo-and-the-branch-does-not-have-upstream() { @ignore echo load-gp @mockfalse git rev-parse --abbrev-ref --symbolic-full-name '@{u}' gp gp another-remote gp another-remote another-branch gp -f } test-gp-running-inside-a-git-repo-and-the-branch-does-not-have-upstream-assert() { git push -u remote-master branch-name git push -u another-remote branch-name git push -u another-remote another-branch git push -f -u remote-master branch-name } function load-gpr() { @load_function "${curr_dir}/example-functions" gpr @load_function "${curr_dir}/example-functions" gpw } test-gpr-typical() { @mock git log -1 --pretty=%B === @out "This is the latest commit message" @mockpipe hub pull-request -F- load-gpr gpr -f } test-gpr-typical-assert() { gp -f @out "This is the latest commit message" } test-gpw-typical() { @mock git log -1 --pretty="WIP %B" === @out "WIP This is the latest commit message" @mockpipe hub pull-request -F- load-gpr gpw -u } test-gpw-typical-assert() { gp -u @out "WIP This is the latest commit message" } function init-current-working-dir-is-not-a-repo() { @mockfalse git config --get branch.master.remote @mockfalse git rev-parse --abbrev-ref HEAD @mockfalse git rev-parse --abbrev-ref --symbolic-full-name '@{u}' } test-gp-running-not-inside-a-valid-git-repo() { load-gp init-current-working-dir-is-not-a-repo set -eu gp } test-gp-running-not-inside-a-valid-git-repo-assert() { @fail } test-gp-running-not-inside-a-valid-git-repo-again() { load-gp init-current-working-dir-is-not-a-repo set -eu gp origin } test-gp-running-not-inside-a-valid-git-repo-again-assert() { @fail } test-bach-real-command-mock-builtin() { command hostname command id -u } test-bach-real-command-mock-builtin-assert() { @dryrun hostname @dryrun id -u } test-bach-real-command-slash-bin() { @real hostname -f } test-bach-real-command-slash-bin-assert() { /bin/hostname -f } test-bach-simple-command() { hostname -f } test-bach-simple-command-assert() { hostname -f } test-must-have-an-assertion() { : do nothing } test-must-have-an-assertion-assert() { : do nothing } function load-ff() { @load_function "${curr_dir}/example-functions" ff } test-load-and-test-ff-function() { load-ff ff ff file1 } test-load-and-test-ff-function-assert() { find . -type f -name "*" find . -type f -name "*file1*" } test-load-and-test-ff-function-with-multi-filenames() { load-ff ff file1 bar "file name" } test-load-and-test-ff-function-with-multi-filenames-assert() { find . -type f -name "*file1*" -or -name "*bar*" -or -name "*file name*" } test-cannot-mock-absolute-path-of-script() { @mock /tmp/cannot-mock-this 2>&1 } test-cannot-mock-absolute-path-of-script-assert() { #printf "\e[1;31m%s\e[0;m\n" @echo 'Cannot mock an absolute path: /tmp/cannot-mock-this' @fail } test-mock-script() { @mock ./path/to/script ./path/to/script foo bar } test-mock-script-assert() { @dryrun ./path/to/script foo bar } test-cannot-mock-existed-script() { @mock ./cannot-mock-existed-script 2>&1 || return 1 @mock ./cannot-mock-existed-script 2>&1 @fail 2 It should not go here ./cannot-mock-existed-script foo bar 2>&1 } test-cannot-mock-existed-script-assert() { @echo 'Cannot mock an existed path: ./cannot-mock-existed-script' @fail } test-mock-script-with-custom-action() { @mock ./path/to/script === something ./path/to/script } test-mock-script-with-custom-action-assert() { something } test-mock-script-with-custom-complex-action() { @mock ./path/to/script <<\SCRIPT if [[ "$1" == foo ]]; then the first param is foo else other params fi SCRIPT ./path/to/script foo ./path/to/script something } test-mock-script-with-custom-complex-action-assert() { the first param is foo other params } test-bach-framework-can-get-all-tests() { @unset -f .bach.get-all-functions @sort @mock @sort -R === @real sort @mock .bach.get-all-functions <&1 @mock anything === anything @ignore foobar } test-mock-cd-builtin-command() { cd /path } test-mock-cd-builtin-command-assert() { @dryrun cd /path } test-builtin-true() { true @assert-success } test-builtin-false() { set +e false @assert-fail } test-builtin-exec() { @exec 2>&1 @mock exec exec '7>&2' } test-builtin-exec-assert() { @dryrun exec '7>&2' } test-builtin-pushd() { @exec 2>&1 pushd /path } test-builtin-pushd-assert() { @dryrun pushd /path } test-builtin-popd() { popd } test-builtin-popd-assert() { @dryrun popd } test-builtin-type() { @exec 2>&1 type -a ls } test-builtin-type-assert() { @dryrun type -a ls } test-builtin-trap() { @exec 2>&1 trap - EXIT } test-builtin-trap-assert() { @dryrun trap - EXIT } test-cannot-mock-echo-builtin-default-behavior() { say "$(echo hello)" } test-cannot-mock-echo-builtin-default-behavior-assert() { say hello } test-cannot-mock-echo-builtin-with-custom-behavior-command() { @mock echo with custom behavior === @anything 2>&7 } test-cannot-mock-echo-builtin-with-custom-behavior-command-assert() { echo "Cannot mock the builtin command: echo" @false } test-cannot-mock-echo-builtin-without-custom-behavior() { @mock echo hello echo hello } test-cannot-mock-echo-builtin-without-custom-behavior-assert() { @dryrun echo hello } test-ignore-echo-command() { @ignore echo ( declare -pf echo @real ls -l /dev/fd/ ) >&2 echo you should not see this } test-ignore-echo-command-assert() { @do-nothing } test-echo-pipe() { @capture foobar foo bar ( declare -pf echo ) >&2 echo we should see this | foobar foo bar } test-echo-pipe-assert() { @assert-capture foobar foo bar <<< "we should see this" } test-mock-function-multiple-times() { @@mock random numbers === @echo num 1 @@mock random numbers === @echo num 2 @@mock random numbers === @echo num 3 random random hello random numbers random numbers random numbers random numbers } test-mock-function-multiple-times-assert() { @dryrun random @dryrun random hello @cat << EOF num 1 num 2 num 3 num 3 EOF } test-bach-should-ignore-comment() { only-you @comment hi } test-bach-should-ignore-comment-assert() { only-you } test-xargs-withoug-double-dashes() { @mock ls === @stdout foo bar ls | xargs -n1 do-something } test-xargs-withoug-double-dashes-assert() { @dryrun xargs -n1 do-something } test-xargs-with-double-dashes() { @mock ls === @stdout foo bar ls | xargs -- do-something ls | xargs -n1 -- do-something ls | xargs -I file -- do-something file with other parameters } test-xargs-with-double-dashes-assert() { do-something foo bar do-something foo do-something bar do-something foo with other parameters do-something bar with other parameters } test-xargs-complicated-command() { @mock ls === @stdout foo bar foobar ls | xargs -n2 -- bash -c "do-something \$@" -s } test-xargs-complicated-command-assert() { bash -c 'do-something $@' -s foo bar bash -c 'do-something $@' -s foobar } test-xargs-complicated-command-curly-brackets() { @mock ls === @stdout foo bar foobar ls | xargs -n2 -- bash -c 'do-something ${@}' -s } test-xargs-complicated-command-curly-brackets-assert() { bash -c 'do-something ${@}' -s foo bar bash -c 'do-something ${@}' -s foobar } test-xargs-complicated-command-curly-brackets-expressions() { @mock ls === @stdout foo bar foobar ls | xargs -n2 -- bash -c 'do-something ${@//aaa/xxx}' -s } test-xargs-complicated-command-curly-brackets-expressions-assert() { bash -c 'do-something ${@//aaa/xxx}' -s foo bar bash -c 'do-something ${@//aaa/xxx}' -s foobar } test-xargs-complicated-command-without-dash-dash() { @mock ls === @quote foo bar foobar ls | xargs -n2 bash -c 'do-something ${@//aaa/xxx}' -s } test-xargs-complicated-command-without-dash-dash-assert() { xargs -n2 bash -c 'do-something ${@//aaa/xxx}' -s } test-xargs-with-mock-commands() { @mock ls === @quote foo bar @mock do-something foo === @stdout "Hello-FOO" ls | xargs -n1 -- do-something } test-xargs-with-mock-commands-assert() { @echo "Hello-FOO" do-something bar } test-xargs-with-spaces-in-params() { @mock ls === @quote "a b" "c d" @mock "foo-bar" "a b" === @stdout "FOO-BAR A-B" ls | xargs -n1 -- foo-bar } test-xargs-with-spaces-in-params-assert() { @echo "FOO-BAR A-B" @dryrun "foo-bar" "c d" } test-bach-framework-set--e-should-work() { set -e do-this builtin false should-not-do-this } test-bach-framework-set--e-should-work-assert() { do-this @fail } test-bach-framework-set--o-pipefail-should-work() { set -o pipefail @false | do-this | @true } test-bach-framework-set--o-pipefail-should-work-assert() { do-this @fail } test-bach-framework-mock-builtin-trap-function() { @mock trap @type -t trap trap - ERR } test-bach-framework-mock-builtin-trap-function-assert() { @echo function @dryrun trap - ERR } test-bach-framework-should-clear-the-exit-trap-in-tests() { builtin trap -p EXIT } test-bach-framework-should-clear-the-exit-trap-in-tests-assert() { : do nothing here : } test-bach-framework-should-clear-the-exit-trap-in-assertion() { : do nothing here : } test-bach-framework-should-clear-the-exit-trap-in-assertion-assert() { builtin trap -p EXIT } test-bach-framework-set--u-should-work-in-tests() { set -ue @unset foobar visit-an-undefined-variable "$foobar" should-not-show-this } test-bach-framework-set--u-should-work-in-tests-assert() { @fail } test-ASSERT-FAIL-bach-frmework-should-output-error-code-in-test() { @false } test-ASSERT-FAIL-bach-frmework-should-output-error-code-in-assertion() { @true } test-ASSERT-FAIL-bach-frmework-should-output-error-code-in-assertion-assert() { @fail } test-bach-framework-@fail-function-default-error-code-is-1() { set -euo pipefail @fail } test-bach-framework-@fail-function-default-error-code-is-1-assert() { builtin return 1 } test-bach-framework-@fail-function-return-a-certain-error-code() { set +e # needed by Bash v4.3 @fail 42 } test-bach-framework-@fail-function-return-a-certain-error-code-assert() { set +e # needed by Bash v4.3 builtin return 42 } test-bach-framework-@fail-function-with-code-and-message() { set +e # needed by Bash v4.3 @fail 43 "the error code is 43" } test-bach-framework-@fail-function-with-code-and-message-assert() { set +e # needed by Bash v4.3 @out the error code is 43 builtin return 43 } test-bach-framework-@fail-function-with-error-message() { @fail "the error code is 1" } test-bach-framework-@fail-function-with-error-message-assert() { @out the error code is 1 builtin return 1 } function mock-bach-get-all-functions() { @mock .bach.get-all-functions <&1 } test-bach-framework-could-not-set-PATH-during-testing-assert() { /bin/hostname @fail } test-bach-framework-could-not-export-PATH() { @export new_path=/bin:/usr/bin @export PATH="$new_path" @export FOOBAR=foobar ls -al "$FOOBAR" [[ "$PATH" == "$new_path" ]] } test-bach-framework-could-not-export-PATH-assert() { @fail } test-builtin-export() { export NAME=tom export MSG="Hello, $NAME" say "$MSG" } test-builtin-export-assert() { export NAME=tom export MSG="Hello, tom" say "Hello, tom" } test-ignore-export() { @ignore export export NAME=tom export MSG="Hello, $NAME" say "$MSG" } test-ignore-export-assert() { say "Hello, tom" } test-cannot-mock-export() { @mock export NAME=tom === @stdout cannot do this export NAME=tom export MSG="Hello, $NAME" say "$MSG" } test-cannot-mock-export-assert() { @fail } test-bach-framework-could-not-declare-PATH() { declare new_path=/bin:/usr/bin declare FOOBAR=foobar declare PATH="$new_path" ls -al "$FOOBAR" [[ "$PATH" == "$new_path" ]] } test-bach-framework-could-not-declare-PATH-assert() { @fail } test-bach-framework-could-not-set-PATH-for-a-command() { PATH=/bin:/usr/bin ls -al we should not see this } test-bach-framework-could-not-set-PATH-for-a-command-assert() { @fail } test-bach-framework-could-not-set-PATH-by-full-path-of-env-command() { PATH=/bin:/usr/bin @env hostname } test-bach-framework-could-not-set-PATH-by-full-path-of-env-command-assert() { @fail } test-bach-framework-COULD-set-PATH-by-full-path-of-env-command() { @env PATH=/bin:/usr/bin hostname } test-bach-framework-COULD-set-PATH-by-full-path-of-env-command-assert() { /bin/hostname } test-bach-framework-one-pipeline-dryrun-if-no-mocking() { @mock receive-something @echo hello | receive-something done } test-bach-framework-one-pipeline-dryrun-if-no-mocking-assert() { receive-something done } test-bach-framework-one-pipeline-data-directly-if-default-mocking-behavior() { @mock receive-something done @echo It will by default ignore all data coming through the pipeline | receive-something done } test-bach-framework-one-pipeline-data-directly-if-default-mocking-behavior-assert() { @do-nothing } test-bach-framework-one-pipeline-data-directly-if-customize-action() { @mock receive-something done === @stdout foobar @echo anything | receive-something done } test-bach-framework-one-pipeline-data-directly-if-customize-action-assert() { @echo foobar } test-bach-framework-two-pipelines-when-both-non-mocking-commands() { @mockpipe first-cmd done @mockpipe second-cmd too @echo something | first-cmd done | second-cmd too | @grep '^something$' } test-bach-framework-two-pipelines-when-both-non-mocking-commands-assert() { @echo something } test-bach-framework-two-pipelines-when-mock-the-first() { @mock this-is-a-mock command @mock this-non-mock @mock the-last-command @echo hello | this-is-a-mock command | this-non-mock command | the-last-command } test-bach-framework-two-pipelines-when-mock-the-first-assert() { this-non-mock command } test-bach-framework-two-pipelines-when-mock-the-second() { @mockpipe this-is-a-mock command @mock this-non-mock @echo we cannot see this | this-non-mock command | this-is-a-mock command } test-bach-framework-two-pipelines-when-mock-the-second-assert() { this-non-mock command } test-bach-framework-mockpipe() { @mockpipe this-is-a-mock command @mock another-mock command === @cat # @mockpipe @echo hello | this-is-a-mock command | another-mock command } test-bach-framework-mockpipe-assert() { @stdout hello } test-bach-framework-multi-pipelines() { @mock first @mock second @mock third 3 { declare -pf first } >&2 @echo gone | first non mocking | second | third 3 } test-bach-framework-multi-pipelines-assert() { @dryrun first non mocking } test-bach-framework-pipeline-in-if-statement() { @mock curl website @mock sed something if curl website | sed something; then do-something else show-error fi } test-bach-framework-pipeline-in-if-statement-assert() { curl website do-something } test-bach-framework-pipeline-in-if-statement-turn-off-pipefail() { set +eo pipefail @mock curl website @mock sed something if curl website | sed something; then do-something else show-error fi } test-bach-framework-pipeline-in-if-statement-turn-off-pipefail-assert() { curl website do-something } test-bach-framework-pipeline-in-if-statement-else() { @mock curl website @mockfalse sed something if curl website | sed something; then do-something else show-error fi } test-bach-framework-pipeline-in-if-statement-else-assert() { curl website show-error } test-bach-framework-handles-an-empty-command() { set +eo pipefail "" 7>&1 | @grep -Fq 'found an empty command' @assert-success } test-bach-framework-is_function() { function this_is_a_function() { : do nothing } .bach.is-function this_is_a_function @assert-success } test-bach-framework-is_function-2() { this_is_a_variable=some_value .bach.is-function this_is_a_variable } test-bach-framework-is_function-2-assert() { @fail } test-bach-framework-@assert-equals-no-parameters() { @assert-equals } test-bach-framework-@assert-equals-no-parameters-assert() { @fail } test-bach-framework-@assert-equals-pass-one-parameter() { @assert-equals only-one } test-bach-framework-@assert-equals-pass-one-parameter-assert() { @fail } test-bach-framework-@assert-equals-integers() { @assert-equals 9 9 } test-bach-framework-@assert-equals-strings() { @assert-equals "hello world" "hello world" } test-bach-framework-@assert-equals-variables() { foo="hello world" bar="hello world" @assert-equals "$foo" "$bar" } test-ASSERT-FAIL-bach-framework-each-test-case-has-a-failed-assertion() { @echo Every test case must have an assertion, it is fail @assert-equals "Answer to the Ultimate Question of Life, the Universe, and Everything" 42 } test-bach-framework-each-test-case-has-a-successful-assertion() { @echo Every test case must have an assertion, it is success @assert-success } test-ASSERT-FAIL-bach-framework-each-test-case-has-assert-fail() { @echo Every test case must have an assertion @assert-fail } test-bach-framework-API-@assert-fail() { set +e @echo Every test case must have an assertion @false @assert-fail } test-bach-framework-API-@assert-fail-assert() { @echo Every test case must have an assertion @cat < But got: 0 EOF builtin return 1 } test-bach-framework-API-@assert-success() { @echo Every test case must have an assertion @assert-success } test-bach-framework-API-@assert-success-assert() { @echo Every test case must have an assertion @cat < file.sh @. file.sh } test-at_dot-command-assert() { do-something } test-show-empty-parameter() { foo "" bar } test-show-empty-parameter-assert() { @dryrun foo $'\x1b\x5b31m\u2205\x1b\x5b0m' bar } test-mock-printf() { @mock printf "something %s" foobar === @stdout foo printf "something %s" foobar } test-mock-printf-assert() { @echo foo } test-ASSERT-FAIL-cannot-mock-builtin(){ @mock builtin } test-ASSERT-FAIL-cannot-mock-declare(){ @mock declare } test-ASSERT-FAIL-cannot-mock-eval(){ @mock eval } test-ASSERT-FAIL-cannot-mock-set(){ @mock set } test-ASSERT-FAIL-cannot-mock-unset(){ @mock unset } test-ASSERT-FAIL-cannot-mock-true(){ @mock true } test-ASSERT-FAIL-cannot-mock-false(){ @mock false } test-ASSERT-FAIL-cannot-mock-read(){ @mock read } test-ASSERT-FAIL-cannot-ignore-builtin(){ cannot not ignore the builtin command @ignore builtin } test-ASSERT-FAIL-cannot-ignore-declare(){ @ignore declare } test-ASSERT-FAIL-cannot-ignore-eval(){ @ignore eval } test-ASSERT-FAIL-cannot-ignore-set(){ @ignore set } test-ASSERT-FAIL-cannot-ignore-unset(){ @ignore unset } test-ASSERT-FAIL-cannot-ignore-true(){ @ignore true } test-ASSERT-FAIL-cannot-ignore-false(){ @ignore false } test-ASSERT-FAIL-cannot-ignore-read(){ @ignore read } test-left-square-bracket-is-a-function() { @type -t '[' } test-left-square-bracket-is-a-function-assert() { @echo function } test-left-square-bracket-if-string-is-empty() { if [ -z "" ] then empty else non-empty fi } test-left-square-bracket-if-string-is-empty-assert() { empty } test-left-square-bracket-returns-false-if-string-is-empty() { @mock [ -z "" ] === @false if [ -z "" ] then empty string should be empty but we reverse the result by mocking it incorrectly else an empty string which is not empty fi } test-left-square-bracket-returns-false-if-string-is-empty-assert() { an empty string which is not empty } test-left-square-bracket-returns-false-if-string-is-not-empty() { @mock [ -n XYZ ] === @false if [ -n "abc" ] then abc is not empty else abc should not be empty fi if [ -n "XYZ" ] then XYZ should not be empty but we reverse the result by mocking it incorrectly else XYZ is empty fi } test-left-square-bracket-returns-false-if-string-is-not-empty-assert() { abc is not empty XYZ is empty } test-if-string-is-empty() { if [ -n "original behavior" ] # We did not mock it, so this test keeps the original behavior then It keeps the original behavior by default # We should see this else It should not be empty fi @mockfalse [ -n "Non-empty string" ] # We can reverse the test result by mocking it if [ -n "Non-empty string" ] then Non-empty string is not empty # No, we cannot see this else Non-empty string should not be empty but we reverse its result fi } test-if-string-is-empty-assert() { It keeps the original behavior by default Non-empty string should not be empty but we reverse its result } # Mocking the test command `[ ... ]` is useful # when we want to check whether a file with absolute path exists or not test-a-file-exists() { @mocktrue [ -f /etc/an-awesome-config.conf ] if [ -f /etc/an-awesome-config.conf ]; then Found this awesome config file else Even though this config file does not exist fi } test-a-file-exists-assert() { Found this awesome config file } test-api-capture() { @capture foobar foo bar @echo hello | foobar foo bar } test-api-capture-assert() { @assert-capture foobar foo bar <<< hello } test-api-capture-redirect-dev-null() { @capture tee filename @allow-real cat { cat <<< alpha cat <<< beta } | tee filename >/dev/null 2>&1 } test-api-capture-redirect-dev-null-assert() { @assert-capture tee filename </dev/null } test-allow-real-cat-and-capture-tee-assert() { @assert-capture tee filename </dev/null; then hello fi } test-api-allow-real-if-grep-assert() { @dryrun hello } test-allow-real-grep() { @allow-real grep -i hello grep -i hello <<< "Hello, Bach" grep -i hello <<< "Goodbye" } test-allow-real-grep-assert() { @stdout "Hello, Bach" @fail } test-allow-real-cat-here-doc() { @allow-real cat cat <<< hello } test-allow-real-cat-here-doc-assert() { @stdout hello }