import sys, os
import re
import unittest
import traceback
import pywin32_testutil

# A list of demos that depend on user-interface of *any* kind.  Tests listed
# here are not suitable for unattended testing.
ui_demos = """GetSaveFileName print_desktop win32cred_demo win32gui_demo
              win32gui_dialog win32gui_menu win32gui_taskbar
              win32rcparser_demo winprocess win32console_demo
              win32clipboard_bitmapdemo
              win32gui_devicenotify
              NetValidatePasswordPolicy""".split()
# Other demos known as 'bad' (or at least highly unlikely to work)
# cerapi: no CE module is built (CE via pywin32 appears dead)
# desktopmanager: hangs (well, hangs for 60secs or so...)
# EvtSubscribe_*: must be run together
bad_demos = """cerapi desktopmanager win32comport_demo
               EvtSubscribe_pull EvtSubscribe_push
            """.split()

argvs = {
    "rastest": ("-l",),
}

no_user_interaction = False

# re to pull apart an exception line into the exception type and the args.
re_exception = re.compile("([a-zA-Z0-9_.]*): (.*)$")
def find_exception_in_output(data):
    have_traceback = False
    for line in data.splitlines():
        line = line.decode('ascii') # not sure what the correct encoding is...
        if line.startswith("Traceback ("):
            have_traceback = True
            continue
        if line.startswith(" "):
            continue
        if have_traceback:
            # first line not starting with a space since the traceback.
            # must be the exception!
            m = re_exception.match(line)
            if m:
                exc_type, args = m.groups()
                # get hacky - get the *real* exception object from the name.
                bits = exc_type.split(".", 1)
                if len(bits) > 1:
                    mod = __import__(bits[0])
                    exc = getattr(mod, bits[1])
                else:
                    # probably builtin
                    exc = eval(bits[0])
            else:
                # hrm - probably just an exception with no args
                try:
                    exc = eval(line.strip())
                    args = "()"
                except:
                    return None
            # try and turn the args into real args.
            try:
                args = eval(args)
            except:
                pass
            if not isinstance(args, tuple):
                args = (args,)
            # try and instantiate the exception.
            try:
                ret = exc(*args)
            except:
                ret = None
            return ret
        # apparently not - keep looking...
        have_traceback = False


class TestRunner:
    def __init__(self, argv):
        self.argv = argv
        self.__name__ = "Test Runner for cmdline {}".format(argv)

    def __call__(self):
        import subprocess
        p = subprocess.Popen(self.argv,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)
        output, _ = p.communicate()
        rc = p.returncode

        if rc:
            base = os.path.basename(self.argv[1])
            # See if we can detect and reconstruct an exception in the output.
            reconstituted = find_exception_in_output(output)
            if reconstituted is not None:
                raise reconstituted
            raise AssertionError("%s failed with exit code %s.  Output is:\n%s" % (base, rc, output))

def get_demo_tests():
    import win32api
    ret = []
    demo_dir = os.path.abspath(os.path.join(os.path.dirname(win32api.__file__), "Demos"))
    assert os.path.isdir(demo_dir), demo_dir
    for name in os.listdir(demo_dir):
        base, ext = os.path.splitext(name)
        if base in ui_demos and no_user_interaction:
            continue
        # Skip any other files than .py and bad tests in any case
        if ext != ".py" or base in bad_demos:
            continue
        argv = (sys.executable, os.path.join(demo_dir, base+".py")) + \
               argvs.get(base, ())
        ret.append(unittest.FunctionTestCase(TestRunner(argv), description="win32/demos/" + name))
    return ret

def import_all():
    # Some hacks for import order - dde depends on win32ui
    try:
        import win32ui
    except ImportError:
        pass # 'what-ev-a....'

    import win32api
    dir = os.path.dirname(win32api.__file__)
    num = 0
    is_debug = os.path.basename(win32api.__file__).endswith("_d")
    for name in os.listdir(dir):
        base, ext = os.path.splitext(name)
        if (ext==".pyd") and \
           name != "_winxptheme.pyd" and \
           (is_debug and base.endswith("_d") or \
           not is_debug and not base.endswith("_d")):
            try:
                __import__(base)
            except:
                print("FAILED to import", name)
                raise
            num += 1

def suite():
    # Loop over all .py files here, except me :)
    try:
        me = __file__
    except NameError:
        me = sys.argv[0]
    me = os.path.abspath(me)
    files = os.listdir(os.path.dirname(me))
    suite = unittest.TestSuite()
    suite.addTest(unittest.FunctionTestCase(import_all))
    for file in files:
        base, ext = os.path.splitext(file)
        if ext=='.py' and os.path.basename(me) != file:
            try:
                mod = __import__(base)
            except:
                print("FAILED to import test module %r" % base)
                traceback.print_exc()
                continue
            if hasattr(mod, "suite"):
                test = mod.suite()
            else:
                test = unittest.defaultTestLoader.loadTestsFromModule(mod)
            suite.addTest(test)
    for test in get_demo_tests():
        suite.addTest(test)
    return suite


class CustomLoader(pywin32_testutil.TestLoader):
    def loadTestsFromModule(self, module):
        return self.fixupTestsForLeakTests(suite())


if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description="Test runner for PyWin32/win32")
    parser.add_argument("-no-user-interaction",
                        default=no_user_interaction,
                        action='store_true',
                        help="Run all tests without user interaction")

    parsed_args, remains = parser.parse_known_args()

    no_user_interaction = parsed_args.no_user_interaction

    sys.argv = [sys.argv[0]] + remains

    pywin32_testutil.testmain(testLoader=CustomLoader())
