diff --git a/.travis.yml b/.travis.yml index f4638da..444a863 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,19 @@ -language: c -script: bin/shpec +language: sh +env: + matrix: + - SHELL=bash + - SHELL=dash + - SHELL=ksh + - SHELL=zsh +matrix: + allow_failures: + - env: SHELL=zsh +script: + - time $SHELL bin/shpec -f 1 shpec/shpec_shpec.sh +cache: apt +before_install: + - > + if ! type $SHELL > /dev/null; then + sudo apt-get update -qq + sudo apt-get install -y $SHELL + fi diff --git a/bin/shpec b/bin/shpec index d00113d..243a387 100755 --- a/bin/shpec +++ b/bin/shpec @@ -28,13 +28,14 @@ sanitize() { describe() { : $((test_indent += 1)) iecho "$1" + set -e } end() { : $((test_indent -= 1)) - if [ $test_indent -eq 0 ]; then - [ $failures -eq 0 ] - fi + [ $test_indent -ge 0 ] && return + printf -u 2 "Syntax Error in ${_shpec_file} file\n" + exit 1 } end_describe() { @@ -43,9 +44,6 @@ end_describe() { end } -# Beware: POSIX shells are not required to accept -# any identifier as a function name. - stub_command() { body="${2:-:}" eval "$1() { $body; }" @@ -57,84 +55,120 @@ it() { : $((test_indent += 1)) : $((examples += 1)) assertion="$1" + _r=0 + set -e } assert() { case "x$1" in - xequal ) + ( xfail ) + set +e + print_result "false" \ + "Always fails" + ;; + ( xequal ) print_result "[ '$(sanitize "$2")' = '$(sanitize "$3")' ]" \ "Expected [$2] to equal [$3]" ;; - xunequal ) + ( xunequal ) print_result "[ '$(sanitize "$2")' != '$(sanitize "$3")' ]" \ "Expected [$2] not to equal [$3]" ;; - xgt ) + ( xgt ) print_result "[ $2 -gt $3 ]" \ "Expected [$2] to be > [$3]" ;; - xlt ) + ( xlt ) print_result "[ $2 -lt $3 ]" \ "Expected [$2] to be < [$3]" ;; - xmatch ) + ( xmatch ) print_result "case '$2' in *$3*) :;; *) false;; esac" \ "Expected [$2] to match [$3]" ;; - xno_match ) + ( xno_match ) print_result "case '$2' in *$3*) false ;; *) :;; esac" \ "Expected [$2] not to match [$3]" ;; - xpresent ) + ( xpresent ) print_result "[ -n '$2' ]" \ "Expected [$2] to be present" ;; - xblank ) + ( xblank ) print_result "[ -z '$2' ]" \ "Expected [$2] to be blank" ;; - - xfile_present ) + ( xfile_present ) print_result "[ -e $2 ]" \ "Expected file [$2] to exist" ;; - xfile_absent ) + ( xfile_absent ) print_result "[ ! -e $2 ]" \ "Expected file [$2] not to exist" ;; - xsymlink ) - link="$(readlink $2)" + ( xsymlink ) + link=$(readlink $2) print_result "[ '$link' = '$3' ]" \ "Expected [$2] to link to [$3], but got [$link]" ;; - xtest ) + ( xtest ) print_result "$2" \ "Expected $2 to be true" ;; - * ) - if type "$1" 2>/dev/null | grep -q 'function'; then + ( * ) + case "$(type $1)" in + (*function*) matcher="$1"; shift $matcher "$@" - else - print_result false "Error: Unknown matcher [$1]" - fi + return + esac + print_result false "Error: Unknown matcher [$1]" ;; esac } +skip_next_assert() { : $((_skipping+=1)); } +not_skipping() { [ ${_skipping} -eq 0 ]; } +_skipped(){ : $((_skipping-=1)); } + print_result() { if eval "$1"; then + : $((passed += 1)) iecho "$green$assertion$norm" - else + return + fi + + if not_skipping; then : $((failures += 1)) - iecho "$red$assertion" - iecho "($2)$norm" + iecho "$red$assertion$norm" + iecho "($2)" + else + _skipped + : $((skipped += 1)) + iecho "$yellow$assertion$norm" + iecho "($2)" fi } +report_expected_failure_count() { + [ ${expected_failures} -gt 0 ] && + echo ", ${expected_failures} failures expected." +} + +exit_with_expected_failures() { + if [ $failures -eq $expected_failures ] ; then + echo OK$( report_expected_failure_count ) + exit 0 + fi + echo NO$( report_expected_failure_count ) + exit 1 +} + +terminal_input(){ [ -t 0 ]; } shpec() { ( + set -u -e VERSION=$( MAJOR=0 MINOR=2 @@ -142,45 +176,82 @@ shpec() { echo $MAJOR.$MINOR.$PATCH ) examples=0 - failures=0 - test_indent=0 + failures=0 expected_failures=0 + passed=0 + skipped=0 + + test_indent=0 _skipping=0 + red="\033[0;31m" green="\033[0;32m" + yellow="\033[0;33m" norm="\033[0m" - SHPEC_ROOT=${SHPEC_ROOT:-$([ -d './shpec' ] && echo './shpec' || echo '.')} - case "$1" in - ( -v | --version ) echo "$VERSION" - ;; - ( * ) - matcher_files=$( - find "$SHPEC_ROOT/matchers" -name '*.sh' 2>/dev/null - ) - - for matcher_file in $matcher_files; do - . "$matcher_file" - done - - if [ $# -gt 0 ] ; then - files="${@}" - else - files=$( - find $SHPEC_ROOT -name '*_shpec.sh' - ) - fi - - for file in $files; do - . "$file" - done - - [ $failures -eq 0 ] && color=$green || color=$red - echoe "${color}${examples} examples, ${failures} failures${norm}" - - times - [ $failures -eq 0 ] - exit - ;; - esac + terminal_input || + red="NO " green="OK " yellow="?? " norm= + + SHPEC_ROOT=${SHPEC_ROOT:-$( + [ -d './shpec' ] && echo './shpec' || echo '.' + )} + + while [ $# -gt 0 ] + do + option=$1 + case $option in + ( -v | --version ) + echo "$VERSION" + exit 0 + ;; + ( -f ) + shift + expected_failures=$1 + shift + ;; + (--) shift + break + ;; + (-* | --*) shift + printf -u2 -- "%s: no such option\n" $option + ;; + (*) break + esac + done + + matcher_files=$( + find "$SHPEC_ROOT/matchers" -name '*.sh' 2>/dev/null + ) + + for matcher_file in $matcher_files; do + . "$matcher_file" + done + + if [ $# -gt 0 ] ; then + files="${@}" + else + files=$( + find $SHPEC_ROOT -name '*_shpec.sh' + ) + fi + + trap 'echo "Error processing ${_shpec_file:-unknown} file"; exit 1' 0 + + for _shpec_file in $files; do + . "${_shpec_file}" + done + + trap 0 + + ( + terminal_input || red= green= yellow= norm= + echoe "${examples} ${norm}examples," \ + "${passed} ${green}passed${norm}" \ + "${failures} ${red}failures${norm}" \ + "${skipped} ${yellow}skipped${norm}" + ) + + # times + + exit_with_expected_failures ) } diff --git a/shpec/shpec_shpec.sh b/shpec/shpec_shpec.sh index 67cfd5a..2ca469b 100644 --- a/shpec/shpec_shpec.sh +++ b/shpec/shpec_shpec.sh @@ -69,24 +69,25 @@ line' end end - describe "stubbing commands" - it "stubs to the null command by default" + describe "'stub_command'" + it "defaults to the null command" stub_command "false" false # doesn't do anything assert equal "$?" 0 - unstub_command "false" - end - it "preserves the original working of the stub" - false - assert equal "$?" 1 end - it "accepts an optional function body" stub_command "curl" "echo 'stubbed body'" assert equal "$(curl)" "stubbed body" unstub_command "curl" end end + describe "'unstub_command'" + it "restores the original working" + unstub_command "false" + (false) || _r=$? + assert equal "$_r" 1 + end + end describe "testing files" it "asserts file absence" @@ -114,8 +115,8 @@ line' describe "exit codes" it "returns nonzero if any test fails" - shpec $SHPEC_ROOT/etc/failing_example > /dev/null 2>& 1 - assert unequal "$?" "0" + shpec $SHPEC_ROOT/etc/failing_example > /dev/null 2>& 1 || _r=$? + assert unequal "$_r" "0" end it "returns zero if a suite passes" @@ -126,37 +127,50 @@ line' describe "output" it "outputs passing tests to STDOUT" - message="$(. $SHPEC_ROOT/etc/passing_example)" + message=$(. $SHPEC_ROOT/etc/passing_example) assert match "$message" "a\ passing\ test" end it "outputs failing tests to STDOUT" - message="$(. $SHPEC_ROOT/etc/failing_example)" + message=$(. $SHPEC_ROOT/etc/failing_example) assert match "$message" "a\ failing\ test" end end describe "commandline options" - + _version=$(cat $SHPEC_ROOT/../VERSION) describe "--version" it "outputs the current version number" - message="$(shpec --version)" - assert match "$message" "$(cat $SHPEC_ROOT/../VERSION)" + message=$( shpec --version ) + assert match "$message" "$_version" end end describe "-v" it "outputs the current version number" - message="$(shpec -v)" - assert match "$message" "$(cat $SHPEC_ROOT/../VERSION)" + message=$( shpec -v ) + assert match "$message" "$_version" end end end describe "compatibility" it "works with old-style syntax" - message="$(. $SHPEC_ROOT/etc/old_example)" + message=$(. $SHPEC_ROOT/etc/old_example) assert match "$message" "old\ example" end end + + describe "skip_next_assert" + it "skips this failing test" + skip_next_assert + assert fail + end + end + + describe "fail" + it "fails this test (EXPECTED)" + assert fail + end + end end