#!/usr/bin/env python3

import os
import sys
import subprocess
import time
import difflib
import argparse
import re

parser = argparse.ArgumentParser()
parser.add_argument('testdir_or_test', nargs='*',
        help='subdir name or test script name')
parser.add_argument('-u', '--update', action='store_true',
        help='Update test expecations instead of verifying them')
args = parser.parse_args()

def run_test(path):
    print(path)
    p = subprocess.Popen(path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    o,e = p.communicate()
    while True:
        retval = p.poll()
        if retval is not None:
            break;
        p.kill()
        time.sleep(.1)
    return retval, o.decode('utf-8'), e.decode('utf-8')

def udiff(expect, got, expect_path):
    expect = expect.splitlines(1)
    got =  got.splitlines(1)
    for line in difflib.unified_diff(expect, got,
                                     fromfile=expect_path, tofile='got'):
        sys.stderr.write(line)
        if not line.endswith('\n'):
            sys.stderr.write('[no-newline]\n')

def verify_output(got, expect_file, update=False):
    if os.path.isfile(expect_file):
        ign_file = expect_file + '.ign'
        if os.path.isfile(ign_file):
            with open(ign_file, 'r') as f:
                ign_rules = f.readlines()
            for ign_rule in ign_rules:
                if not ign_rule:
                    continue
                if '\t' in ign_rule:
                    ign_rule, repl = ign_rule.split('\t')
                    repl = repl.strip()
                else:
                    repl = '*'
                ir = re.compile(ign_rule)
                got = repl.join(ir.split(got))

        if update:
            with open(expect_file, 'w') as f:
                f.write(got)
            return True

        with open(expect_file, 'r') as f:
            expect = f.read()

        if expect != got:
            udiff(expect, got, expect_file)
            sys.stderr.write('output mismatch: %r\n'
                            % os.path.basename(expect_file))
            return False
    return True


script_dir = sys.path[0]

tests = []
for d in os.listdir(script_dir):
    dir_path = os.path.join(script_dir, d)
    if not os.path.isdir(dir_path):
        continue
    if not dir_path.endswith('_test'):
        continue
    for f in os.listdir(dir_path):
        file_path = os.path.join(script_dir, d, f)
        if not os.path.isfile(file_path):
            continue

        if not (file_path.endswith('_test.py') or file_path.endswith('_test.sh')):
            continue
        tests.append(file_path)

ran = []
errors = []

for test in sorted(tests):

    if args.testdir_or_test:
        if not any([t in test for t in args.testdir_or_test]):
            continue

    ran.append(test)

    success = True

    name, ext = os.path.splitext(test)
    ok_file = name + '.ok'
    err_file = name + '.err'

    rc, out, err = run_test(test)

    if rc != 0:
        sys.stderr.write('%r: returned %d\n' % (os.path.basename(test), rc))
        success = False

    if not verify_output(out, ok_file, args.update):
        success = False
    if not verify_output(err, err_file, args.update):
        success = False

    if not success:
        sys.stderr.write('\nTest failed: %r\n\n' % os.path.basename(test))
        errors.append(test)

if errors:
    print('%d of %d TESTS FAILED:\n  %s' % (len(errors), len(ran), '\n  '.join(errors)))
    exit(1)

print('%d tests ok' % len(ran))
exit(0)

# vim: expandtab tabstop=4 shiftwidth=4