#! /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)
ÿ