From 759767708655e39e53f28b3062d0d8517f950e04 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 10 Jun 2014 01:35:27 -0400 Subject: [PATCH 1/5] Added env var value expectation --- src/expect.sh | 1 + src/expect/env.sh | 66 ++++++++++++++ src/spec.sh | 11 ++- test/test-expect-core | 2 +- test/test-expect-env | 207 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 src/expect/env.sh create mode 100644 test/test-expect-env 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..771d0c6 --- /dev/null +++ b/src/expect/env.sh @@ -0,0 +1,66 @@ +#!/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 +# +# The variable need not be exported. +# +_expect--set() +{ + local -ri shiftn="$2" + local -r envpath="$4" + shift "$shiftn" + + # ensure envpath is available + __chk-shiftn 4 "$shiftn" + + local -r var="$1" cmp="$2" + shift 2 + local -r expect="$@" + + # TODO: support escaped newlines; use awk (do not source the file) + local -r line="$( grep "^declare \.*-- $var=" "$envpath" )" + local -r valq="${line##*=\"}" + local -r val="${valq%%\"}" + + # 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";; + + *) false;; + esac +} + 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..e5fb3be --- /dev/null +++ b/test/test-expect-env @@ -0,0 +1,207 @@ +#!/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 -- var="val" +declare -- long="foo bar baz" +declare -- empty="" +declare -- one="1"' + + +function setchk() +{ + _expect--set 0 4 <(:) <( echo "$stubenv" ) "$@" +} + + +describe set + 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 +end + From 331a1161b294fa44b72331eec0bd0fcfe7b87a9f Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 10 Jun 2014 22:12:09 -0400 Subject: [PATCH 2/5] Added `declare` as an alias for `set` expectation --- src/expect/env.sh | 9 ++ test/test-expect-env | 338 ++++++++++++++++++++++--------------------- 2 files changed, 181 insertions(+), 166 deletions(-) diff --git a/src/expect/env.sh b/src/expect/env.sh index 771d0c6..cedaf9a 100644 --- a/src/expect/env.sh +++ b/src/expect/env.sh @@ -64,3 +64,12 @@ _expect--set() esac } + +## +# Alias for `set` +# +_expect--declare() +{ + _expect--set "$@" +} + diff --git a/test/test-expect-env b/test/test-expect-env index e5fb3be..dcb79b0 100644 --- a/test/test-expect-env +++ b/test/test-expect-env @@ -26,182 +26,188 @@ declare -- empty="" declare -- one="1"' +declare curchk function setchk() { - _expect--set 0 4 <(:) <( echo "$stubenv" ) "$@" + _expect--$curchk 0 4 <(:) <( echo "$stubenv" ) "$@" } -describe set - describe = and == operators - it succeeds on string equality - expect setchk var = val - to succeed +for name in set declare; do + curchk=$name - expect setchk var == val - to succeed + describe set + 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 - it fails on string inequality - expect setchk var = unval - to fail - expect setchk var == unval + 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 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 -end +done From 302977eb74bc820c37e5487ebcb3011bb165b4f2 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 10 Jun 2014 23:20:38 -0400 Subject: [PATCH 3/5] Refactored environment expectations to support grabbing flags --- src/expect/env.sh | 61 ++++++++++++++++++++++++++++++++------------ test/test-expect-env | 1 + 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/expect/env.sh b/src/expect/env.sh index cedaf9a..e5879f8 100644 --- a/src/expect/env.sh +++ b/src/expect/env.sh @@ -24,27 +24,18 @@ __INC_EXPECT_ENV=1 ## -# Expect that an environment variable is set to a particular value +# Expect that an environment variable is set to a particular value, or +# assert on flags # -# The variable need not be exported. -# -_expect--set() +__expect-env() { - local -ri shiftn="$2" - local -r envpath="$4" - shift "$shiftn" - - # ensure envpath is available - __chk-shiftn 4 "$shiftn" - - local -r var="$1" cmp="$2" - shift 2 + local -r flag="$1" var="$2" cmp="$3" + shift 3 local -r expect="$@" - # TODO: support escaped newlines; use awk (do not source the file) - local -r line="$( grep "^declare \.*-- $var=" "$envpath" )" - local -r valq="${line##*=\"}" - local -r val="${valq%%\"}" + # TODO: support escaped newlines + local flags val + __read-env-line "$var" flags val < "$envpath" # cannot quote regex without causing problems, and [[ syntax does not # allow a variable comparison operator; further, argument order varies @@ -65,6 +56,42 @@ _expect--set() } +## +# 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` # diff --git a/test/test-expect-env b/test/test-expect-env index dcb79b0..9e2aa8b 100644 --- a/test/test-expect-env +++ b/test/test-expect-env @@ -33,6 +33,7 @@ function setchk() } +# we have a few different expectations that have the same syntax for name in set declare; do curchk=$name From 9045a3298b589bd0fb28cad5bff0bfaa07c9b70b Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 10 Jun 2014 23:33:05 -0400 Subject: [PATCH 4/5] Added `exports` expectation --- src/expect/env.sh | 26 +++++++++++++++++++++++++- test/test-expect-env | 20 ++++++++++++++------ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/expect/env.sh b/src/expect/env.sh index e5879f8..252c7f8 100644 --- a/src/expect/env.sh +++ b/src/expect/env.sh @@ -29,7 +29,7 @@ __INC_EXPECT_ENV=1 # __expect-env() { - local -r flag="$1" var="$2" cmp="$3" + local -r expflags="$1" var="$2" cmp="$3" shift 3 local -r expect="$@" @@ -37,6 +37,11 @@ __expect-env() 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 @@ -100,3 +105,22 @@ _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/test/test-expect-env b/test/test-expect-env index 9e2aa8b..935f23b 100644 --- a/test/test-expect-env +++ b/test/test-expect-env @@ -20,10 +20,11 @@ ## declare -r stubenv=' -declare -- var="val" -declare -- long="foo bar baz" -declare -- empty="" -declare -- one="1"' +declare -x -- var="val" +declare -x -- long="foo bar baz" +declare -x -- empty="" +declare -x -- one="1" +declare -- nonexport=""' declare curchk @@ -34,10 +35,10 @@ function setchk() # we have a few different expectations that have the same syntax -for name in set declare; do +for name in set declare export; do curchk=$name - describe set + describe "$name" describe = and == operators it succeeds on string equality expect setchk var = val @@ -209,6 +210,13 @@ for name in set declare; do 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 -z + to fail + end + fi end done From a116a4ac7284e8675d17fe6d433285eb64c48ef0 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 10 Jun 2014 23:39:20 -0400 Subject: [PATCH 5/5] Environment expectations now permit omitting operator Export expectations will only succeed/fail based on flag checks, whereas the declare/set expectations will fail on value. --- src/expect/env.sh | 7 +++++++ test/test-expect-env | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/expect/env.sh b/src/expect/env.sh index 252c7f8..8523efa 100644 --- a/src/expect/env.sh +++ b/src/expect/env.sh @@ -56,6 +56,13 @@ __expect-env() =|==|!=|-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 } diff --git a/test/test-expect-env b/test/test-expect-env index 935f23b..e0f7d7c 100644 --- a/test/test-expect-env +++ b/test/test-expect-env @@ -24,7 +24,8 @@ declare -x -- var="val" declare -x -- long="foo bar baz" declare -x -- empty="" declare -x -- one="1" -declare -- nonexport=""' +declare -- nonexport="moo" +declare -- nonexport_empty=""' declare curchk @@ -213,9 +214,43 @@ for name in set declare export; do if [ "$name" == export ]; then it fails when variable is not exported - expect setchk nonexport -z + 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