###
### use like:
###
###      PYTHONPATH=build/lib.macosx-10.12-x86_64-3.6 python tests/run.py tests/tools/image/test_ia_imageconcat.py tests/tools/image/test_ia_makecomplex.py
###
import os
import sys
import time
import inspect
import unittest
import subprocess
from subprocess import Popen, PIPE
import shutil
from xml.sax.saxutils import escape

###
### xUnit generation
###

xml_escape_table = {
    "&": "&",
    '"': """,
    "'": "'",
    ">": ">",
    "<": "&lt;",
}

def xml_escape(text):
    return "".join(xml_escape_table.get(c,c) for c in text)

def readFile(f):
    with open(f, 'r') as f1:
      return f1.read()

def test_result_to_xml (result):

    returncode, testname, run_time, teststdout, testerr = result
    testxml = '<testcase classname="' + testname + '"' \
          + ' name="full log" time="' + str(round(run_time)) + '">'
    if ( returncode != 0) :
       testxml = testxml + '<failure>' + xml_escape(readFile(testerr)) + '</failure>'
    testxml = testxml + '</testcase>\n'
    return testxml

###
### support routines
###
def mkpath(path):
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

pyversion = float(sys.version_info[0]) + float(sys.version_info[1]) / 10.0

if pyversion < 3:
    str_encode = str
    str_decode = str
    def pipe_decode(output):
        return output
else:
    def str_encode(s):
        return bytes(s,sys.getdefaultencoding())
    def str_decode(bs):
        return bs.decode(sys.getdefaultencoding(),"strict")
    def pipe_decode(output):
        if isinstance(output,bytes) or isinstance(output,bytearray):
            return str_decode(output)
        elif isinstance(output,tuple):
            return (str_decode(output[0]),str_decode(output[1]))
        else:
            return ("","")

test_env = os.environ.copy( )
def dump_output(working_dir, bname, out, err):
    stdout_path = "%s/%s-stdout.txt" % (working_dir,bname)
    stdout_fd = open(stdout_path, 'w')
    stdout_fd.write(out)
    stdout_fd.close( )
    stderr_path = "%s/%s-stderr.txt" % (working_dir,bname)
    stderr_fd = open(stderr_path, 'w')
    stderr_fd.write(err)
    stderr_fd.close( )
    return (stdout_path,stderr_path)

def run_test(tabwidth,test_path,working_dir):
    label = '.'.join(os.path.basename(test_path).split('.')[:-1])
    sys.stdout.write(label + '.' * (tabwidth - len(label)))
    sys.stdout.flush( )
    start_time = time.time()
    proc = Popen( [sys.executable,test_path], cwd=working_dir, env=test_env,
                  stdout=subprocess.PIPE, stderr=subprocess.PIPE )
    (output, error) = pipe_decode(proc.communicate( ))
    exit_code = proc.wait( )
    (stdout_path,stderr_path) = dump_output(working_dir,"log",output,error)
    end_time = time.time()
    run_time = end_time-start_time
    print(" ok" if exit_code == 0 else " fail")
    return (exit_code, label, run_time, stdout_path, stderr_path)

###
### collect paths and test module names
###

if len(sys.argv) == 1:
    tests = [ ]
    run_py = os.path.realpath(__file__)
    test_dir = os.path.dirname(run_py)
    for dir, subdirs, files in os.walk(test_dir):
        for f in files:
            if f.endswith(".py") and f.startswith("test_"):
                workingdir = "%s/work/%s" % (test_dir,f[:-3])
                if os.path.exists(workingdir):
                    shutil.rmtree(workingdir)
                mkpath(workingdir)
                tests.append((os.path.abspath("%s/%s" % (dir,f)),workingdir))

    testwidth = 0 if len(tests) == 0 else max(map(lambda x: len(os.path.basename(x[0]))+3,tests))
    tabwidth = max(testwidth,45)

    start_time = time.time()

    results = list(map(lambda params: run_test(tabwidth,*params),tests))

    print('-' * (tabwidth + 8))
    passed = list(filter(lambda v: v[0] == 0,results))
    failed = list(filter(lambda v: v[0] != 0,results))

    # Construct xUnit.xml
    testHeader = '<?xml version="1.0" encoding="UTF-8"?>' + "\n" \
             + '<testsuite name="UnitTests" tests="' \
             + str(len(results)) + '" errors="0"' \
             + ' failures="' + str(len(failed)) + '" skip="0">\n'
    xmlResults = list(map(lambda result: test_result_to_xml (result), results))
    testFooter ="\n</testsuite>"

    # Write xUnit.xml
    xUnit = open("xUnit.xml","w+")
    xUnit.write(testHeader + ''.join(xmlResults) + testFooter)
    xUnit.close()

    end_time = time.time()

    print("ran %s tests in %.02f minutes, %d passed, %d failed" % (len(results),(end_time-start_time) / 60.0,len(passed),len(failed)))
    print("OK" if len(failed) == 0 else "FAIL")


    sys.exit(0 if len(failed) == 0 else 1)

else:
    test_paths = set( )
    test_modules = [ ]

    for i in sys.argv[1:]:
        ## note directory
        suite_path = os.path.dirname(i)
        test_paths.add(suite_path)
        ## discover module name
        suite_module, xxx = os.path.splitext(os.path.basename(i))
        test_modules.append(suite_module)

    sys.path = list(test_paths) + sys.path

    suite = unittest.defaultTestLoader.loadTestsFromNames(test_modules)
    unittest.TextTestRunner().run(suite)