#!/usr/bin/env python3
import subprocess
from subprocess import Popen
from os import system
from sys import argv, exit, platform, executable, version_info


# Use the Python provided to execute the script, or fall back to a sane default
if version_info >= (3, 5, 0):
    python_name = executable
else:
    if platform == 'win32':
        python_name = 'py -3'
    else:
        python_name = 'python3'

# Slow test suites
CMDLINE = 'PythonCmdline'
SAMPLES = 'SamplesSuite'
TYPESHED = 'TypeshedSuite'
PEP561 = 'PEP561Suite'
EVALUATION = 'PythonEvaluation'
DAEMON = 'testdaemon'
STUBGEN_CMD = 'StubgenCmdLine'
STUBGEN_PY = 'StubgenPythonSuite'
MYPYC_RUN = 'TestRun'
MYPYC_RUN_MULTI = 'TestRunMultiFile'
MYPYC_EXTERNAL = 'TestExternal'
MYPYC_COMMAND_LINE = 'TestCommandLine'
ERROR_STREAM = 'ErrorStreamSuite'
STUBTEST = 'StubtestUnit'
STUBTEST_MISC = 'StubtestMiscUnit'
STUBTEST_INTEGRATION = 'StubtestIntegration'


ALL_NON_FAST = [
    CMDLINE,
    SAMPLES,
    TYPESHED,
    PEP561,
    EVALUATION,
    DAEMON,
    STUBGEN_CMD,
    STUBGEN_PY,
    MYPYC_RUN,
    MYPYC_RUN_MULTI,
    MYPYC_EXTERNAL,
    MYPYC_COMMAND_LINE,
    ERROR_STREAM,
    STUBTEST,
    STUBTEST_MISC,
    STUBTEST_INTEGRATION,
]


# These must be enabled by explicitly including 'mypyc-extra' on the command line.
MYPYC_OPT_IN = [MYPYC_RUN,
                MYPYC_RUN_MULTI]

# These must be enabled by explicitly including 'stubtest' on the command line.
STUBTEST_OPT_IN = [STUBTEST, STUBTEST_MISC, STUBTEST_INTEGRATION]

# We split the pytest run into three parts to improve test
# parallelization. Each run should have tests that each take a roughly similar
# time to run.
cmds = {
    # Self type check
    'self': python_name + ' -m mypy --config-file mypy_self_check.ini -p mypy',
    # Lint
    'lint': 'flake8 -j0',
    # Fast test cases only (this is the bulk of the test suite)
    'pytest-fast': 'pytest -k "not (%s)"' % ' or '.join(ALL_NON_FAST),
    # Test cases that invoke mypy (with small inputs)
    'pytest-cmdline': 'pytest -k "%s"' % ' or '.join([CMDLINE,
                                                      EVALUATION,
                                                      STUBGEN_CMD,
                                                      STUBGEN_PY]),
    # Test cases that may take seconds to run each
    'pytest-slow': 'pytest -k "%s"' % ' or '.join(
        [SAMPLES,
         TYPESHED,
         PEP561,
         DAEMON,
         MYPYC_EXTERNAL,
         MYPYC_COMMAND_LINE,
         ERROR_STREAM]),
    # Mypyc tests that aren't run by default, since they are slow and rarely
    # fail for commits that don't touch mypyc
    'mypyc-extra': 'pytest -k "%s"' % ' or '.join(MYPYC_OPT_IN),
    'stubtest': 'pytest -k "%s"' % ' or '.join(STUBTEST_OPT_IN),
}

# Stop run immediately if these commands fail
FAST_FAIL = ['self', 'lint']

DEFAULT_COMMANDS = [cmd for cmd in cmds if cmd != 'mypyc-extra']

assert all(cmd in cmds for cmd in FAST_FAIL)


def run_cmd(name: str) -> int:
    status = 0
    cmd = cmds[name]
    print('run %s: %s' % (name, cmd))
    res = (system(cmd) & 0x7F00) >> 8
    if res:
        print('\nFAILED: %s' % name)
        status = res
        if name in FAST_FAIL:
            exit(status)
    return status


def start_background_cmd(name: str) -> Popen:
    cmd = cmds[name]
    proc = subprocess.Popen(cmd,
                            shell=True,
                            stderr=subprocess.STDOUT,
                            stdout=subprocess.PIPE)
    return proc


def wait_background_cmd(name: str, proc: Popen) -> int:
    output = proc.communicate()[0]
    status = proc.returncode
    print('run %s: %s' % (name, cmds[name]))
    if status:
        print(output.decode().rstrip())
        print('\nFAILED: %s' % name)
        if name in FAST_FAIL:
            exit(status)
    return status


def main() -> None:
    prog, *args = argv

    if not set(args).issubset(cmds):
        print("usage:", prog, " ".join('[%s]' % k for k in cmds))
        print()
        print('Run the given tests. If given no arguments, run everything except mypyc-extra.')
        exit(1)

    if not args:
        args = DEFAULT_COMMANDS[:]

    status = 0

    if 'self' in args and 'lint' in args:
        # Perform lint and self check in parallel as it's faster.
        proc = start_background_cmd('lint')
        cmd_status = run_cmd('self')
        if cmd_status:
            status = cmd_status
        cmd_status = wait_background_cmd('lint', proc)
        if cmd_status:
            status = cmd_status
        args = [arg for arg in args if arg not in ('self', 'lint')]

    for arg in args:
        cmd_status = run_cmd(arg)
        if cmd_status:
            status = cmd_status

    exit(status)


if __name__ == '__main__':
    main()
