#! /usr/bin/env python | |
import sys,os,subprocess,signal | |
class Bash2PyException(Exception): def __init__(self, value=None): self.value = value def __str__(self): return repr(self.value) | |
class Bash2Py(object): __slots__ = ["val"] def __init__(self, value=''): self.val = value def setValue(self, value=None): self.val = value return value def postinc(self,inc=1): tmp = self.val self.val += inc return tmp | |
def GetVariable(name, local=locals()): if name in local: return local[name] if name in globals(): return globals()[name] return None def Make(name, local=locals()): ret = GetVariable(name, local) if ret is None: ret = Bash2Py(0) globals()[name] = ret return ret def GetValue(name, local=locals()): variable = GetVariable(name,local) if variable is None or variable.val is None: return '' return variable.val def Array(value): if isinstance(value, list): return value if isinstance(value, basestring): return value.strip().split(' ') return [ value ] | |
class Expand(object): @staticmethod def at(): if (len(sys.argv) < 2): return [] return sys.argv[1:] @staticmethod def star(in_quotes): if (in_quotes): if (len(sys.argv) < 2): return "" return " ".join(sys.argv[1:]) return Expand.at() @staticmethod def exclamation(): raise Bash2PyException("$! unsupported") @staticmethod def underbar(): raise Bash2PyException("$_ unsupported") @staticmethod def colonMinus(name, value=''): ret = GetValue(name) if (ret is None or ret == ''): ret = value return ret @staticmethod def colonPlus(name, value=''): ret = GetValue(name) if (ret is None or ret == ''): return '' return value | |
#!/bin/bash | |
# assert.sh 1.0 - bash unit testing framework | |
# Copyright (C) 2009, 2010, 2011, 2012 Robert Lehmann | |
# | |
# http://github.com/lehmannro/assert.sh | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU Lesser 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 Lesser General Public License for more details. | |
# | |
# You should have received a copy of the GNU Lesser General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
export DISCOVERONLY=${DISCOVERONLY:-} | |
# assert.sh 1.0 - bash unit testing framework | |
# Copyright (C) 2009, 2010, 2011, 2012 Robert Lehmann | |
# | |
# http://github.com/lehmannro/assert.sh | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU Lesser 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 Lesser General Public License for more details. | |
# | |
# You should have received a copy of the GNU Lesser General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
os.environ['DISCOVERONLY'] = Expand.colonMinus("DISCOVERONLY") | |
export DEBUG=${DEBUG:-} | |
os.environ['DEBUG'] = Expand.colonMinus("DEBUG") | |
export STOP=${STOP:-} | |
os.environ['STOP'] = Expand.colonMinus("STOP") | |
export INVARIANT=${INVARIANT:-} | |
os.environ['INVARIANT'] = Expand.colonMinus("INVARIANT") | |
export CONTINUE=${CONTINUE:-} | |
os.environ['CONTINUE'] = Expand.colonMinus("CONTINUE") | |
args="$(getopt -n "$0" -l \ verbose,help,stop,discover,invariant,continue vhxdic $*)" \ || exit -1 | |
if not Make("args").setValue(os.popen("getopt -n \""+__file__+"\" -l verbose,help,stop,discover,invariant,continue vhxdic "+Expand.star(1)).read().rstrip("\n")): exit(-1) | |
for arg in $args; do case "$arg" in -h) echo "$0 [-vxidc]" \ "[--verbose] [--stop] [--invariant] [--discover] [--continue]" echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]" exit 0;; --help) cat <<EOF Usage: $0 [options] Language-agnostic unit tests for subprocesses. Options: -v, --verbose generate output for every individual test case -x, --stop stop running tests after the first failure -i, --invariant do not measure timings to remain invariant between runs -d, --discover collect test suites only, do not run any tests -c, --continue do not modify exit code to test suite status -h show brief usage information and exit --help show this help message and exit EOF exit 0;; -v|--verbose) DEBUG=1;; -x|--stop) STOP=1;; -i|--invariant) INVARIANT=1;; -d|--discover) DISCOVERONLY=1;; -c|--continue) CONTINUE=1;; esac done | |
for Make("arg").val in Array(args.val): if ( str(arg.val) == '-h'): print(__file__+" [-vxidc]","[--verbose] [--stop] [--invariant] [--discover] [--continue]") print(os.popen("sed 's/./ /g' <<< \""+__file__+"\"").read().rstrip("\n")+" [-h] [--help]") exit(0) elif ( str(arg.val) == '--help'): subprocess.Popen("cat",shell=True,stdin=subprocess.PIPE) _rc0.communicate("Usage: "+__file__+" [options]\nLanguage-agnostic unit tests for subprocesses.\n\nOptions:\n -v, --verbose generate output for every individual test case\n -x, --stop stop running tests after the first failure\n -i, --invariant do not measure timings to remain invariant between runs\n -d, --discover collect test suites only, do not run any tests\n -c, --continue do not modify exit code to test suite status\n -h show brief usage information and exit\n --help show this help message and exit\n") _rc0 = _rc0.wait() exit(0) elif ( str(arg.val) == '-v' or str(arg.val) == '--verbose'): Make("DEBUG").setValue(1) elif ( str(arg.val) == '-x' or str(arg.val) == '--stop'): Make("STOP").setValue(1) elif ( str(arg.val) == '-i' or str(arg.val) == '--invariant'): Make("INVARIANT").setValue(1) elif ( str(arg.val) == '-d' or str(arg.val) == '--discover'): Make("DISCOVERONLY").setValue(1) elif ( str(arg.val) == '-c' or str(arg.val) == '--continue'): Make("CONTINUE").setValue(1) | |
printf -v _indent "\n\t" # local format helper | |
_indent = "\n\t" | |
_assert_reset() { tests_ran=0 tests_failed=0 tests_errors=() tests_starttime="$(date +%s.%N)" # seconds_since_epoch.nanoseconds } | |
# local format helper | |
def _assert_reset () : global tests_ran global tests_failed global tests_errors global tests_starttime Make("tests_ran").setValue(0) tests_failed=Bash2Py(0) tests_errors=Bash2Py("()") tests_starttime=Bash2Py(os.popen("date +%s.%N").read().rstrip("\n")) | |
assert_end() { # assert_end [suite ..] tests_endtime="$(date +%s.%N)" tests="$tests_ran ${*:+$* }tests" [[ -n "$DISCOVERONLY" ]] && echo "collected $tests." && _assert_reset && return [[ -n "$DEBUG" ]] && echo [[ -z "$INVARIANT" ]] && report_time=" in $(bc \ <<< "${tests_endtime%.N} - ${tests_starttime%.N}" \ | sed -e 's/\.\([0-9]\{0,3\}\)[0-9]*/.\1/' -e 's/^\./0./')s" \ || report_time= if [[ "$tests_failed" -eq 0 ]]; then echo "all $tests passed$report_time." else for error in "${tests_errors[@]}"; do echo "$error"; done echo "$tests_failed of $tests failed$report_time." fi tests_failed_previous=$tests_failed [[ $tests_failed -gt 0 ]] && tests_suite_status=1 _assert_reset return $tests_failed_previous } | |
# seconds_since_epoch.nanoseconds | |
def assert_end () : global tests_endtime global tests global tests_ran global DISCOVERONLY global DEBUG global INVARIANT global report_time global tests_starttime global tests_failed global tests_errors global error global tests_failed_previous global tests_suite_status # assert_end [suite ..] | |
Make("tests_endtime").setValue(os.popen("date +%s.%N").read().rstrip("\n")) tests=Bash2Py(str(tests_ran.val)+" "+Expand.star(1):+Expand.star(1) +"tests") if if if str(DISCOVERONLY.val) != '': print("collected "+str(tests.val)+"."): _assert_reset(): return if str(DEBUG.val) != '': print() if not if str(INVARIANT.val) == '': Make("report_time").setValue(" in "+os.popen("bc <<< \""+str(tests_endtime.val%.N)+" - "+str(tests_starttime.val%.N)+"\" | sed -e 's/.([0-9]{0,3})[0-9]*/.1/' -e 's/^./0./'").read().rstrip("\n")+"s"): Make("report_time").setValue() if (int(tests_failed.val) == 0 ): print("all "+str(tests.val)+" passed"+str(report_time.val)+".") else: for Make("error").val in Array(tests_errors.val[@] ]): print(error.val) print(str(tests_failed.val)+" of "+str(tests.val)+" failed"+str(report_time.val)+".") tests_failed_previous=Bash2Py(tests_failed.val) if int(tests_failed.val) > 0: Make("tests_suite_status").setValue(1) _assert_reset() return(int(tests_failed_previous.val)) | |
assert() { # assert <command> <expected stdout> [stdin] (( tests_ran++ )) || : [[ -n "$DISCOVERONLY" ]] && return || true # printf required for formatting printf -v expected "x${2:-}" # x required to overwrite older results result="$(eval 2>/dev/null $1 <<< ${3:-})" || true # Note: $expected is already decorated if [[ "x$result" == "$expected" ]]; then [[ -n "$DEBUG" ]] && echo -n . || true return fi result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<< "$result")" [[ -z "$result" ]] && result="nothing" || result="\"$result\"" [[ -z "$2" ]] && expected="nothing" || expected="\"$2\"" _assert_fail "expected $expected${_indent}got $result" "$1" "$3" } | |
def assert (_p1,_p2,_p3) : global DISCOVERONLY global result global expected global DEBUG # assert <command> <expected stdout> [stdin] | |
if not Make("tests_ran").postinc(): pass if not if str(DISCOVERONLY.val) != '': return: True # printf required for formatting | |
expected = "x"+Expand.colonMinus("2") # x required to overwrite older results | |
if not Make("result").setValue(os.popen("eval 2>/dev/null "+str(_p1)+" <<< "+Expand.colonMinus("3")).read().rstrip("\n")): True # Note: $expected is already decorated | |
if ("x"+str(result.val) == str(expected.val) ): if not if str(DEBUG.val) != '': print(".",end=""): True return result=Bash2Py(os.popen("sed -e :a -e '"+str(Expand.exclamation())+"N;s/n/\\n/;ta' <<< \""+str(result.val)+"\"").read().rstrip("\n")) if not if str(result.val) == '': Make("result").setValue("nothing"): Make("result").setValue("\""+str(result.val)+"\"") if not if str(_p2) == '': Make("expected").setValue("nothing"): Make("expected").setValue("\""+str(_p2)+"\"") _rc0 = subprocess.call(["_assert_fail","expected "+str(expected.val)+Expand.underbar()indent+"got "+str(result.val),str(_p1),str(_p3)],shell=True) | |
assert_raises() { # assert_raises <command> <expected code> [stdin] (( tests_ran++ )) || : [[ -n "$DISCOVERONLY" ]] && return || true status=0 (eval $1 <<< ${3:-}) > /dev/null 2>&1 || status=$? expected=${2:-0} if [[ "$status" -eq "$expected" ]]; then [[ -n "$DEBUG" ]] && echo -n . || true return fi _assert_fail "program terminated with code $status instead of $expected" "$1" "$3" } | |
def assert_raises (_p1,_p2,_p3) : global DISCOVERONLY global status global expected global DEBUG # assert_raises <command> <expected code> [stdin] | |
if not Make("tests_ran").postinc(): pass if not if str(DISCOVERONLY.val) != '': return: True status=Bash2Py(0) if not > /dev/null2>&1(eval(str(_p1))) > /dev/null 2>&1: Make("status").setValue(_rc0) expected=Bash2Py(Expand.colonMinus("2","0")) if (int(status.val) == int(expected.val) ): if not if str(DEBUG.val) != '': print(".",end=""): True return _rc0 = subprocess.call(["_assert_fail","program terminated with code "+str(status.val)+" instead of "+str(expected.val),str(_p1),str(_p3)],shell=True) | |
_assert_fail() { # _assert_fail <failure> <command> <stdin> [[ -n "$DEBUG" ]] && echo -n X report="test #$tests_ran \"$2${3:+ <<< $3}\" failed:${_indent}$1" if [[ -n "$STOP" ]]; then [[ -n "$DEBUG" ]] && echo echo "$report" exit 1 fi tests_errors[$tests_failed]="$report" (( tests_failed++ )) || : } | |
def _assert_fail (_p1,_p2,_p3) : global DEBUG global report global tests_ran global STOP global tests_errors global tests_failed # _assert_fail <failure> <command> <stdin> | |
if str(DEBUG.val) != '': print("X",end="") report=Bash2Py("test #"+str(tests_ran.val)+" \""+str(_p2)+Expand.colonPlus("3"," <<< "+str(_p3))+"\" failed:"+Expand.underbar()indent+str(_p1)) if (str(STOP.val) != '' ): if str(DEBUG.val) != '': print() print(report.val) exit(1) tests_errors.val[tests_failed]=Bash2Py(report.val) if not Make("tests_failed").postinc(): pass | |
_assert_reset | |
_assert_reset() | |
: ${tests_suite_status:=0} # remember if any of the tests failed so far | |
pass | |
_assert_cleanup() { local status=$? # modify exit code if it's not already non-zero [[ $status -eq 0 && -z $CONTINUE ]] && exit $tests_suite_status } | |
# remember if any of the tests failed so far | |
def _assert_cleanup () : global CONTINUE global tests_suite_status Make("status").setValue(_rc0) # modify exit code if it's not already non-zero | |
if int(status.val) == 0 and str(CONTINUE.val) == '': exit(int(tests_suite_status.val)) | |
trap _assert_cleanup EXIT | |
signal.signal(signal.SIGEXIT,_assert_cleanup) | |
ÿ |