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

# 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")
os.environ['DEBUG'] = Expand.colonMinus("DEBUG")
os.environ['STOP'] = Expand.colonMinus("STOP")
os.environ['INVARIANT'] = Expand.colonMinus("INVARIANT")
os.environ['CONTINUE'] = Expand.colonMinus("CONTINUE")
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 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)
_indent = "\n\t"
# 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"))

# 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))

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)

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)

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()
pass
# 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))

signal.signal(signal.SIGEXIT,_assert_cleanup)

