From 117051d91933db23b9ceb339bb30dc3c7d6c98be Mon Sep 17 00:00:00 2001 From: keithy Date: Tue, 12 Mar 2019 03:10:44 +0000 Subject: [PATCH 1/9] got rid of bash-spec-ext.sh --- test_counters.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_counters.sh b/test_counters.sh index 9bd015d..1db3843 100755 --- a/test_counters.sh +++ b/test_counters.sh @@ -1,5 +1,5 @@ #! /bin/bash -. ./bash-spec-ext.sh +. ./bash-spec.sh describe "A simple test" "$( From 15045e6b53c04b7ecd67af14d4f3df0c53cc927f Mon Sep 17 00:00:00 2001 From: keithy Date: Tue, 12 Mar 2019 13:55:06 +0000 Subject: [PATCH 2/9] Modified to accept code block format: context this && {..code.. } --- bash-spec-2.1.sh | 184 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 bash-spec-2.1.sh diff --git a/bash-spec-2.1.sh b/bash-spec-2.1.sh new file mode 100644 index 0000000..f9d83cd --- /dev/null +++ b/bash-spec-2.1.sh @@ -0,0 +1,184 @@ +#!/bin/bash +#================================================================================== +# BDD-style testing framework for bash scripts. +# +# expect variable [not] to_be value Compare scalar values for equality +# expect variable [not] to_match regex Regex match +# expect array [not] to_contain value Look for a value in an array +# expect filename [not] to_exist Verify file existence +# expect filename [not] to_have_mode modestring Verify file mode (permissions) +# expect [not] to_be_true condition Verify exit mode as boolean +# +# Author: Dave Nicolette +# Date: 29 Jul 2014 +# Modified by REA Group 2014 +#================================================================================== +# Modified by keithy +# Specify output file + +# XXX: should use mktemp for proper random file name -- (GM) +#result_file="${SPEC_RESULTS:-$RANDOM}" +result_file="$RANDOM" +_passed_=0 +_failed_=0 + +exec 6<&1 +exec > "$result_file" + +function output_results { + exec 1>&6 6>&- + local results="$(<$result_file)" + rm -f -- "$result_file" + local passes=$(printf '%s' "$results" | grep -F PASS | wc -l) + local fails=$(printf '%s' "$results" | grep -F '**** FAIL' | wc -l ) + printf '%s\n--SUMMARY\n%d PASSED\n%d FAILED\n' "$results" "$passes" "$fails" + [[ ${fails:-1} -eq 0 ]] + exit $? +} + +function _array_contains_ { + for elem in "${_actual_[@]}"; do + [[ "$elem" == "$_expected_" ]] && return 0 + done + return 1 +} + +function _negation_check_ { + if [[ "$_negation_" == true ]]; then + if [[ "$_pass_" == true ]]; then + _pass_=false + else + _pass_=true + fi + fi + if [[ "$_pass_" == true ]]; then + (( _passed_+=1 )) + pass + else + (( _failed_+=1 )) + fail + fi +} + +function it { + echo " $1" + [ -z ${2+x} ] || echo " $2" +} + +function describe { + echo "$1" + [ -z ${2+x} ] || echo "$2" +} + +function context { + echo "$1" + [ -z ${2+x} ] || echo "$2" +} + +function pass { + echo " PASS" +} + +function fail { + echo "**** FAIL - expected:$( if [[ "$_negation_" == true ]]; then echo ' NOT'; fi; ) '$_expected_' | actual: '${_actual_[@]}'" +} + +function expect { + _expected_= + _negation_=false + declare -a _actual_ + until [[ "${1:0:3}" == to_ || "$1" == not || -z "$1" ]]; do + _actual_+=("$1") + shift + done + "$@" +} + +function not { + _negation_=true + "$@" +} + +function to_be { + _expected_="$1" + _pass_=false + [[ "${_actual_[0]}" == "$_expected_" ]] && _pass_=true + _negation_check_ +} + +function to_be_true { + _expected_="$@ IS TRUE" + _pass=false + _actual_="$@ IS FALSE" + if "$@"; then + _pass_=true + _actual_="$@ IS TRUE" + fi + _negation_check_ +} + +function to_match { + _expected_="$1" + _pass_=false + [[ "${_actual_[0]}" =~ $_expected_ ]] && _pass_=true + _negation_check_ +} + +function to_contain { + _expected_="$1" + _pass_=false + _array_contains_ "$_expected_" "$_actual_" && _pass_=true + _negation_check_ +} + +function to_exist { + _pass_=false + _expected_="$_actual_ EXISTS" + if [[ -e "${_actual_[0]}" ]]; then + _pass_=true + [[ "$_negation_" == true ]] && _expected_="$_actual_ EXISTS" + else + _actual_="File not found" + fi + _negation_check_ +} + +function to_have_mode { + _filename_="${_actual_[0]}" + _expected_="$1" + _pass_=false + if [[ -e "$_filename_" ]]; then + _fullname_="$_filename_" + else + _fullname_="$(which $_filename_)" + fi + if [[ -e "$_fullname_" ]]; then + _os_="$(uname -s)" + if [[ "$_os_" == Linux ]]; then + _actual_="$(stat -c %A $_fullname_)" + else + _actual_="$(stat $_fullname_ | cut -f 3 -d ' ')" + fi + [[ "$_actual_" =~ "$_expected_" ]] && _pass_=true + else + echo "File not found: $_fullname_" + fi + _negation_check_ +} + +TEMP="$(getopt -o h --long help \ + -n 'javawrap' -- $@)" + +if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi + +eval set -- "$TEMP" + +while true; do + case "$1" in + -h | --help ) show_help; exit 0 ;; + -- ) shift; break ;; + * ) break ;; + esac +done + +trap output_results EXIT From dc7bc54cf87de8495deb510d331e02e93a8d280a Mon Sep 17 00:00:00 2001 From: keithy Date: Tue, 12 Mar 2019 14:49:39 +0000 Subject: [PATCH 3/9] Prepended to README about 2.1 alternative format(s) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 40638fb..1517b36 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +New in 2.1 +========== +# 2.1 Supporting other formats +was: describe "title" "$( ... )" +new: describe "title" && { ... } +new: describe "title" && ( ... ) + bash-spec ========= @@ -92,3 +99,4 @@ bash-spec-2 works hand in hand with [stub.sh](https://github.com/jimeh/stub.sh) - An equivalent for let blocks (memoized, lazy evaluated) might be useful - Proper nesting of the output would be cool + From b5d58328ed967bd8a1629532bad1929bae31a081 Mon Sep 17 00:00:00 2001 From: keithy Date: Wed, 13 Mar 2019 00:23:25 +0000 Subject: [PATCH 4/9] Alt-format test suites, test for bash version, should_succeed should_fail --- README.md | 15 +- bash-spec-2.1.sh | 184 ---------------- bash-spec.sh | 61 +++++- test_bash-spec-altfmt.sh | 429 ++++++++++++++++++++++++++++++++++++++ test_bash-spec-altfmt2.sh | 429 ++++++++++++++++++++++++++++++++++++++ test_bash-spec.sh | 96 ++++++++- 6 files changed, 1020 insertions(+), 194 deletions(-) delete mode 100644 bash-spec-2.1.sh create mode 100755 test_bash-spec-altfmt.sh create mode 100755 test_bash-spec-altfmt2.sh diff --git a/README.md b/README.md index 1517b36..aec8239 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ New in 2.1 ========== -# 2.1 Supporting other formats -was: describe "title" "$( ... )" -new: describe "title" && { ... } -new: describe "title" && ( ... ) +#### 2.1 Supporting other formats +``` +old format: describe "title" "$( ... )" +alt format: describe "title" && { ... } (most readable but predictably fails one test) +alt format2: describe "title" && ( ... ) + +[[ some_expression ]] +should_succeed +[[ some_expression ]] +should_fail +``` bash-spec ========= diff --git a/bash-spec-2.1.sh b/bash-spec-2.1.sh deleted file mode 100644 index f9d83cd..0000000 --- a/bash-spec-2.1.sh +++ /dev/null @@ -1,184 +0,0 @@ -#!/bin/bash -#================================================================================== -# BDD-style testing framework for bash scripts. -# -# expect variable [not] to_be value Compare scalar values for equality -# expect variable [not] to_match regex Regex match -# expect array [not] to_contain value Look for a value in an array -# expect filename [not] to_exist Verify file existence -# expect filename [not] to_have_mode modestring Verify file mode (permissions) -# expect [not] to_be_true condition Verify exit mode as boolean -# -# Author: Dave Nicolette -# Date: 29 Jul 2014 -# Modified by REA Group 2014 -#================================================================================== -# Modified by keithy -# Specify output file - -# XXX: should use mktemp for proper random file name -- (GM) -#result_file="${SPEC_RESULTS:-$RANDOM}" -result_file="$RANDOM" -_passed_=0 -_failed_=0 - -exec 6<&1 -exec > "$result_file" - -function output_results { - exec 1>&6 6>&- - local results="$(<$result_file)" - rm -f -- "$result_file" - local passes=$(printf '%s' "$results" | grep -F PASS | wc -l) - local fails=$(printf '%s' "$results" | grep -F '**** FAIL' | wc -l ) - printf '%s\n--SUMMARY\n%d PASSED\n%d FAILED\n' "$results" "$passes" "$fails" - [[ ${fails:-1} -eq 0 ]] - exit $? -} - -function _array_contains_ { - for elem in "${_actual_[@]}"; do - [[ "$elem" == "$_expected_" ]] && return 0 - done - return 1 -} - -function _negation_check_ { - if [[ "$_negation_" == true ]]; then - if [[ "$_pass_" == true ]]; then - _pass_=false - else - _pass_=true - fi - fi - if [[ "$_pass_" == true ]]; then - (( _passed_+=1 )) - pass - else - (( _failed_+=1 )) - fail - fi -} - -function it { - echo " $1" - [ -z ${2+x} ] || echo " $2" -} - -function describe { - echo "$1" - [ -z ${2+x} ] || echo "$2" -} - -function context { - echo "$1" - [ -z ${2+x} ] || echo "$2" -} - -function pass { - echo " PASS" -} - -function fail { - echo "**** FAIL - expected:$( if [[ "$_negation_" == true ]]; then echo ' NOT'; fi; ) '$_expected_' | actual: '${_actual_[@]}'" -} - -function expect { - _expected_= - _negation_=false - declare -a _actual_ - until [[ "${1:0:3}" == to_ || "$1" == not || -z "$1" ]]; do - _actual_+=("$1") - shift - done - "$@" -} - -function not { - _negation_=true - "$@" -} - -function to_be { - _expected_="$1" - _pass_=false - [[ "${_actual_[0]}" == "$_expected_" ]] && _pass_=true - _negation_check_ -} - -function to_be_true { - _expected_="$@ IS TRUE" - _pass=false - _actual_="$@ IS FALSE" - if "$@"; then - _pass_=true - _actual_="$@ IS TRUE" - fi - _negation_check_ -} - -function to_match { - _expected_="$1" - _pass_=false - [[ "${_actual_[0]}" =~ $_expected_ ]] && _pass_=true - _negation_check_ -} - -function to_contain { - _expected_="$1" - _pass_=false - _array_contains_ "$_expected_" "$_actual_" && _pass_=true - _negation_check_ -} - -function to_exist { - _pass_=false - _expected_="$_actual_ EXISTS" - if [[ -e "${_actual_[0]}" ]]; then - _pass_=true - [[ "$_negation_" == true ]] && _expected_="$_actual_ EXISTS" - else - _actual_="File not found" - fi - _negation_check_ -} - -function to_have_mode { - _filename_="${_actual_[0]}" - _expected_="$1" - _pass_=false - if [[ -e "$_filename_" ]]; then - _fullname_="$_filename_" - else - _fullname_="$(which $_filename_)" - fi - if [[ -e "$_fullname_" ]]; then - _os_="$(uname -s)" - if [[ "$_os_" == Linux ]]; then - _actual_="$(stat -c %A $_fullname_)" - else - _actual_="$(stat $_fullname_ | cut -f 3 -d ' ')" - fi - [[ "$_actual_" =~ "$_expected_" ]] && _pass_=true - else - echo "File not found: $_fullname_" - fi - _negation_check_ -} - -TEMP="$(getopt -o h --long help \ - -n 'javawrap' -- $@)" - -if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi - -eval set -- "$TEMP" - -while true; do - case "$1" in - -h | --help ) show_help; exit 0 ;; - -- ) shift; break ;; - * ) break ;; - esac -done - -trap output_results EXIT diff --git a/bash-spec.sh b/bash-spec.sh index 66e19dd..f563072 100644 --- a/bash-spec.sh +++ b/bash-spec.sh @@ -1,20 +1,27 @@ -#!/bin/bash +#!/usr/bin/env bash #================================================================================== # BDD-style testing framework for bash scripts. # # expect variable [not] to_be value Compare scalar values for equality # expect variable [not] to_match regex Regex match # expect array [not] to_contain value Look for a value in an array +# expect_array arrayname [not] to_contain value Look for a value in an array (by ref) +# expect_var variablename [not] to_contain value Look for a value in a variable (by ref) # expect filename [not] to_exist Verify file existence # expect filename [not] to_have_mode modestring Verify file mode (permissions) # expect [not] to_be_true condition Verify exit mode as boolean +# [[ some_expression ]] +# should_succeed +# [[ some_expression ]] +# should_fail # # Author: Dave Nicolette # Date: 29 Jul 2014 # Modified by REA Group 2014 +# Modified by keithy@consultant.com 03/2019 #================================================================================== - # XXX: should use mktemp for proper random file name -- (GM) + result_file="$RANDOM" _passed_=0 _failed_=0 @@ -58,15 +65,18 @@ function _negation_check_ { } function it { - printf ' %s\n %s\n' "$1" "$2" + echo " $1" + [ -z ${2+x} ] || echo " $2" } function describe { - printf '%s\n%s\n' "$1" "$2" + echo "$1" + [ -z ${2+x} ] || echo "$2" } function context { - printf '%s\n%s\n' "$1" "$2" + echo "$1" + [ -z ${2+x} ] || echo "$2" } function pass { @@ -77,9 +87,30 @@ function fail { echo "**** FAIL - expected:$( if [[ "$_negation_" == true ]]; then echo ' NOT'; fi; ) '$_expected_' | actual: '${_actual_[@]}'" } +function should_succeed { + _actual_=$? + _expected_=0 + _pass_=false + [[ $_actual_ == $_expected_ ]] && _pass_=true + _expected_="0(success)" + _actual_="$_actual_(failed)" + _negation_check_ +} + +function should_fail { + _actual_=$? + _expected_=0 + _pass_=true + [[ $_actual_ == $_expected_ ]] && _pass_=false + _expected_="NOT 0(fail)" + _actual_="0(succeeded)" + _negation_check_ +} + function expect { _expected_= _negation_=false + _pass_=false declare -a _actual_ until [[ "${1:0:3}" == to_ || "$1" == not || -z "$1" ]]; do _actual_+=("$1") @@ -88,6 +119,26 @@ function expect { "$@" } +function expect_var { + _expected_= + _negation_=false + declare -n _actual_=$1 + until [[ "${1:0:3}" == to_ || "$1" == not || -z "$1" ]]; do + shift + done + "$@" +} + +function expect_array { + _expected_= + _negation_=false + declare -n _actual_=$1 + until [[ "${1:0:3}" == to_ || "$1" == not || -z "$1" ]]; do + shift + done + "$@" +} + function not { _negation_=true "$@" diff --git a/test_bash-spec-altfmt.sh b/test_bash-spec-altfmt.sh new file mode 100755 index 0000000..c4ce430 --- /dev/null +++ b/test_bash-spec-altfmt.sh @@ -0,0 +1,429 @@ +#!/usr/bin/env bash + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/bash-spec.sh" + +describe "The bash version test" && { + + context "When using bash (especially on MacOS X}" && { + + it "Version must be 4+" && { + [[ "${BASH_VERSINFO[0]}" > "3" ]] + should_succeed + } + } +} + +describe "Should_succeed" && { + context "When testing conditional" && { + it "reports success as pass" && { + [[ "1" = "1" ]] + should_succeed + } + } + context "When testing condition fails" && { + result="$( + [[ "1" = "2" ]] + should_succeed + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: '0(success)' | actual: '1(failed)'" + } + } +} + +describe "Should_fail" && { + context "When testing conditional" && { + it "reports fail as pass" && { + [[ "1" = "2" ]] + should_fail + } + } + context "When testing condition fails" && { + result="$( + [[ "1" = "1" ]] + should_fail + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: 'NOT 0(fail)' | actual: '0(succeeded)'" + } + } +} + +describe "The equality test" && { + + context "When a single value is passed" && { + + it "Reports two scalar values are equal" && { + one="1" + expect $one to_be 1 + } + + } + + context "When a multi word value is passed" && { + + it "Reports two scalar values are equal" && { + string="This is a string." + expect "$string" to_be "This is a string." + } + + } + + context "When there is a failure" && { + + result="$( + expect "Test text" to_be "Something else" + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: 'Something else' | actual: 'Test text'" + } + + } + +} + +describe "The inequality test" && { + + it "Reports two scalar values are unequal" && { + one="1" + expect $one not to_be 2 + } + + context "When there is a failure" && { + + result="$( + expect "1" not to_be "1" + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT '1' | actual: '1'" + } + + } + +} + +describe "The regex matcher" && { + + str="one fine day" + + it "Reports a regex match" && { + expect "$str" to_match day$ + } + + context "When there is a failure" && { + + result="$( + expect "$str" to_match wibble$ + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: 'wibble$' | actual: 'one fine day'" + } + + } + +} + +describe "The regex non-matcher" && { + + str="one fine night" + + it "Reports regex mismatch" && { + expect "$str" not to_match day$ + } + + context "When there is a failure" && { + + result="$( + expect "$str" not to_match night$ + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT 'night$' | actual: 'one fine night'" + } + + } + +} + +describe "The array matcher" && { + + declare -a arr=(1 2 3 4) + + it "Reports an array contains a given value" && { + expect "${arr[@]}" to_contain 3 + } + + context "When there is a failure" && { + + result="$( + expect "${arr[@]}" to_contain 5 + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" + } + + } + +} + +describe "The array non-matcher" && { + + declare -a arr=(1 2 3 4) + + it "Reports an array does not contain a given value" && { + expect "${arr[@]}" not to_contain 5 + } + + context "When there is a failure" && { + + result="$( + expect "${arr[@]}" not to_contain 4 + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" + } + + } + +} + +describe "The array (passed by reference) matcher" && { + + declare -a arr=(1 2 3 4) + + it "Reports an array contains a given value" && { + expect_array arr to_contain 3 + } + + context "When there is a failure" && { + + result="$( + expect_array arr to_contain 5 + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" + } + + } + +} + +describe "The array (passed by reference) non-matcher" && { + + declare -a arr=(1 2 3 4) + + it "Reports an array does not contain a given value" && { + expect_array arr not to_contain 5 + } + + context "When there is a failure" && { + + result="$( + expect_array arr not to_contain 4 + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" + } + + } + +} + + +describe "The file existence matcher" && { + + echo 'test' > tempfile + + it "Reports a file exists" && { + expect tempfile to_exist + } + + context "When there is a failure" && { + + rm -f tempfile + + result="$( + expect tempfile to_exist + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: 'tempfile EXISTS' | actual: 'File not found'" + } + + } + + rm -f tempfile + +} + +describe "The file non-existence matcher" && { + + it "Reports a file does not exist" && { + rm -f tempfile + expect tempfile not to_exist + } + + context "When there is a failure" && { + + echo 'test' > tempfile + + result="$( + expect tempfile not to_exist + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT 'tempfile EXISTS' | actual: 'tempfile'" + } + + } + + rm -f tempfile + +} + +describe "The file mode matcher" && { + + touch tempfile + chmod u=rw,g=r,o=x tempfile + + it "Reports a file has the given mode" && { + expect tempfile to_have_mode -rw-r----x + } + + context "When there is a failure" && { + + result="$( + expect tempfile to_have_mode -rw-rw-rwx + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: '-rw-rw-rwx' | actual: '-rw-r----x'" + } + + } + + rm -f tempfile + +} + +describe "The file mode non-matcher" && { + + touch tempfile + chmod u=rw,g=r,o=x tempfile + + it "Reports a file does not have the given mode" && { + expect tempfile not to_have_mode -rw-rw-rwx + } + + context "When there is a failure" && { + + result="$( + expect tempfile not to_have_mode -rw-r----x + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT '-rw-r----x' | actual: '-rw-r----x'" + } + + } + + rm -f tempfile + +} + +describe "The exit mode matcher" && { + + function return_boolean { + if [[ $1 == "true" ]]; then + return 0 + fi + return 1 + } + + it "Reports truth when the exit code of the following command is 0" && { + expect to_be_true return_boolean true + } + + context "When there is a failure" && { + + result="$( + expect to_be_true return_boolean false + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: 'return_boolean false IS TRUE' | actual: 'return_boolean false IS FALSE'" + } + + } + +} + +describe "The exit mode non matcher" && { + + function return_boolean { + if [[ $1 == "true" ]]; then + return 0 + fi + return 1 + } + + it "Reports false when the exit code of the following command is 1" && { + expect not to_be_true return_boolean false + } + + context "When there is a failure" && { + + result="$( + expect not to_be_true return_boolean true + )" + + it "Reports the actual and expected correctly" && { + expect "$result" to_be "**** FAIL - expected: NOT 'return_boolean true IS TRUE' | actual: 'return_boolean true IS TRUE'" + } + + } + +} + +describe "Setting variables when nesting" && { + + test_var="first value" + + it "Pulls a value into an it from the outer level" && { + expect "$test_var" to_be "first value" + } + + context "When there is a nested context" && { + + it "Pulls a value into the inner it from the very outer level" && { + expect "$test_var" to_be "first value" + } + + } + + context "When the context overwrites the value" && { + + test_var="second value" + + it "Pulls the value into the inner it from the next level out" && { + expect "$test_var" to_be "second value" + } + + } + + it "Does not get affected by values set in inner nesting earlier on" && { + expect "$test_var" to_be "first value" + } + +} diff --git a/test_bash-spec-altfmt2.sh b/test_bash-spec-altfmt2.sh new file mode 100755 index 0000000..9c2fda4 --- /dev/null +++ b/test_bash-spec-altfmt2.sh @@ -0,0 +1,429 @@ +#!/usr/bin/env bash + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/bash-spec.sh" + +describe "The bash version test" && ( + + context "When using bash (especially on MacOS X}" && ( + + it "Version must be 4+" && ( + [[ "${BASH_VERSINFO[0]}" > "3" ]] + should_succeed + ) + ) +) + +describe "Should_succeed" && ( + context "When testing conditional" && ( + it "reports success as pass" && ( + [[ "1" = "1" ]] + should_succeed + ) + ) + context "When testing condition fails" && ( + result="$( + [[ "1" = "2" ]] + should_succeed + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: '0(success)' | actual: '1(failed)'" + ) + ) +) + +describe "Should_fail" && ( + context "When testing conditional" && ( + it "reports fail as pass" && ( + [[ "1" = "2" ]] + should_fail + ) + ) + context "When testing condition fails" && ( + result="$( + [[ "1" = "1" ]] + should_fail + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: 'NOT 0(fail)' | actual: '0(succeeded)'" + ) + ) +) + +describe "The equality test" && ( + + context "When a single value is passed" && ( + + it "Reports two scalar values are equal" && ( + one="1" + expect $one to_be 1 + ) + + ) + + context "When a multi word value is passed" && ( + + it "Reports two scalar values are equal" && ( + string="This is a string." + expect "$string" to_be "This is a string." + ) + + ) + + context "When there is a failure" && ( + + result="$( + expect "Test text" to_be "Something else" + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: 'Something else' | actual: 'Test text'" + ) + + ) + +) + +describe "The inequality test" && ( + + it "Reports two scalar values are unequal" && ( + one="1" + expect $one not to_be 2 + ) + + context "When there is a failure" && ( + + result="$( + expect "1" not to_be "1" + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT '1' | actual: '1'" + ) + + ) + +) + +describe "The regex matcher" && ( + + str="one fine day" + + it "Reports a regex match" && ( + expect "$str" to_match day$ + ) + + context "When there is a failure" && ( + + result="$( + expect "$str" to_match wibble$ + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: 'wibble$' | actual: 'one fine day'" + ) + + ) + +) + +describe "The regex non-matcher" && ( + + str="one fine night" + + it "Reports regex mismatch" && ( + expect "$str" not to_match day$ + ) + + context "When there is a failure" && ( + + result="$( + expect "$str" not to_match night$ + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT 'night$' | actual: 'one fine night'" + ) + + ) + +) + +describe "The array matcher" && ( + + declare -a arr=(1 2 3 4) + + it "Reports an array contains a given value" && ( + expect "${arr[@]}" to_contain 3 + ) + + context "When there is a failure" && ( + + result="$( + expect "${arr[@]}" to_contain 5 + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" + ) + + ) + +) + +describe "The array non-matcher" && ( + + declare -a arr=(1 2 3 4) + + it "Reports an array does not contain a given value" && ( + expect "${arr[@]}" not to_contain 5 + ) + + context "When there is a failure" && ( + + result="$( + expect "${arr[@]}" not to_contain 4 + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" + ) + + ) + +) + +describe "The array (passed by reference) matcher" && ( + + declare -a arr=(1 2 3 4) + + it "Reports an array contains a given value" && ( + expect_array arr to_contain 3 + ) + + context "When there is a failure" && ( + + result="$( + expect_array arr to_contain 5 + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" + ) + + ) + +) + +describe "The array (passed by reference) non-matcher" && ( + + declare -a arr=(1 2 3 4) + + it "Reports an array does not contain a given value" && ( + expect_array arr not to_contain 5 + ) + + context "When there is a failure" && ( + + result="$( + expect_array arr not to_contain 4 + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" + ) + + ) + +) + + +describe "The file existence matcher" && ( + + echo 'test' > tempfile + + it "Reports a file exists" && ( + expect tempfile to_exist + ) + + context "When there is a failure" && ( + + rm -f tempfile + + result="$( + expect tempfile to_exist + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: 'tempfile EXISTS' | actual: 'File not found'" + ) + + ) + + rm -f tempfile + +) + +describe "The file non-existence matcher" && ( + + it "Reports a file does not exist" && ( + rm -f tempfile + expect tempfile not to_exist + ) + + context "When there is a failure" && ( + + echo 'test' > tempfile + + result="$( + expect tempfile not to_exist + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT 'tempfile EXISTS' | actual: 'tempfile'" + ) + + ) + + rm -f tempfile + +) + +describe "The file mode matcher" && ( + + touch tempfile + chmod u=rw,g=r,o=x tempfile + + it "Reports a file has the given mode" && ( + expect tempfile to_have_mode -rw-r----x + ) + + context "When there is a failure" && ( + + result="$( + expect tempfile to_have_mode -rw-rw-rwx + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: '-rw-rw-rwx' | actual: '-rw-r----x'" + ) + + ) + + rm -f tempfile + +) + +describe "The file mode non-matcher" && ( + + touch tempfile + chmod u=rw,g=r,o=x tempfile + + it "Reports a file does not have the given mode" && ( + expect tempfile not to_have_mode -rw-rw-rwx + ) + + context "When there is a failure" && ( + + result="$( + expect tempfile not to_have_mode -rw-r----x + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT '-rw-r----x' | actual: '-rw-r----x'" + ) + + ) + + rm -f tempfile + +) + +describe "The exit mode matcher" && ( + + function return_boolean { + if [[ $1 == "true" ]]; then + return 0 + fi + return 1 + } + + it "Reports truth when the exit code of the following command is 0" && ( + expect to_be_true return_boolean true + ) + + context "When there is a failure" && ( + + result="$( + expect to_be_true return_boolean false + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: 'return_boolean false IS TRUE' | actual: 'return_boolean false IS FALSE'" + ) + + ) + +) + +describe "The exit mode non matcher" && ( + + function return_boolean { + if [[ $1 == "true" ]]; then + return 0 + fi + return 1 + } + + it "Reports false when the exit code of the following command is 1" && ( + expect not to_be_true return_boolean false + ) + + context "When there is a failure" && ( + + result="$( + expect not to_be_true return_boolean true + )" + + it "Reports the actual and expected correctly" && ( + expect "$result" to_be "**** FAIL - expected: NOT 'return_boolean true IS TRUE' | actual: 'return_boolean true IS TRUE'" + ) + + ) + +) + +describe "Setting variables when nesting" && ( + + test_var="first value" + + it "Pulls a value into an it from the outer level" && ( + expect "$test_var" to_be "first value" + ) + + context "When there is a nested context" && ( + + it "Pulls a value into the inner it from the very outer level" && ( + expect "$test_var" to_be "first value" + ) + + ) + + context "When the context overwrites the value" && ( + + test_var="second value" + + it "Pulls the value into the inner it from the next level out" && ( + expect "$test_var" to_be "second value" + ) + + ) + + it "Does not get affected by values set in inner nesting earlier on" && ( + expect "$test_var" to_be "first value" + ) + +) diff --git a/test_bash-spec.sh b/test_bash-spec.sh index d8e52ec..21ff1af 100755 --- a/test_bash-spec.sh +++ b/test_bash-spec.sh @@ -1,9 +1,58 @@ -#! /bin/bash +#!/usr/bin/env bash DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/bash-spec.sh" +describe "The bash version test" "$( + + context "When using bash (especially on MacOS X)" "$( + + it "Version must be 4+" "$( + [[ "${BASH_VERSINFO[0]}" > "3" ]] + should_succeed + )" + )" +)" + +describe "Should_succeed" "$( + context "When testing conditional" "$( + it "reports success as pass" "$( + [[ "1" = "1" ]] + should_succeed + )" + )" + context "When testing condition fails" "$( + result="$( + [[ "1" = "2" ]] + should_succeed + )" + + it "Reports the actual and expected correctly" "$( + expect "$result" to_be "**** FAIL - expected: '0(success)' | actual: '1(failed)'" + )" + )" +)" + +describe "Should_fail" "$( + context "When testing conditional" "$( + it "reports fail as pass" "$( + [[ "1" = "2" ]] + should_fail + )" + )" + context "When testing condition fails" "$( + result="$( + [[ "1" = "1" ]] + should_fail + )" + + it "Reports the actual and expected correctly" "$( + expect "$result" to_be "**** FAIL - expected: 'NOT 0(fail)' | actual: '0(succeeded)'" + )" + )" +)" + describe "The equality test" "$( context "When a single value is passed" "$( @@ -147,6 +196,51 @@ describe "The array non-matcher" "$( )" +describe "The array (passed by reference) matcher" "$( + + declare -a arr=(1 2 3 4) + + it "Reports an array contains a given value" "$( + expect_array arr to_contain 3 + )" + + context "When there is a failure" "$( + + result="$( + expect_array arr to_contain 5 + )" + + it "Reports the actual and expected correctly" "$( + expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" + )" + + )" + +)" + +describe "The array (passed by reference) non-matcher" "$( + + declare -a arr=(1 2 3 4) + + it "Reports an array does not contain a given value" "$( + expect_array arr not to_contain 5 + )" + + context "When there is a failure" "$( + + result="$( + expect_array arr not to_contain 4 + )" + + it "Reports the actual and expected correctly" "$( + expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" + )" + + )" + +)" + + describe "The file existence matcher" "$( echo 'test' > tempfile From 4f90818cd6e6eb954cedaf4e93815a528dc582ca Mon Sep 17 00:00:00 2001 From: keithy Date: Wed, 13 Mar 2019 00:36:34 +0000 Subject: [PATCH 5/9] enabled unofficial bash strict mode --- bash-spec.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bash-spec.sh b/bash-spec.sh index f563072..3078167 100644 --- a/bash-spec.sh +++ b/bash-spec.sh @@ -22,6 +22,11 @@ #================================================================================== # XXX: should use mktemp for proper random file name -- (GM) +# http://redsymbol.net/articles/unofficial-bash-strict-mode/ +set -uo pipefail +IFS=$'\n\t' +shopt -s nullglob # make sure globs are empty arrays if nothing is foound + result_file="$RANDOM" _passed_=0 _failed_=0 @@ -90,6 +95,7 @@ function fail { function should_succeed { _actual_=$? _expected_=0 + _negation_=false _pass_=false [[ $_actual_ == $_expected_ ]] && _pass_=true _expected_="0(success)" @@ -100,6 +106,7 @@ function should_succeed { function should_fail { _actual_=$? _expected_=0 + _negation_=false _pass_=true [[ $_actual_ == $_expected_ ]] && _pass_=false _expected_="NOT 0(fail)" From 93693391f86bfad2f3558d580b2ba3107633dde7 Mon Sep 17 00:00:00 2001 From: keithy Date: Wed, 13 Mar 2019 01:19:26 +0000 Subject: [PATCH 6/9] Document expect_array expect_var --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index aec8239..6debe70 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,30 @@ New in 2.1 ========== -#### 2.1 Supporting other formats +#### Supporting other formats ``` old format: describe "title" "$( ... )" alt format: describe "title" && { ... } (most readable but predictably fails one test) -alt format2: describe "title" && ( ... ) - +alt format2: describe "title" && ( ... ) (compromise?) +``` +#### Assertions on expressions +``` [[ some_expression ]] should_succeed [[ some_expression ]] should_fail ``` +#### Cleaner support for arrays - pass by reference + +``` +expect_var varref to_be 5 +expect_array arrayname to_contain 5 +``` + +#### Unofficial bash strict mode + +http://redsymbol.net/articles/unofficial-bash-strict-mode/ + bash-spec ========= From 6a243b2a24bc72cbb328f39bec0c8cec56fe517e Mon Sep 17 00:00:00 2001 From: keithy Date: Sat, 17 Aug 2019 01:25:32 +0100 Subject: [PATCH 7/9] use mktemp, restore show_help --- README.md | 17 ++++--- bash-spec-baby.sh | 97 +++++++++++++++++++++++++++++++++++++++ bash-spec.sh | 66 ++++++++++++++++---------- test_bash-spec-altfmt.sh | 15 +++--- test_bash-spec-altfmt2.sh | 39 ++++------------ test_bash-spec.sh | 9 ++++ 6 files changed, 174 insertions(+), 69 deletions(-) create mode 100644 bash-spec-baby.sh diff --git a/README.md b/README.md index 6debe70..aab20d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -New in 2.1 -========== -#### Supporting other formats +New for 2.1 +=========== +#### Supporting other coding styles/formats ``` old format: describe "title" "$( ... )" alt format: describe "title" && { ... } (most readable but predictably fails one test) @@ -13,11 +13,10 @@ should_succeed [[ some_expression ]] should_fail ``` - #### Cleaner support for arrays - pass by reference ``` -expect_var varref to_be 5 +expect_var varname to_be 5 expect_array arrayname to_contain 5 ``` @@ -92,7 +91,8 @@ When converted to lowercase ### Matchers -Most of the matchers from the original bash-spec survived. The to_be_installed matcher went away, because it didn't work and wasn't super useful. It can be resurrected if someone cares enough. +Most of the matchers from the original bash-spec survived. The to_be_installed matcher went away, +because it didn't work and wasn't super useful. It can be resurrected if someone cares enough. The available matchers are: @@ -107,7 +107,10 @@ Each matcher has a negated mode (`not to_be`, `not to_match` etc) ### Blocks and the notably absent "before" syntax -You'll have noticed that the command substitution syntax is used. This provides something similar to independent blocks, since each "$( )" spawns a subshell that doesn't affect other subshells or the parent shell. Each subshell also gets a copy of the environment in the parent shell, making a "before" syntax unnecessary. +You'll have noticed that the command substitution syntax is used. +This provides something similar to independent blocks, since each "$( )" spawns a subshell that doesn't +affect other subshells or the parent shell. Each subshell also gets a copy of the environment in the parent shell, +making a "before" syntax unnecessary. The [bash-spec test suite](https://github.com/realestate-com-au/bash-spec-2/blob/master/test_bash-spec.sh) has some good examples of this. diff --git a/bash-spec-baby.sh b/bash-spec-baby.sh new file mode 100644 index 0000000..05f2611 --- /dev/null +++ b/bash-spec-baby.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +#================================================================================== +# Minimal testing framework for bash scripts. +# +# [[ some_expression ]] +# should_succeed +# [[ some_expression ]] +# should_fail +# +# bash-spec Author: Dave Nicolette +# Date: 29 Jul 2014 +# Modified by REA Group 2014 +# bash-spec-baby by keithy@consultant.com 03/2019 +#================================================================================== + +# http://redsymbol.net/articles/unofficial-bash-strict-mode/ +set -uo pipefail +IFS=$'\n\t' +shopt -s nullglob # make sure globs are empty arrays if nothing is found + +result_file=$(mktemp) + +_passed_=0 +_failed_=0 + +exec 6<&1 +exec > "$result_file" + +function show_help { + exec 1>&6 6>&- + rm -f -- "$result_file" + grep "^##" $BASH_SOURCE | sed 's/^##.//' +} + +function output_results { + exec 1>&6 6>&- + local results="$(<$result_file)" + rm -f -- "$result_file" + local passes=$(printf '%s' "$results" | grep -F PASS | wc -l) + local fails=$(printf '%s' "$results" | grep -F '**** FAIL' | wc -l ) + printf '%s\n--SUMMARY\n%d PASSED\n%d FAILED\n' "$results" "$passes" "$fails" + [[ ${fails:-1} -eq 0 ]] + exit $? +} + +function pass { + echo " PASS" +} + +function fail { + echo "**** FAIL - expected:$( if [[ "$_negation_" == true ]]; then echo ' NOT'; fi; ) '$_expected_' | actual: '${_actual_[@]}'" +} + +function should_succeed { + _actual_=$? + _expected_=0 + _negation_=false + _pass_=false + [[ $_actual_ == $_expected_ ]] && _pass_=true + _expected_="0(success)" + _actual_="$_actual_(failed)" + _negation_check_ +} + +function should_fail { + _actual_=$? + _expected_=0 + _negation_=false + _pass_=true + [[ $_actual_ == $_expected_ ]] && _pass_=false + _expected_="NOT 0(fail)" + _actual_="0(succeeded)" + _negation_check_ +} + +# pattern - user supplied return variable name +function capture { + mapfile -t "${!#}" < "$1" +} + +#kph asks why? +TEMP="$(getopt -o h --long help \ + -n 'javawrap' -- $@)" + +if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi + +eval set -- "$TEMP" + +while true; do + case "$1" in + h | help ) show_help; exit 0 ;; + -- ) shift ;; + * ) shift; break ;; + esac +done + +trap output_results EXIT diff --git a/bash-spec.sh b/bash-spec.sh index 3078167..7db8780 100644 --- a/bash-spec.sh +++ b/bash-spec.sh @@ -1,39 +1,51 @@ #!/usr/bin/env bash -#================================================================================== -# BDD-style testing framework for bash scripts. -# -# expect variable [not] to_be value Compare scalar values for equality -# expect variable [not] to_match regex Regex match -# expect array [not] to_contain value Look for a value in an array -# expect_array arrayname [not] to_contain value Look for a value in an array (by ref) -# expect_var variablename [not] to_contain value Look for a value in a variable (by ref) -# expect filename [not] to_exist Verify file existence -# expect filename [not] to_have_mode modestring Verify file mode (permissions) -# expect [not] to_be_true condition Verify exit mode as boolean -# [[ some_expression ]] -# should_succeed -# [[ some_expression ]] -# should_fail -# +##================================================================================== +## BDD-style testing framework for bash scripts. +##. +## expect variable [not] to_be value Compare scalar values for equality +## expect variable [not] to_match regex Regex match +## expect array [not] to_contain value Look for a value in an array +## expect_array arrayname [not] to_contain value Look for a value in an array (by ref) +## expect_var variablename [not] to_contain value Look for a value in a variable (by ref) +## expect filename [not] to_exist Verify file existence +## expect filename [not] to_have_mode modestring Verify file mode (permissions) +## expect [not] to_be_true condition Verify exit mode as boolean +## [[ some_expression ]] +## should_succeed +## [[ some_expression ]] +## should_fail +##. +# Original Author: Dave Nicolette +# Version 1.0.0 29 Jul 2014 +# Release # Author: Dave Nicolette -# Date: 29 Jul 2014 +# License: MIT +# (GPL3 licence added to bash-spec after the bash-spec-2 fork) # Modified by REA Group 2014 +# License: MIT # Modified by keithy@consultant.com 03/2019 +# License: MIT #================================================================================== -# XXX: should use mktemp for proper random file name -- (GM) - + # http://redsymbol.net/articles/unofficial-bash-strict-mode/ set -uo pipefail IFS=$'\n\t' -shopt -s nullglob # make sure globs are empty arrays if nothing is foound +shopt -s nullglob # make sure globs are empty arrays if nothing is found + +result_file=$(mktemp) -result_file="$RANDOM" _passed_=0 _failed_=0 exec 6<&1 exec > "$result_file" +function show_help { + exec 1>&6 6>&- + rm -f -- "$result_file" + grep "^##" $BASH_SOURCE | sed 's/^##.//' +} + function output_results { exec 1>&6 6>&- local results="$(<$result_file)" @@ -218,6 +230,12 @@ function to_have_mode { _negation_check_ } +# pattern - user supplied return variable name +function capture { + mapfile -t "${!#}" < "$1" +} + +#kph asks why? TEMP="$(getopt -o h --long help \ -n 'javawrap' -- $@)" @@ -227,9 +245,9 @@ eval set -- "$TEMP" while true; do case "$1" in - -h | --help ) show_help; exit 0 ;; - -- ) shift; break ;; - * ) break ;; + h | help ) show_help; exit 0 ;; + -- ) shift ;; + * ) shift; break ;; esac done diff --git a/test_bash-spec-altfmt.sh b/test_bash-spec-altfmt.sh index c4ce430..7da4ee6 100755 --- a/test_bash-spec-altfmt.sh +++ b/test_bash-spec-altfmt.sh @@ -63,14 +63,21 @@ describe "The equality test" && { } } + + context "When a single value is passed (by ref)" && { + it "Reports two scalar values are equal" && { + one="1" + expect_var one to_be 1 + } + } + context "When a multi word value is passed" && { it "Reports two scalar values are equal" && { string="This is a string." expect "$string" to_be "This is a string." } - } context "When there is a failure" && { @@ -82,9 +89,7 @@ describe "The equality test" && { it "Reports the actual and expected correctly" && { expect "$result" to_be "**** FAIL - expected: 'Something else' | actual: 'Test text'" } - } - } describe "The inequality test" && { @@ -103,9 +108,7 @@ describe "The inequality test" && { it "Reports the actual and expected correctly" && { expect "$result" to_be "**** FAIL - expected: NOT '1' | actual: '1'" } - } - } describe "The regex matcher" && { @@ -125,9 +128,7 @@ describe "The regex matcher" && { it "Reports the actual and expected correctly" && { expect "$result" to_be "**** FAIL - expected: 'wibble$' | actual: 'one fine day'" } - } - } describe "The regex non-matcher" && { diff --git a/test_bash-spec-altfmt2.sh b/test_bash-spec-altfmt2.sh index 9c2fda4..2963555 100755 --- a/test_bash-spec-altfmt2.sh +++ b/test_bash-spec-altfmt2.sh @@ -61,16 +61,22 @@ describe "The equality test" && ( one="1" expect $one to_be 1 ) - ) + + context "When a single value is passed (by ref)" && ( + it "Reports two scalar values are equal" && ( + one="1" + expect_var one to_be 1 + ) + ) + context "When a multi word value is passed" && ( it "Reports two scalar values are equal" && ( string="This is a string." expect "$string" to_be "This is a string." ) - ) context "When there is a failure" && ( @@ -82,9 +88,7 @@ describe "The equality test" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: 'Something else' | actual: 'Test text'" ) - ) - ) describe "The inequality test" && ( @@ -103,9 +107,7 @@ describe "The inequality test" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT '1' | actual: '1'" ) - ) - ) describe "The regex matcher" && ( @@ -125,9 +127,7 @@ describe "The regex matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: 'wibble$' | actual: 'one fine day'" ) - ) - ) describe "The regex non-matcher" && ( @@ -147,9 +147,7 @@ describe "The regex non-matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT 'night$' | actual: 'one fine night'" ) - ) - ) describe "The array matcher" && ( @@ -169,9 +167,7 @@ describe "The array matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" ) - ) - ) describe "The array non-matcher" && ( @@ -191,11 +187,8 @@ describe "The array non-matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" ) - ) - ) - describe "The array (passed by reference) matcher" && ( declare -a arr=(1 2 3 4) @@ -213,11 +206,8 @@ describe "The array (passed by reference) matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: '5' | actual: '1 2 3 4'" ) - ) - ) - describe "The array (passed by reference) non-matcher" && ( declare -a arr=(1 2 3 4) @@ -235,9 +225,7 @@ describe "The array (passed by reference) non-matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT '4' | actual: '1 2 3 4'" ) - ) - ) @@ -260,7 +248,6 @@ describe "The file existence matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: 'tempfile EXISTS' | actual: 'File not found'" ) - ) rm -f tempfile @@ -285,7 +272,6 @@ describe "The file non-existence matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT 'tempfile EXISTS' | actual: 'tempfile'" ) - ) rm -f tempfile @@ -310,7 +296,6 @@ describe "The file mode matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: '-rw-rw-rwx' | actual: '-rw-r----x'" ) - ) rm -f tempfile @@ -335,7 +320,6 @@ describe "The file mode non-matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT '-rw-r----x' | actual: '-rw-r----x'" ) - ) rm -f tempfile @@ -364,9 +348,7 @@ describe "The exit mode matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: 'return_boolean false IS TRUE' | actual: 'return_boolean false IS FALSE'" ) - ) - ) describe "The exit mode non matcher" && ( @@ -391,9 +373,7 @@ describe "The exit mode non matcher" && ( it "Reports the actual and expected correctly" && ( expect "$result" to_be "**** FAIL - expected: NOT 'return_boolean true IS TRUE' | actual: 'return_boolean true IS TRUE'" ) - ) - ) describe "Setting variables when nesting" && ( @@ -409,7 +389,6 @@ describe "Setting variables when nesting" && ( it "Pulls a value into the inner it from the very outer level" && ( expect "$test_var" to_be "first value" ) - ) context "When the context overwrites the value" && ( @@ -419,11 +398,9 @@ describe "Setting variables when nesting" && ( it "Pulls the value into the inner it from the next level out" && ( expect "$test_var" to_be "second value" ) - ) it "Does not get affected by values set in inner nesting earlier on" && ( expect "$test_var" to_be "first value" ) - ) diff --git a/test_bash-spec.sh b/test_bash-spec.sh index 21ff1af..8402a51 100755 --- a/test_bash-spec.sh +++ b/test_bash-spec.sh @@ -64,6 +64,15 @@ describe "The equality test" "$( )" + context "When a single value is passed (by ref)" "$( + + it "Reports two scalar values are equal" "$( + one="1" + expect_var one to_be 1 + )" + + )" + context "When a multi word value is passed" "$( it "Reports two scalar values are equal" "$( From c86b05bff831e124fa938b83f7bf44c04cd23bb1 Mon Sep 17 00:00:00 2001 From: keithy Date: Sat, 17 Aug 2019 03:45:36 +0100 Subject: [PATCH 8/9] Handle expected failures & use travis-ci --- .travis.yml | 4 + Makefile | 5 + Makefile.test | 102 ++++++++++++++++++ README.md | 11 +- bash-spec-baby.sh | 25 ++--- bash-spec.sh | 10 +- .../test_bash-spec-altfmt.sh | 2 +- test_counters.sh => fails/test_counters.sh | 5 +- tests/README.md | 2 + tests/test_bash-altfmt.expected_fail.sh | 4 + .../test_bash-spec-altfmt2.sh | 2 +- test_bash-spec.sh => tests/test_bash-spec.sh | 2 +- tests/test_counters.expected_fail.sh | 4 + 13 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 Makefile.test rename test_bash-spec-altfmt.sh => fails/test_bash-spec-altfmt.sh (99%) rename test_counters.sh => fails/test_counters.sh (71%) create mode 100755 tests/README.md create mode 100755 tests/test_bash-altfmt.expected_fail.sh rename test_bash-spec-altfmt2.sh => tests/test_bash-spec-altfmt2.sh (99%) rename test_bash-spec.sh => tests/test_bash-spec.sh (99%) create mode 100755 tests/test_counters.expected_fail.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3748f77 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: bash + +script: + - make diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2bd1bad --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +TESTS ?= $(wildcard tests/*.sh) + +include Makefile.test + + diff --git a/Makefile.test b/Makefile.test new file mode 100644 index 0000000..adb9548 --- /dev/null +++ b/Makefile.test @@ -0,0 +1,102 @@ +# FROM: https://github.com/box/Makefile.test +# LICENCE: Apache2 +# +# Makefile that has a convenient check target. +# It can be included from another Makefile that only has a TESTS variable +# defined like this +# +# TESTS ?= +# +# Runs the specified test executables. Prepends the test's name to each test's output +# and gives a nice summary at the end of test execution about passed failed +# tests. + +# Only bash is supported +SHELL := /bin/bash + +THIS_FILE := $(realpath $(lastword $(MAKEFILE_LIST))) +# The directory where Makefile.test (this file) resides +THIS_FILE_DIR := $(shell dirname $(THIS_FILE)) + +# FIRST_MAKEFILE may be passed from parent make to child make. If it is not +# absent, do not overwrite it. +FIRST_MAKEFILE ?= $(realpath $(firstword $(MAKEFILE_LIST))) +export FIRST_MAKEFILE + +# The directory where the Makefile, that is invoked from the command line, +# resides. That makefile would define the TESTS variable. We assume that the +# binaries defined in the TESTS variable also reside in the directory as +# the Makefile. The generated intermediate files will also go to this directory. +FIRST_MAKEFILE_DIR ?= $(shell dirname $(FIRST_MAKEFILE)) +export FIRST_MAKEFILE_DIR + +# So that the child makefiles can see the same TESTS variable. +export TESTS + +failedTestsName := .makefile_test_failed_tests +executedTestsName := .makefile_test_executed_tests +TEST_TARGETS := $(TESTS:%=TARGET_FOR_%) +export TEST_TARGETS + +# If the tests need a different environment one can append to this variable. +TEST_ENVIRONMENT = PYTHONPATH=$(THIS_FILE_DIR):$$PYTHONPATH PATH=$(THIS_FILE_DIR):$$PATH + +# TODO: Only write to intermediate files, if they exist already. +# https://unix.stackexchange.com/q/405497/212862 +# There is still a race condition here. Maybe we should use sed for appending. +define RUN_ONE_TEST +TARGET_FOR_$(1): $$(FIRST_MAKEFILE_DIR)/$(1) + +@export PATH=$$$$(pwd):$$$$PATH; \ + if [ -e $$(FIRST_MAKEFILE_DIR)/$$(executedTestsName) ]; then \ + echo $$< >> $$(FIRST_MAKEFILE_DIR)/$$(executedTestsName); \ + fi; \ + $$(TEST_ENVIRONMENT) $$< 2>&1 | sed "s/^/ [$$$$(basename $$<)] /"; test $$$${PIPESTATUS[0]} -eq 0; \ + if [ $$$$? -eq 0 ]; then \ + echo " PASSED: $$$$(basename $$<)"; \ + else \ + echo " FAILED: $$$$(basename $$<)"; \ + if [ -e $$(FIRST_MAKEFILE_DIR)/$$(failedTestsName) ]; then \ + echo $$< >> $$(FIRST_MAKEFILE_DIR)/$$(failedTestsName); \ + fi; \ + fi; +endef + +# Build the above rule to run one test, for all tests. +$(foreach currtest,$(TESTS),$(eval $(call RUN_ONE_TEST,$(currtest)))) + +# execute the tests and look at the generated temp files afterwards. +actualCheck: $(TEST_TARGETS) + +@failed_tests=$$(cat $(FIRST_MAKEFILE_DIR)/$(failedTestsName) 2> /dev/null | wc -l;); \ + executed_tests=$$(cat $(FIRST_MAKEFILE_DIR)/$(executedTestsName) 2> /dev/null | wc -l;); \ + if [ $$failed_tests -ne 0 -a $$executed_tests -ne 0 ]; then \ + echo ---------------------------------; \ + echo "Failed $$failed_tests out of $$executed_tests tests"; \ + echo ---------------------------------; \ + elif [ $$failed_tests -eq 0 ]; then \ + echo ---------------------------------; \ + echo "All $$executed_tests tests passed"; \ + echo ---------------------------------; \ + fi; \ + exit $$failed_tests; + +# A commonly used bash command to clean intermediate files. Instead of writing +# it every time re-use this variable. +RM_INTERMEDIATE_FILES := rm -f $(FIRST_MAKEFILE_DIR)/$(failedTestsName) $(FIRST_MAKEFILE_DIR)/$(executedTestsName) + +# At the start of the make, we want to start with empty intermediate files. +TRUNCATE_INTERMEDIATE_FILES := cat /dev/null > $(FIRST_MAKEFILE_DIR)/$(failedTestsName) && cat /dev/null > $(FIRST_MAKEFILE_DIR)/$(executedTestsName) + +# With trap make sure the clean step is always executed before and after the +# tests run time. Do not leave residual files in the repo. +check: + +@trap "code=\$$?; \ + $(RM_INTERMEDIATE_FILES); \ + exit \$${code};" EXIT; \ + $(TRUNCATE_INTERMEDIATE_FILES); \ + $(MAKE) -f $(THIS_FILE) actualCheck; + +all: check + +.PHONY: all check preCheck actualCheck $(TEST_TARGETS) +.DEFAULT_GOAL := all + diff --git a/README.md b/README.md index aab20d3..6e9cc5d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) +[![Build Status](https://travis-ci.com/keithy/bash-spec-2.svg?branch=master)](https://travis-ci.com/keithy/bash-spec-2) +[![GitHub issues](https://img.shields.io/github/issues/keithy/bash-spec-2.svg)](https://github.com/keithy/bash-spec-2/issues) +[![Latest Version](https://img.shields.io/github/release/keithy/bash-spec-2.svg)](https://github.com/keithy/bash-spec-2/releases) + New for 2.1 =========== #### Supporting other coding styles/formats ``` old format: describe "title" "$( ... )" -alt format: describe "title" && { ... } (most readable but predictably fails one test) +alt format: describe "title" && { ... } (most readable but variables are not locally scoped) alt format2: describe "title" && ( ... ) (compromise?) ``` #### Assertions on expressions @@ -13,7 +18,7 @@ should_succeed [[ some_expression ]] should_fail ``` -#### Cleaner support for arrays - pass by reference +#### Cleaner support for arrays and vars - pass by reference ``` expect_var varname to_be 5 @@ -24,6 +29,8 @@ expect_array arrayname to_contain 5 http://redsymbol.net/articles/unofficial-bash-strict-mode/ +#### Travis-CI + bash-spec ========= diff --git a/bash-spec-baby.sh b/bash-spec-baby.sh index 05f2611..83186c8 100644 --- a/bash-spec-baby.sh +++ b/bash-spec-baby.sh @@ -1,16 +1,17 @@ #!/usr/bin/env bash #================================================================================== -# Minimal testing framework for bash scripts. -# -# [[ some_expression ]] -# should_succeed -# [[ some_expression ]] -# should_fail -# -# bash-spec Author: Dave Nicolette -# Date: 29 Jul 2014 -# Modified by REA Group 2014 -# bash-spec-baby by keithy@consultant.com 03/2019 +## Minimal testing framework for bash scripts. +## usage: +## +## [[ some_expression ]] +## should_succeed +## [[ some_expression ]] +## should_fail +## +## bash-spec Author: Dave Nicolette +## Date: 29 Jul 2014 +## Modified by REA Group 2014 +## bash-spec-baby by keithy@consultant.com 03/2019 #================================================================================== # http://redsymbol.net/articles/unofficial-bash-strict-mode/ @@ -29,7 +30,7 @@ exec > "$result_file" function show_help { exec 1>&6 6>&- rm -f -- "$result_file" - grep "^##" $BASH_SOURCE | sed 's/^##.//' + grep "^##" $BASH_SOURCE | sed 's/^##//' } function output_results { diff --git a/bash-spec.sh b/bash-spec.sh index 7db8780..2f11dd2 100644 --- a/bash-spec.sh +++ b/bash-spec.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash ##================================================================================== ## BDD-style testing framework for bash scripts. -##. +## ## expect variable [not] to_be value Compare scalar values for equality ## expect variable [not] to_match regex Regex match ## expect array [not] to_contain value Look for a value in an array @@ -14,7 +14,7 @@ ## should_succeed ## [[ some_expression ]] ## should_fail -##. +## # Original Author: Dave Nicolette # Version 1.0.0 29 Jul 2014 # Release @@ -43,7 +43,7 @@ exec > "$result_file" function show_help { exec 1>&6 6>&- rm -f -- "$result_file" - grep "^##" $BASH_SOURCE | sed 's/^##.//' + grep "^##" $BASH_SOURCE | sed 's/^##//' } function output_results { @@ -246,8 +246,8 @@ eval set -- "$TEMP" while true; do case "$1" in h | help ) show_help; exit 0 ;; - -- ) shift ;; - * ) shift; break ;; + -- ) break ;; + * ) break ;; esac done diff --git a/test_bash-spec-altfmt.sh b/fails/test_bash-spec-altfmt.sh similarity index 99% rename from test_bash-spec-altfmt.sh rename to fails/test_bash-spec-altfmt.sh index 7da4ee6..dec2a76 100755 --- a/test_bash-spec-altfmt.sh +++ b/fails/test_bash-spec-altfmt.sh @@ -2,7 +2,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/bash-spec.sh" +. "$DIR/../bash-spec.sh" describe "The bash version test" && { diff --git a/test_counters.sh b/fails/test_counters.sh similarity index 71% rename from test_counters.sh rename to fails/test_counters.sh index 1db3843..f84a9ac 100755 --- a/test_counters.sh +++ b/fails/test_counters.sh @@ -1,5 +1,8 @@ #! /bin/bash -. ./bash-spec.sh + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/../bash-spec.sh" describe "A simple test" "$( diff --git a/tests/README.md b/tests/README.md new file mode 100755 index 0000000..2db8580 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,2 @@ +These files run the tests for CI, which PASS expected failures + diff --git a/tests/test_bash-altfmt.expected_fail.sh b/tests/test_bash-altfmt.expected_fail.sh new file mode 100755 index 0000000..fee521f --- /dev/null +++ b/tests/test_bash-altfmt.expected_fail.sh @@ -0,0 +1,4 @@ +# This is run in the directory context of the Makefile +./fails/test_bash-spec-altfmt.sh | grep "1 FAILED" + + diff --git a/test_bash-spec-altfmt2.sh b/tests/test_bash-spec-altfmt2.sh similarity index 99% rename from test_bash-spec-altfmt2.sh rename to tests/test_bash-spec-altfmt2.sh index 2963555..3544ae7 100755 --- a/test_bash-spec-altfmt2.sh +++ b/tests/test_bash-spec-altfmt2.sh @@ -2,7 +2,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/bash-spec.sh" +. "$DIR/../bash-spec.sh" describe "The bash version test" && ( diff --git a/test_bash-spec.sh b/tests/test_bash-spec.sh similarity index 99% rename from test_bash-spec.sh rename to tests/test_bash-spec.sh index 8402a51..40a1ceb 100755 --- a/test_bash-spec.sh +++ b/tests/test_bash-spec.sh @@ -2,7 +2,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/bash-spec.sh" +. "$DIR/../bash-spec.sh" describe "The bash version test" "$( diff --git a/tests/test_counters.expected_fail.sh b/tests/test_counters.expected_fail.sh new file mode 100755 index 0000000..0c3011a --- /dev/null +++ b/tests/test_counters.expected_fail.sh @@ -0,0 +1,4 @@ +# This is run in the directory context of the Makefile +./fails/test_counters.sh | grep "1 FAILED" + + From e57b916a6dbdea3321877bd6dca71edc29a3b642 Mon Sep 17 00:00:00 2001 From: keithy Date: Sat, 17 Aug 2019 14:53:07 +0100 Subject: [PATCH 9/9] tidied tests --- .travis.yml | 2 +- Makefile | 5 ----- tests/Makefile | 5 +++++ Makefile.test => tests/Makefile.test | 0 {fails => tests/fails}/test_bash-spec-altfmt.sh | 2 +- {fails => tests/fails}/test_counters.sh | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 Makefile create mode 100644 tests/Makefile rename Makefile.test => tests/Makefile.test (100%) rename {fails => tests/fails}/test_bash-spec-altfmt.sh (99%) rename {fails => tests/fails}/test_counters.sh (91%) diff --git a/.travis.yml b/.travis.yml index 3748f77..0acb2e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: bash script: - - make + - make -C tests diff --git a/Makefile b/Makefile deleted file mode 100644 index 2bd1bad..0000000 --- a/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -TESTS ?= $(wildcard tests/*.sh) - -include Makefile.test - - diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..6962d28 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,5 @@ +TESTS ?= $(wildcard *.sh) + +include Makefile.test + + diff --git a/Makefile.test b/tests/Makefile.test similarity index 100% rename from Makefile.test rename to tests/Makefile.test diff --git a/fails/test_bash-spec-altfmt.sh b/tests/fails/test_bash-spec-altfmt.sh similarity index 99% rename from fails/test_bash-spec-altfmt.sh rename to tests/fails/test_bash-spec-altfmt.sh index dec2a76..f71f633 100755 --- a/fails/test_bash-spec-altfmt.sh +++ b/tests/fails/test_bash-spec-altfmt.sh @@ -2,7 +2,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/../bash-spec.sh" +. "$DIR/../../bash-spec.sh" describe "The bash version test" && { diff --git a/fails/test_counters.sh b/tests/fails/test_counters.sh similarity index 91% rename from fails/test_counters.sh rename to tests/fails/test_counters.sh index f84a9ac..b49a99d 100755 --- a/fails/test_counters.sh +++ b/tests/fails/test_counters.sh @@ -2,7 +2,7 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/../bash-spec.sh" +. "$DIR/../../bash-spec.sh" describe "A simple test" "$(