├── .travis.yml ├── CHANGELOG.md ├── COMPATIBILITY.md ├── LICENSE.txt ├── Makefile ├── README.md ├── VERSION ├── bin └── shpec ├── install.sh ├── package.json ├── shpec.plugin.zsh └── shpec ├── etc ├── example with spaces ├── failing_example ├── multi_assert_example ├── multi_assert_fail_example ├── old_example ├── passing_example └── syntax_error ├── matchers └── custom.sh ├── shell_compatibility_shpec.sh └── shpec_shpec.sh /.travis.yml: -------------------------------------------------------------------------------- 1 | language: sh 2 | cache: apt 3 | env: 4 | - LINT=true 5 | - SHELL=bash 6 | - SHELL=dash 7 | - SHELL=ksh 8 | - SHELL=zsh 9 | - SHELL=bash NON_ENGLISH=true LC_ALL=fr_FR.UTF-8 10 | matrix: 11 | fast_finish: true 12 | include: 13 | - env: 14 | - SHELL=bash 15 | os: osx 16 | script: 17 | - | 18 | if [ -n "${LINT}" ]; then 19 | shellcheck --version 20 | shellcheck -s sh bin/shpec # 'sh' refers to POSIX 21 | else 22 | $SHELL bin/shpec 23 | fi 24 | before_install: 25 | - | 26 | install_shell() { 27 | echo "Installing $SHELL" 28 | sudo apt-get update -qq 29 | sudo apt-get install -y $SHELL 30 | } 31 | install_language_pack() { 32 | echo "Installing language pack" 33 | sudo apt-get --reinstall install -qq language-pack-fr 34 | } 35 | 36 | if [ "${SHELL}" = "zsh" ]; then 37 | echo "disable -r end; setopt sh_word_split" >> $HOME/.zshenv 38 | fi 39 | 40 | if [ "${TRAVIS_OS_NAME}" = "linux" ]; then 41 | if [ -n "${NON_ENGLISH}" ]; then 42 | install_language_pack 43 | fi 44 | 45 | if ! type $SHELL > /dev/null; then 46 | install_shell 47 | fi 48 | fi 49 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # shpec Changelog 2 | 3 | ## Unreleased 4 | * Handles arguments containing whitespace (#112) 5 | * Updates URI in installation instructions and screenshot (#124) 6 | 7 | ## 0.3.1 (Dec 16, 2018) 8 | * Replaces usage of `type` for better POSIX compatibility 9 | * Better `tar` compatibility in install script 10 | 11 | ## 0.3.0 (Apr 26, 2017) 12 | * custom matchers now work in non-english locales (#113) 13 | * tests containing multiple assertions are aggregated to a single line of 14 | output 15 | * shpec files are no longer restricted to the .sh extension 16 | 17 | ## 0.2.2 (May 26, 2015) 18 | * Fix Antigen plugin to work with new shpec syntax 19 | 20 | ## 0.2.1 (May 11 2015) 21 | * shpec now exits on an unbalanced `end` statement 22 | * shpec can now be installed using bpkg 23 | 24 | ## 0.2.0 (Apr 23 2015) 25 | * POSIX support - shpec now works on bash, dash, and ksh93 26 | 27 | ## 0.1.2 (Feb 16 2015) 28 | * Add Antigen package manager support 29 | 30 | ## 0.1.1 (Dec 17 2014) 31 | * Add no_match matcher 32 | * Refactor indented printing 33 | 34 | ## 0.1.0 (Nov 20 2014) 35 | 36 | * Add end function 37 | 38 | ## 0.0.10 (Jul 16 2014) 39 | 40 | * Allow comparison of strings containing quotes 41 | 42 | ## 0.0.9 (Mar 7 2013) 43 | 44 | * Set SHPEC_ROOT without using find 45 | 46 | ## 0.0.8 (Mar 6 2013) 47 | 48 | * Pass in SHPEC_ROOT as an env variable 49 | * Gt lt bugfix 50 | 51 | ## 0.0.7 (Mar 3 2013) 52 | 53 | * Add --version command line option 54 | * Add default TMPDIR 55 | 56 | ## 0.0.6 (Feb 27 2013) 57 | 58 | * Custom Matchers 59 | 60 | ## 0.0.5 (Feb 23 2013) 61 | 62 | * Remove sudo from installer 63 | 64 | ## 0.0.4 (Feb 22, 2013) 65 | 66 | * Add function to stub commands 67 | * Allow a function body to be passed to stub_command 68 | * Equality matcher handles newlines 69 | 70 | ## 0.0.3 (Feb 8, 2013) 71 | 72 | * Cleanup Makefile for Homebrew 73 | * Color final summary output 74 | * Less hacky indenting 75 | * errors_to_stdout: 76 | * Shpec files use _shpec.sh naming convention 77 | * Install one liner 78 | * Moar assertions 79 | * Add tests for gt lt asserts 80 | * Add time and summary 81 | * Add file matchers 82 | * Add 'unequal' and 'prsent' matchers 83 | * Rename file suffix from spec to shpec 84 | * Test exit codes 85 | -------------------------------------------------------------------------------- /COMPATIBILITY.md: -------------------------------------------------------------------------------- 1 | Shpec Compatibility Notes 2 | ------------------------- 3 | 4 | The following are shell-specific quirks you might care about when using `shpec`. 5 | 6 | ## zsh 7 | 8 | ### Disabling the 'end' Keyword 9 | If you use `zsh` you must disable the `end` keyword. This won't be that big of a problem 10 | since it belongs to a `foreach...end` structure that is rarely used. 11 | 12 | To disable it, just run `disable -r end`. 13 | Otherwise you'll get the following error `parse error near 'end'` 14 | 15 | If you have installed `shpec` through `antigen`, there is nothing you need to do, 16 | `shpec.plugin.zsh` defines an alias that does this for you: 17 | 18 | alias shpec="zsh -c 'disable -r end; . $(dirname $0:A)/bin/shpec'" 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Ryland Herrick 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | BINDIR ?= $(PREFIX)/bin 3 | 4 | all: shpec 5 | 6 | release: 7 | sed -i '' "s/^\( *\)VERSION=.*/\1VERSION=`cat VERSION`/" bin/shpec install.sh 8 | sed -i '' "s/^\( *\)\"version\":.*/\1\"version\": \"`cat VERSION`\",/" package.json 9 | git add install.sh bin/shpec package.json VERSION 10 | git commit -m "Release `cat VERSION`" || true 11 | git push origin master 12 | git tag `cat VERSION` 13 | git push --tags 14 | 15 | install: 16 | mkdir -p $(BINDIR) 17 | install bin/shpec $(BINDIR)/ 18 | 19 | uninstall: 20 | rm -f $(BINDIR)/shpec 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shpec [![Build Status](https://travis-ci.org/rylnd/shpec.svg?branch=master)](https://travis-ci.org/rylnd/shpec) [![Gitter](https://badges.gitter.im/rylnd/shpec.svg)](https://gitter.im/rylnd/shpec?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 2 | ---- 3 | 4 | *Test your shell scripts!* 5 | 6 |

