from __future__ import absolute_import, unicode_literals

import signal
import subprocess
import sys
from datetime import datetime

import pytest
from flaky import flaky
from pathlib2 import Path

from tox.constants import INFO
from tox.util.main import MAIN_FILE


@flaky(max_runs=3)
@pytest.mark.skipif(INFO.IS_PYPY, reason="TODO: process numbers work differently on pypy")
@pytest.mark.skipif(
    "sys.platform == 'win32'", reason="triggering SIGINT reliably on Windows is hard",
)
def test_parallel_interrupt(initproj, monkeypatch, capfd):
    monkeypatch.setenv(str("_TOX_SKIP_ENV_CREATION_TEST"), str("1"))
    monkeypatch.setenv(str("TOX_REPORTER_TIMESTAMP"), str("1"))
    start = datetime.now()
    initproj(
        "pkg123-0.7",
        filedefs={
            "tox.ini": """
                    [tox]
                    envlist = a, b

                    [testenv]
                    skip_install = True
                    commands = python -c "open('{{envname}}', 'w').write('done'); \
                    import time; time.sleep(100)"
                    whitelist_externals = {}

                """.format(
                sys.executable,
            ),
        },
    )
    process = subprocess.Popen(
        [sys.executable, MAIN_FILE, "-p", "all"],
        creationflags=(
            subprocess.CREATE_NEW_PROCESS_GROUP
            if sys.platform == "win32"
            else 0
            # needed for Windows signal send ability (CTRL+C)
        ),
    )
    try:
        import psutil

        current_process = psutil.Process(process.pid)
    except ImportError:
        current_process = None

    wait_for_env_startup(process)

    all_children = []
    if current_process is not None:
        all_children.append(current_process)
        all_children.extend(current_process.children(recursive=True))
        assert len(all_children) >= 1 + 2 + 2, all_children
    end = datetime.now() - start
    assert end
    process.send_signal(signal.CTRL_C_EVENT if sys.platform == "win32" else signal.SIGINT)
    process.wait()
    out, err = capfd.readouterr()
    output = "{}\n{}".format(out, err)
    assert "KeyboardInterrupt parallel - stopping children" in output, output
    assert "ERROR:   a: parallel child exit code " in output, output
    assert "ERROR:   b: parallel child exit code " in output, output
    for process in all_children:
        msg = "{}{}".format(output, "\n".join(repr(i) for i in all_children))
        assert not process.is_running(), msg


def wait_for_env_startup(process):
    """the environments will write files once they are up"""
    signal_files = [Path() / "a", Path() / "b"]
    found = False
    while True:
        if process.poll() is not None:
            break
        for signal_file in signal_files:
            if not signal_file.exists():
                break
        else:
            found = True
            break
    if not found or process.poll() is not None:
        missing = [f for f in signal_files if not f.exists()]
        out, _ = process.communicate()
        assert len(missing), out
        assert False, out