diff --git a/src/expect.sh b/src/expect.sh index 2d44875..56db94e 100644 --- a/src/expect.sh +++ b/src/expect.sh @@ -23,6 +23,7 @@ __INC_EXPECT_CORE=1 source expect/output.sh +source expect/env.sh ## diff --git a/src/expect/env.sh b/src/expect/env.sh new file mode 100644 index 0000000..8523efa --- /dev/null +++ b/src/expect/env.sh @@ -0,0 +1,133 @@ +#!/bin/bash +# Environment expectations +# +# Copyright (C) 2014 Mike Gewitz +# +# This file is part of shspec. +# +# shspec is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +## + +[ -z $__INC_EXPECT_ENV ] || return +__INC_EXPECT_ENV=1 + + +## +# Expect that an environment variable is set to a particular value, or +# assert on flags +# +__expect-env() +{ + local -r expflags="$1" var="$2" cmp="$3" + shift 3 + local -r expect="$@" + + # TODO: support escaped newlines + local flags val + __read-env-line "$var" flags val < "$envpath" + + # perform flag assertion if requested + test -n "$expflags" && { + [[ "$flags" =~ [$expflags] ]] || return 1 + } + + # cannot quote regex without causing problems, and [[ syntax does not + # allow a variable comparison operator; further, argument order varies + # with certain operators; whitelist to explicitly document support and + # prevent oddities + case "$cmp" in + =~) + [[ "$val" =~ $expect ]];; + + -[nz]) + test "$cmp" "$val";; + + =|==|!=|-eq|-ne|-lt|-le|-gt|-ge) + test "$val" $cmp "$expect";; + + # at this point, if we have succeeded in performing flag tests, then we + # will always pass; otherwise, if no such tests were performed, then we + # fall back to the conventional non-empty check + '') + test -n "$expflags" -o -n "$val";; + + # TODO: provide error description + *) false;; + esac +} + + +## +# Parse environment line (from `declare`) into flag and value variables +# +# Expected output is of the form: +# declare -flags? -- var="val" +# +__read-env-line() +{ + local -r var="$1" destflag="$2" destval="$3" + + read $destflag $destval < <( + awk ' + match( $0, /^declare (-([a-z]+) )?-- '$var'="(.*)"$/, m ) { + print "-" m[2], m[3] + }' + ) +} + + +## +# Expect that an environment variable has been set to a certain value +# +_expect--set() +{ + local -ri shiftn="$2" + local -r envpath="$4" + shift "$shiftn" + + # ensure envpath is available + __chk-shiftn 4 "$shiftn" + + # no flag expectation + __expect-env '' "$@" +} + + +## +# Alias for `set` +# +_expect--declare() +{ + _expect--set "$@" +} + + +## +# Checks that a variable is exported with the given value +# +# Same syntax as `set` +# +_expect--export() +{ + local -ri shiftn="$2" + local -r envpath="$4" + shift "$shiftn" + + # ensure envpath is available + __chk-shiftn 4 "$shiftn" + + # expect the -x flag, which denotes export + __expect-env x "$@" +} + diff --git a/src/spec.sh b/src/spec.sh index 16cce58..3d898a7 100644 --- a/src/spec.sh +++ b/src/spec.sh @@ -33,7 +33,7 @@ source specstack.sh source expect.sh # number of internal arguments before remainder clause -declare -ir __SHIFTN=3 +declare -ir __SHIFTN=4 ## @@ -48,6 +48,9 @@ mktemp-shm() # stderr file declare -r __spec_errpath="$(mktemp-shm)" +# env dump file +declare -r __spec_envpath="$(mktemp-shm)" + # most recent expect result and its exit code declare __spec_result= declare -i __spec_rexit=0 @@ -162,7 +165,8 @@ to() _sstack-assert-follow :expect to $(caller) _sstack-pop - __handle-to "$__spec_rexit" $__SHIFTN "$__spec_errpath" "$@" \ + __handle-to "$__spec_rexit" $__SHIFTN \ + "$__spec_errpath" "$__spec_envpath" "$@" \ || fail "$*" __spec_caller= @@ -182,6 +186,7 @@ __handle-to() local -ri rexit="$1" local -ri shiftn="$2" local -r errpath="$( [ $shiftn -gt 2 ] && echo "$3" )" + local -r envpath="$( [ $shiftn -gt 3 ] && echo "$4" )" shift "$shiftn" local -r type="$1" @@ -196,7 +201,7 @@ __handle-to() # output file, and all remaining arguments are said remainder clause; the # shift argument allows the implementation to vary without breaking BC so # long as the meaning of the shifted arguments do not change - $assert $rexit $__SHIFTN "$errpath" "$@" \ + $assert $rexit $__SHIFTN "$errpath" "$envpath" "$@" \ < <( echo -n "$__spec_result" ) } diff --git a/test/test-expect-core b/test/test-expect-core index a57f786..1b222e8 100644 --- a/test/test-expect-core +++ b/test/test-expect-core @@ -188,7 +188,7 @@ describe silent # no arguments within context of the DSL, that is it accepts no arguments - expect _expect--silent 0 2 foo < <(:) + expect _expect--silent 0 2 foo < <(:) to fail end end diff --git a/test/test-expect-env b/test/test-expect-env new file mode 100644 index 0000000..e0f7d7c --- /dev/null +++ b/test/test-expect-env @@ -0,0 +1,257 @@ +#!/bin/bash +# Environment expectation tests +# +# Copyright (C) 2014 Mike Gewitz +# +# This file is part of shspec. +# +# shspec is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +## + +declare -r stubenv=' +declare -x -- var="val" +declare -x -- long="foo bar baz" +declare -x -- empty="" +declare -x -- one="1" +declare -- nonexport="moo" +declare -- nonexport_empty=""' + + +declare curchk +function setchk() +{ + _expect--$curchk 0 4 <(:) <( echo "$stubenv" ) "$@" +} + + +# we have a few different expectations that have the same syntax +for name in set declare export; do + curchk=$name + + describe "$name" + describe = and == operators + it succeeds on string equality + expect setchk var = val + to succeed + + expect setchk var == val + to succeed + end + + it fails on string inequality + expect setchk var = unval + to fail + + expect setchk var == unval + to fail + end + end + + + describe != operator + it succeeds on string inequality + expect setchk var != foo + to succeed + end + + it fails on string equality + expect setchk var != val + to fail + end + end + + + describe =~ operator + it succeeds on a match + expect setchk \ + long =~ fo+ ba. baz\$ + to succeed + end + + # note that this also ensures that *all* arguments are part of the + # match + it fails on a mismatch + expect setchk \ + long =~ fo+ baX baz\$ + to fail + end + end + + + describe -n operator + it succeeds when string is non-empty + expect setchk var -n + to succeed + end + + it fails when string is empty + expect setchk empty -n + to fail + end + end + + + describe -z operator + it succeeds when string is empty + expect setchk empty -z + to succeed + end + + it fails when string is non-empty + expect setchk var -z + to fail + end + end + + + describe -eq operator + it succeeds on numeric equality + expect setchk one -eq 1 + to succeed + end + + it fails on numeric inequality + expect setchk one -eq 2 + to fail + end + end + + + describe -gt operator + it succeeds when numerically greater + expect setchk one -gt 0 + to succeed + end + + it fails when not numerically greater + expect setchk one -gt 1 + to fail + end + end + + + describe -ge operator + it succeeds when numerically greater + expect setchk one -ge 0 + to succeed + end + + it succeeds when numerically equal + expect setchk one -ge 1 + to succeed + end + + it fails when numerically less than + expect setchk one -ge 2 + to fail + end + end + + + describe -lt operator + it succeeds when numerically less than + expect setchk one -lt 2 + to succeed + end + + it fails when not numerically less than + expect setchk one -lt 1 + to fail + end + end + + + describe -le operator + it succeeds when numerically less than + expect setchk one -le 2 + to succeed + end + + it succeeds when numerically equal + expect setchk one -le 1 + to succeed + end + + it fails when numerically greater than + expect setchk one -le 0 + to fail + end + end + + + describe -ne operator + it succeeds when numerically unequal + expect setchk one -ne 2 + to succeed + end + + it fails when numerically equal + expect setchk one -ne 1 + to fail + end + end + + + # primarily for safety and strict documentation, but no other tests make + # sense at the moment + it fails on unrecognized operators + # shell injection (not that this is realistically a problem, because + # we can execute arbitrary shell code anyway) + expect setchk var "!= foo -a 1 -eq" 1 + to fail + end + + if [ "$name" == export ]; then + it fails when variable is not exported + expect setchk nonexport + to fail + end + + describe in absence of any operator + it succeeds on exported, non-empty var + expect setchk var + to succeed + end + + it succeeds on exported empty var + expect setchk empty + to succeed + end + + it fails on non-exported, non-empty var + expect setchk nonexport + to fail + end + + it fails on non-exported empty var + expect setchk nonexport_empty + to fail + end + end + else + describe in absence of any operator + it succeeds on non-empty string + expect setchk var + to succeed + end + + it fails on empty string + expect setchk empty + to fail + end + end + fi + end +done +