7 | Screenshot of shpec 9 |

10 | 11 | ## Using shpec 12 | This repo itself is using `shpec`, so feel free to use it as an example. 13 | Here is the basic structure that you'll want: 14 | 15 | └── shpec 16 | └── an_example_shpec.sh 17 | └── another_shpec.sh 18 | 19 | Then to run your tests: 20 | 21 | ```bash 22 | shpec [shpec_files] 23 | ``` 24 | 25 | If you'd like your tests to run automatically when they change, we recommend the [entr]( 26 | http://entrproject.org/) utility: 27 | 28 | ```bash 29 | find . -name "*_shpec.sh" | entr shpec 30 | ``` 31 | 32 | Note that there are some shell specificities, you'll find more about them in the [Compatibility file](./COMPATIBILITY.md). 33 | 34 | ### Structuring your Tests 35 | `shpec` is similar to other *BDD* frameworks like 36 | [`RSpec`]( 37 | https://github.com/rspec/rspec), [`Jasmine`]( 38 | https://github.com/jasmine/jasmine), and [`mocha`]( 39 | https://github.com/mochajs/mocha). 40 | 41 | The two main constructs are `describe/end` (used to group tests) and `it/end` 42 | (used to describe an individual test and wrap assertions). 43 | 44 | __Note:__ Since your test files will be sourced into `shpec`, you can use any 45 | shell command that would normally be available in your session. 46 | 47 | ### Examples 48 | [shpec's own tests]( 49 | https://github.com/rylnd/shpec/tree/master/shpec/shpec_shpec.sh) 50 | are a great place to start. For more examples, see the [wiki page]( 51 | https://github.com/rylnd/shpec/wiki/Examples) 52 | 53 | ### Matchers 54 | The general format of an assertion is: 55 | 56 | assert matcher arguments 57 | 58 | where `matcher` is one of the following: 59 | 60 | #### Binary Matchers 61 | ```bash 62 | equal # equality 63 | unequal # inequality 64 | gt # algebraic '>' 65 | lt # algebraic '<' 66 | glob # glob match 67 | no_glob # lack of glob match 68 | grep # regex match (grep style) 69 | no_grep # lack of regex match (grep style) 70 | egrep # regex match (egrep style) 71 | no_egrep # lack of regex match (egrep style) 72 | ``` 73 | 74 | #### Unary Matchers 75 | ```bash 76 | present # string presence 77 | blank # string absence 78 | file_present # file presence 79 | file_absent # file absence 80 | symlink # tests a symlink's target 81 | test # evaluates a test string 82 | ``` 83 | 84 | #### Custom Matchers 85 | Custom matchers are loaded from `shpec/matchers/*.sh`. 86 | 87 | For example, here's how you'd create a `still_alive` matcher: 88 | 89 | ```bash 90 | # in shpec/matchers/network.sh 91 | still_alive() { 92 | ping -oc1 "$1" > /dev/null 2>&1 93 | assert equal "$?" 0 94 | } 95 | ``` 96 | 97 | Then you can use that matcher like any other: 98 | 99 | ```bash 100 | # in shpec/network_shpec.sh 101 | describe "my server" 102 | it "serves responses" 103 | assert still_alive "my-site.com" 104 | end 105 | end 106 | ``` 107 | 108 | ### Stubbing 109 | You can stub commands using `stub_command`. 110 | This function takes the name of the command you wish to stub. If provided, 111 | the second argument will be used as the body of the command. (code that would be evaluated) 112 | Once you're done, you can delete it with `unstub_command`. 113 | 114 | The best example is the [shpec test for this feature]( 115 | https://github.com/rylnd/shpec/blob/master/shpec/shpec_shpec.sh#L121-L139). 116 | 117 | 118 | ## Installation 119 | you can either install with curl 120 | ```bash 121 | sh -c "`curl -L https://raw.githubusercontent.com/rylnd/shpec/master/install.sh`" 122 | ``` 123 | 124 | or with [antigen](https://github.com/zsh-users/antigen) for zsh by 125 | putting `antigen bundle rylnd/shpec` in your `.zshrc` 126 | 127 | ## Contributing 128 | Pull requests are always welcome. 129 | 130 | If you've got a test or custom matcher you're particularly proud of, 131 | please consider adding it to [the Examples page]( 132 | https://github.com/rylnd/shpec/wiki/Examples)! 133 | 134 | ### Style and code conventions 135 | 136 | #### Language: POSIX shell 137 | The core `shpec` script and function should work the same in 138 | any POSIX compliant shell. You can use `shpec` to test scripts 139 | that use non-POSIX features, but you must avoid them when extending 140 | `shpec` or the main `shpec_shpech.sh` tests. 141 | 142 | #### Namespace 143 | Any variables starting with `_shpec_` are reserved for internal use and 144 | should not be used in test cases (except perhaps for test cases of `shpec` 145 | itself). 146 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.1 2 | -------------------------------------------------------------------------------- /bin/shpec: -------------------------------------------------------------------------------- 1 | # vim: set ft=sh: 2 | 3 | indent() { 4 | printf '%*s' $(( (_shpec_indent - 1) * 2)) 5 | } 6 | 7 | echoe() { printf "%b\n" "$*"; } 8 | 9 | iecho() { 10 | indent && echoe "$@" 11 | } 12 | 13 | sanitize() { 14 | IFS= echoe "$1" | tr '\n' 'n' | tr "'" 'q' 15 | } 16 | 17 | describe() { 18 | : $((_shpec_indent += 1)) 19 | iecho "$1" 20 | } 21 | 22 | end() { 23 | : $((_shpec_indent -= 1)) 24 | _shpec_assertion_printed=0 25 | [ $_shpec_indent -ge 0 ] && return 0 26 | echo >& 2 "shpec: $_shpec_file: unexpected 'end'" 27 | exit 1 28 | } 29 | 30 | end_describe() { 31 | iecho "Warning: end_describe will be deprecated in shpec 1.0." \ 32 | "Please use end instead." 33 | end 34 | } 35 | 36 | # Beware: POSIX shells are not required to accept 37 | # any identifier as a function name. 38 | 39 | stub_command() { 40 | _shpec_stub_body="${2:-:}" 41 | eval "$1() { ${_shpec_stub_body}; }" 42 | } 43 | 44 | unstub_command() { unset -f "$1"; } 45 | 46 | it() { 47 | : $((_shpec_indent += 1)) 48 | : $((_shpec_examples += 1)) 49 | _shpec_assertion="$1" 50 | } 51 | 52 | is_function() { 53 | case $(LC_ALL=C command -V "$1" 2> /dev/null) in 54 | (*function*) return 0;; 55 | esac 56 | return 1 57 | } 58 | 59 | assert() { 60 | case "x$1" in 61 | ( xequal ) 62 | print_result "[ '$(sanitize "$2")' = '$(sanitize "$3")' ]" \ 63 | "Expected [$2] to equal [$3]" 64 | ;; 65 | ( xunequal ) 66 | print_result "[ '$(sanitize "$2")' != '$(sanitize "$3")' ]" \ 67 | "Expected [$2] not to equal [$3]" 68 | ;; 69 | ( xgt ) 70 | print_result "[ $2 -gt $3 ]" \ 71 | "Expected [$2] to be > [$3]" 72 | ;; 73 | ( xlt ) 74 | print_result "[ $2 -lt $3 ]" \ 75 | "Expected [$2] to be < [$3]" 76 | ;; 77 | ( xglob ) 78 | print_result "case '$2' in $3) :;; *) false;; esac" \ 79 | "Expected [$2] to match [$3]" 80 | ;; 81 | ( xno_glob ) 82 | print_result "case '$2' in $3) false ;; *) :;; esac" \ 83 | "Expected [$2] not to match [$3]" 84 | ;; 85 | ( xpresent ) 86 | print_result "[ -n '$2' ]" \ 87 | "Expected [$2] to be present" 88 | ;; 89 | ( xblank ) 90 | print_result "[ -z '$2' ]" \ 91 | "Expected [$2] to be blank" 92 | ;; 93 | 94 | ( xfile_present ) 95 | print_result "[ -e $2 ]" \ 96 | "Expected file [$2] to exist" 97 | ;; 98 | ( xfile_absent ) 99 | print_result "[ ! -e $2 ]" \ 100 | "Expected file [$2] not to exist" 101 | ;; 102 | ( xsymlink ) 103 | link="$(readlink "$2")" 104 | print_result "[ '$link' = '$3' ]" \ 105 | "Expected [$2] to link to [$3], but got [$link]" 106 | ;; 107 | ( xtest ) 108 | print_result "$2" \ 109 | "Expected $2 to be true" 110 | ;; 111 | ( xgrep ) 112 | print_result "echo \"$2\" | grep -q \"$3\"" "Expected [$2] to match [$3]" 113 | ;; 114 | ( xno_grep ) 115 | print_result "echo \"$2\" | grep -qv \"$3\"" "Expected [$2] to not match [$3]" 116 | ;; 117 | ( xegrep ) 118 | print_result "echo \"$2\" | egrep -q \"$3\"" "Expected [$2] to match [$3]" 119 | ;; 120 | ( xno_egrep ) 121 | print_result "echo \"$2\" | egrep -qv \"$3\"" "Expected [$2] to not match [$3]" 122 | ;; 123 | ( * ) 124 | if is_function "$1"; then 125 | _shpec_matcher="$1"; shift 126 | $_shpec_matcher "$@" 127 | return 0 128 | else 129 | print_result false "Error: Unknown matcher [$1]" 130 | fi 131 | ;; 132 | esac 133 | } 134 | 135 | print_result() { 136 | if eval "$1"; then 137 | : $((_shpec_assertion_printed += 1)) 138 | if [ ${_shpec_assertion_printed} -le 1 ]; then 139 | iecho "$_shpec_green$_shpec_assertion$_shpec_norm" 140 | else 141 | printf "%b" "$_shpec_clear_ln" 142 | iecho "$_shpec_green$_shpec_assertion$_shpec_norm(x$_shpec_assertion_printed)" 143 | fi 144 | else 145 | : $((_shpec_failures += 1)) 146 | _shpec_assertion_printed=0 147 | iecho "$_shpec_red$_shpec_assertion" 148 | iecho "($2)$_shpec_norm" 149 | fi 150 | } 151 | 152 | final_results() { 153 | [ $_shpec_failures -eq 0 ] && _shpec_color=$_shpec_green || _shpec_color=$_shpec_red 154 | echoe "${_shpec_color}${_shpec_examples} examples, ${_shpec_failures} failures${_shpec_norm}" 155 | times 156 | [ $_shpec_failures -eq 0 ] 157 | exit 158 | } 159 | 160 | shpec_version() { 161 | ( 162 | VERSION=0.3.1 163 | echo $VERSION 164 | ) 165 | } 166 | 167 | shpec() { 168 | ( 169 | case "$1" in 170 | ( -v | --version ) 171 | shpec_version 172 | exit 0 173 | ;; 174 | esac 175 | 176 | _shpec_examples=0 177 | _shpec_failures=0 178 | _shpec_indent=0 179 | _shpec_red="\033[0;31m" 180 | _shpec_green="\033[0;32m" 181 | _shpec_norm="\033[0m" 182 | _shpec_clear_ln="\033[1A\033[K" 183 | 184 | _shpec_root=${SHPEC_ROOT:-$( 185 | [ -d './shpec' ] && echo './shpec' || echo '.' 186 | )} 187 | SHPEC_ROOT=${_shpec_root} 188 | 189 | _shpec_matcher_files=$( 190 | find "$_shpec_root/matchers" -name '*.sh' 2>/dev/null 191 | ) 192 | 193 | for _shpec_matcher_file in $_shpec_matcher_files; do 194 | # shellcheck source=/dev/null 195 | . "$_shpec_matcher_file" 196 | done 197 | 198 | if [ $# -gt 0 ] ; then 199 | for _shpec_file in "$@"; do 200 | # shellcheck source=/dev/null 201 | . "$_shpec_file" 202 | done 203 | else 204 | _shpec_files=$(find "$_shpec_root" -name '*_shpec.*') 205 | for _shpec_file in $_shpec_files; do 206 | # shellcheck source=/dev/null 207 | . "$_shpec_file" 208 | done 209 | fi 210 | 211 | final_results 212 | ) 213 | } 214 | 215 | ( 216 | _progname=shpec 217 | _pathname=$( command -v "$0" ) 218 | _cmdname=${_pathname##*/} 219 | _main=shpec 220 | 221 | case $_progname in (${_cmdname%.sh}) $_main "$@";; esac 222 | ) 223 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | VERSION=0.3.1 3 | 4 | TMPDIR=${TMPDIR:-/tmp} 5 | 6 | SHPECDIR=${TMPDIR}/shpec-${VERSION} 7 | 8 | cd $TMPDIR 9 | curl -sL https://github.com/rylnd/shpec/archive/${VERSION}.tar.gz | gunzip | tar xf - 10 | cd $SHPECDIR 11 | make install 12 | cd $TMPDIR 13 | rm -rf $SHPECDIR 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shpec", 3 | "version": "0.3.1", 4 | "description": "Test your shell scripts", 5 | "global": "true", 6 | "install": "make install" 7 | } 8 | -------------------------------------------------------------------------------- /shpec.plugin.zsh: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # # 3 | # _____ __ # 4 | # / ___// /_ ____ ___ _____ # 5 | # \__ \/ __ \/ __ \/ _ \/ ___/ # 6 | # ___/ / / / / /_/ / __/ /__ # 7 | # /____/_/ /_/ .___/\___/\___/ # 8 | # /_/ # 9 | # # 10 | ########################################################## 11 | 12 | 13 | ## just create an alias shpec pointing to where the spec 14 | ## executable really is (in this folder) 15 | alias shpec="zsh -c 'disable -r end; . $(dirname $0:A)/bin/shpec'" 16 | -------------------------------------------------------------------------------- /shpec/etc/example with spaces: -------------------------------------------------------------------------------- 1 | describe "a test file with spaces" 2 | it "works" 3 | assert equal "foo" "foo" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /shpec/etc/failing_example: -------------------------------------------------------------------------------- 1 | describe "a failing test" 2 | assert equal "foo" "bar" 3 | end 4 | -------------------------------------------------------------------------------- /shpec/etc/multi_assert_example: -------------------------------------------------------------------------------- 1 | it "a assert" 2 | assert equal 1 1 3 | end 4 | 5 | it "multi assert" 6 | assert equal 1 1 7 | assert equal 2 2 8 | end 9 | 10 | it "another assert" 11 | assert equal 1 1 12 | end -------------------------------------------------------------------------------- /shpec/etc/multi_assert_fail_example: -------------------------------------------------------------------------------- 1 | it "assert with errors" 2 | assert equal 1 1 3 | assert equal 1 2 4 | assert equal 3 3 5 | end 6 | -------------------------------------------------------------------------------- /shpec/etc/old_example: -------------------------------------------------------------------------------- 1 | describe "an old example" 2 | it "works" 3 | assert equal "foo" "foo" 4 | end_describe 5 | -------------------------------------------------------------------------------- /shpec/etc/passing_example: -------------------------------------------------------------------------------- 1 | describe "a passing test" 2 | it "works" 3 | assert equal "foo" "foo" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /shpec/etc/syntax_error: -------------------------------------------------------------------------------- 1 | describe "a malformed file" 2 | end 3 | end 4 | -------------------------------------------------------------------------------- /shpec/matchers/custom.sh: -------------------------------------------------------------------------------- 1 | custom_assertion() { 2 | assert equal "$1" "argument" 3 | } 4 | -------------------------------------------------------------------------------- /shpec/shell_compatibility_shpec.sh: -------------------------------------------------------------------------------- 1 | describe "shell compatibility" 2 | case "$SHELL" in 3 | *zsh) 4 | describe "shpec plugin" 5 | 6 | . ./shpec.plugin.zsh 7 | 8 | it "defines a shpec alias" 9 | shpec_type=$(type shpec | cut -d ' ' -f 4) 10 | assert glob ${shpec_type} "alias" 11 | end 12 | 13 | it "alias that works normally" 14 | tmp_spec="/tmp/shpec_shpec_plugin_test" # problems with $(mktemp -q) 15 | cat > $tmp_spec <<-SHPEC 16 | describe "Dummy test spec" 17 | it "Works"; end 18 | end 19 | SHPEC 20 | shpec "$tmp_spec" > /dev/null 21 | assert equal $? 0 22 | end 23 | end 24 | unalias shpec 25 | ;; 26 | 27 | *) 28 | it "nothing to test for $SHELL" 29 | end 30 | ;; 31 | esac 32 | end 33 | -------------------------------------------------------------------------------- /shpec/shpec_shpec.sh: -------------------------------------------------------------------------------- 1 | describe "shpec" 2 | describe "basic operations" 3 | it "asserts equality" 4 | assert equal "foo" "foo" 5 | end 6 | 7 | it "asserts inequality" 8 | assert unequal "foo" "bar" 9 | end 10 | 11 | it "asserts less than" 12 | assert lt 5 7 13 | end 14 | 15 | it "asserts greater than" 16 | assert gt 7 5 17 | end 18 | 19 | it "asserts presence" 20 | assert present "something" 21 | end 22 | 23 | it "asserts blankness" 24 | assert blank "" 25 | end 26 | end 27 | 28 | describe "equality matcher" 29 | it "handles newlines properly" 30 | string_with_newline_char="new\nline" 31 | multiline_string='new 32 | line' 33 | assert equal "$multiline_string" "$string_with_newline_char" 34 | end 35 | 36 | it "compares strings containing single quotes" 37 | assert equal "a' b" "a' b" 38 | end 39 | 40 | it "compares strings containing double quotes" 41 | assert equal 'a" b' 'a" b' 42 | end 43 | end 44 | 45 | describe "lt matcher" 46 | it "handles numbers of different length properly" 47 | assert lt 5 17 48 | end 49 | end 50 | 51 | describe "gt matcher" 52 | it "handles numbers of different length properly" 53 | assert gt 17 5 54 | end 55 | end 56 | 57 | describe "glob matcher" 58 | it "is essentially 'assert equal' if no special characters are used" 59 | assert glob "word" "word" 60 | end 61 | 62 | it "supports multi-character wildcards" 63 | assert glob "impartially" "*partial*" 64 | end 65 | 66 | it "supports single-character wildcards" 67 | assert glob "word" "wo?d" 68 | assert no_glob "world" "wo?d" 69 | end 70 | 71 | it "supports character lists" 72 | assert glob "foo" "[a-f]oo" 73 | assert no_glob "foo" "[g-z]oo" 74 | assert glob "boo" "[a-z]oo" 75 | assert glob "bar" "[a-z]*" 76 | end 77 | 78 | it "asserts globs which are not compatible with grep" 79 | assert glob "loooooooooooooooooong" "l*ng" 80 | end 81 | 82 | it "asserts lack of globs" 83 | assert no_glob "zebra" "giraffe" 84 | end 85 | end 86 | 87 | describe "grep matcher" 88 | it "supports matching regular expressions" 89 | assert grep "hello" "^[a-z]*$" 90 | end 91 | 92 | it "supports lack of matching regular expressions" 93 | assert no_grep "hello1" "^[a-z]*$" 94 | end 95 | 96 | it "does not match multiple lines in a single expression" 97 | output="$(. $SHPEC_ROOT/etc/multi_assert_example)" 98 | 99 | assert grep "$output" "a assert" 100 | assert grep "$output" "multi assert" 101 | assert no_grep "$output" "a assert.*multi assert" 102 | end 103 | end 104 | 105 | describe "egrep matcher" 106 | it "supports matching extended regular expressions" 107 | assert egrep "hello" "^[a-z]+$" 108 | end 109 | 110 | it "supports lack of matching extended regular expressions" 111 | assert no_egrep "hello1" "^[a-z]+$" 112 | end 113 | end 114 | 115 | describe "passing through to the test builtin" 116 | it "asserts an arbitrary algebraic test" 117 | assert test "[ 5 -lt 10 ]" 118 | end 119 | end 120 | 121 | describe "stubbing commands" 122 | it "stubs to the null command by default" 123 | stub_command "false" 124 | false # doesn't do anything 125 | assert equal "$?" 0 126 | unstub_command "false" 127 | end 128 | 129 | it "preserves the original working of the stub" 130 | false 131 | assert equal "$?" 1 132 | end 133 | 134 | it "accepts an optional function body" 135 | stub_command "curl" "echo 'stubbed body'" 136 | assert equal "$(curl)" "stubbed body" 137 | unstub_command "curl" 138 | end 139 | end 140 | 141 | describe "testing files" 142 | it "asserts file absence" 143 | assert file_absent /tmp/foo 144 | end 145 | 146 | it "asserts file existence" 147 | touch /tmp/foo 148 | assert file_present /tmp/foo 149 | rm /tmp/foo 150 | end 151 | 152 | it "can verify the pointer of a symlink" 153 | ln -s $HOME /tmp/link 154 | assert symlink /tmp/link "$HOME" 155 | rm /tmp/link 156 | end 157 | end 158 | 159 | describe "custom matcher" 160 | it "allows custom matchers" 161 | assert custom_assertion "argument" 162 | end 163 | end 164 | 165 | describe "exit codes" 166 | it "returns nonzero if any test fails" 167 | shpec $SHPEC_ROOT/etc/failing_example > /dev/null 2>& 1 168 | assert unequal "$?" "0" 169 | end 170 | 171 | it "returns zero if a suite passes" 172 | shpec $SHPEC_ROOT/etc/passing_example > /dev/null 2>& 1 173 | assert equal "$?" "0" 174 | end 175 | end 176 | 177 | describe "output" 178 | it "outputs passing tests to STDOUT" 179 | message="$(. $SHPEC_ROOT/etc/passing_example)" 180 | assert grep "$message" "a passing test" 181 | end 182 | 183 | it "outputs failing tests to STDOUT" 184 | message="$(. $SHPEC_ROOT/etc/failing_example)" 185 | assert grep "$message" "a failing test" 186 | end 187 | 188 | it "joins multiple identical assert names" 189 | output="$(. $SHPEC_ROOT/etc/multi_assert_example)" 190 | 191 | assert glob "$output" "*a\ assert*multi\ assert*x[0-9]*another\ assert*" 192 | end 193 | 194 | it "doesn't join FAILED identical assert names" 195 | output="$(. $SHPEC_ROOT/etc/multi_assert_fail_example)" 196 | 197 | assert glob "$output" "*assert\ with\ errors*assert\ with\ errors*" 198 | assert grep "$output" "Expected \[1\] to equal \[2\]" 199 | assert no_grep "$output" "x[0-9]*" 200 | end 201 | end 202 | 203 | describe "malformed test files" 204 | _f=$SHPEC_ROOT/etc/syntax_error 205 | 206 | it "exits with an error" 207 | shpec $_f > /dev/null 2>& 1 208 | assert unequal "$?" "0" 209 | end 210 | 211 | it "informs you of the malformed shpec test file" 212 | shpec $_f > /tmp/syntax_error_output 2>& 1 213 | message="$(cat /tmp/syntax_error_output)" 214 | assert grep "$message" "$_f" 215 | rm /tmp/syntax_error_output 216 | end 217 | end 218 | 219 | describe "commandline arguments" 220 | describe "multiple arguments" 221 | it "runs each file passed to the function" 222 | shpec $SHPEC_ROOT/etc/failing_example $SHPEC_ROOT/etc/passing_example > /dev/null 2>& 1 223 | assert unequal "$?" "0" 224 | 225 | shpec $SHPEC_ROOT/etc/passing_example $SHPEC_ROOT/etc/failing_example > /dev/null 2>& 1 226 | assert unequal "$?" "0" 227 | end 228 | end 229 | 230 | describe "arguments with whitespace" 231 | it "runs arguments with spaces" 232 | shpec "$SHPEC_ROOT/etc/example with spaces" > /tmp/spaces_output 2>& 1 233 | assert equal "$?" "0" 234 | 235 | output="$(cat /tmp/spaces_output)" 236 | 237 | assert glob "$output" "*a\ test\ file\ with\ spaces*works*" 238 | rm /tmp/spaces_output 239 | end 240 | 241 | it "runs multiple arguments with spaces" 242 | shpec "$SHPEC_ROOT/etc/example with spaces" "$SHPEC_ROOT/etc/example with spaces" > /tmp/multi_spaces_output 2>& 1 243 | assert equal "$?" "0" 244 | 245 | output="$(cat /tmp/multi_spaces_output)" 246 | 247 | assert glob "$output" "*spaces*works*spaces*works*" 248 | rm /tmp/multi_spaces_output 249 | end 250 | end 251 | end 252 | 253 | describe "commandline options" 254 | describe "--version" 255 | it "outputs the current version number" 256 | message="$(shpec --version)" 257 | assert grep "$message" "$(cat $SHPEC_ROOT/../VERSION)" 258 | end 259 | end 260 | 261 | describe "-v" 262 | it "outputs the current version number" 263 | message="$(shpec -v)" 264 | assert grep "$message" "$(cat $SHPEC_ROOT/../VERSION)" 265 | end 266 | end 267 | end 268 | 269 | describe "compatibility" 270 | it "works with old-style syntax" 271 | message="$(. $SHPEC_ROOT/etc/old_example)" 272 | assert grep "$message" "old example" 273 | end 274 | end 275 | end 276 | --------------------------------------------------------------------------------