diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0acb2e1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: bash + +script: + - make -C tests diff --git a/README.md b/README.md index 40638fb..6e9cc5d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,36 @@ +[![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 variables are not locally scoped) +alt format2: describe "title" && ( ... ) (compromise?) +``` +#### Assertions on expressions +``` +[[ some_expression ]] +should_succeed +[[ some_expression ]] +should_fail +``` +#### Cleaner support for arrays and vars - pass by reference + +``` +expect_var varname to_be 5 +expect_array arrayname to_contain 5 +``` + +#### Unofficial bash strict mode + +http://redsymbol.net/articles/unofficial-bash-strict-mode/ + +#### Travis-CI + bash-spec ========= @@ -65,7 +98,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: @@ -80,7 +114,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. @@ -92,3 +129,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 + diff --git a/bash-spec-baby.sh b/bash-spec-baby.sh new file mode 100644 index 0000000..83186c8 --- /dev/null +++ b/bash-spec-baby.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +#================================================================================== +## 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/ +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 66e19dd..2f11dd2 100644 --- a/bash-spec.sh +++ b/bash-spec.sh @@ -1,27 +1,51 @@ -#!/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 -# +#!/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 +## +# 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 #================================================================================== + +# 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) -# XXX: should use mktemp for proper random file name -- (GM) -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)" @@ -58,15 +82,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 +104,32 @@ 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_ +} + function expect { _expected_= _negation_=false + _pass_=false declare -a _actual_ until [[ "${1:0:3}" == to_ || "$1" == not || -z "$1" ]]; do _actual_+=("$1") @@ -88,6 +138,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 "$@" @@ -160,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' -- $@)" @@ -169,8 +245,8 @@ eval set -- "$TEMP" while true; do case "$1" in - -h | --help ) show_help; exit 0 ;; - -- ) shift; break ;; + h | help ) show_help; exit 0 ;; + -- ) break ;; * ) break ;; esac done 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/tests/Makefile.test b/tests/Makefile.test new file mode 100644 index 0000000..adb9548 --- /dev/null +++ b/tests/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/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/fails/test_bash-spec-altfmt.sh b/tests/fails/test_bash-spec-altfmt.sh new file mode 100755 index 0000000..f71f633 --- /dev/null +++ b/tests/fails/test_bash-spec-altfmt.sh @@ -0,0 +1,430 @@ +#!/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 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" && { + + 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_counters.sh b/tests/fails/test_counters.sh similarity index 71% rename from test_counters.sh rename to tests/fails/test_counters.sh index 9bd015d..b49a99d 100755 --- a/test_counters.sh +++ b/tests/fails/test_counters.sh @@ -1,5 +1,8 @@ #! /bin/bash -. ./bash-spec-ext.sh + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/../../bash-spec.sh" describe "A simple test" "$( 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/tests/test_bash-spec-altfmt2.sh b/tests/test_bash-spec-altfmt2.sh new file mode 100755 index 0000000..3544ae7 --- /dev/null +++ b/tests/test_bash-spec-altfmt2.sh @@ -0,0 +1,406 @@ +#!/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 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" && ( + + 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/tests/test_bash-spec.sh similarity index 75% rename from test_bash-spec.sh rename to tests/test_bash-spec.sh index d8e52ec..40a1ceb 100755 --- a/test_bash-spec.sh +++ b/tests/test_bash-spec.sh @@ -1,8 +1,57 @@ -#! /bin/bash +#!/usr/bin/env bash DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/bash-spec.sh" +. "$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" "$( @@ -15,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" "$( @@ -147,6 +205,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 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" + +