#!/usr/bin/env python3
# Copyright 2018 Red Hat
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
from __future__ import absolute_import

import os
import psutil
import resource
import signal
import sys
import subprocess


TOP_DIR = os.path.dirname(os.path.dirname(__file__))
if TOP_DIR not in sys.path:
    sys.path.insert(0, TOP_DIR)

from tools import common  # noqa

LOG = common.get_logger(__name__)

# Root tests dir
TEST_PATH = common.normalize_path(
    os.environ.get('TOBIKO_TEST_PATH') or
    os.environ.get('OS_TEST_PATH') or
    os.path.join(TOP_DIR, 'tobiko', 'tests', 'unit'))

# Output dirs
REPORT_DIR = common.normalize_path(
    os.environ.get('TOBIKO_REPORT_DIR') or
    os.environ.get('TOX_REPORT_DIR') or
    os.getcwd())

REPORT_NAME = (
    os.environ.get('TOBIKO_REPORT_NAME') or
    os.environ.get('TOX_REPORT_NAME') or
    'tobiko_results')

REPORT_PREFIX = os.path.join(REPORT_DIR, REPORT_NAME)

REPORT_LOG = (
    os.environ.get('TOBIKO_REPORT_LOG') or
    os.environ.get('TOX_REPORT_LOG') or
    REPORT_PREFIX + '.log')

REPORT_HTML = (
    os.environ.get('TOBIKO_REPORT_HTML') or
    os.environ.get('TOX_REPORT_HTML') or
    REPORT_PREFIX + '.html')

REPORT_XML = (
    os.environ.get('TOBIKO_REPORT_XML') or
    os.environ.get('TOX_REPORT_XML') or
    REPORT_PREFIX + '.xml')

NUM_PROCESSES = (
    os.environ.get('TOBIKO_NUM_PROCESSES') or
    os.environ.get('TOX_NUM_PROCESSES') or
    'auto')

RUN_TESTS_TIMEOUT = float(
    os.environ.get('TOBIKO_RUN_TESTS_TIMEOUT') or
    os.environ.get('TOX_RUN_TESTS_TIMEOUT') or
    0.)

RERUNS = int(
    os.environ.get('TOBIKO_RERUNS') or
    os.environ.get('TOX_RERUNS') or
    0)

RERUNS_DELAY = int(
    os.environ.get('TOBIKO_RERUNS_DELAY') or
    os.environ.get('TOX_RERUNS_DELAY') or
    5)

COVER = (
    os.environ.get('TOBIKO_COVER') or
    os.environ.get('TOX_COVER') or
    'false') in ['1', 'yes', 'true']


def main():
    common.setup_logging()
    try:
        succeeded = run_tests()
        if succeeded:
            LOG.info('SUCCEEDED')
            sys.exit(0)
        else:
            LOG.info('FAILED')
            sys.exit(1)

    except Exception:
        LOG.exception('ERROR')
        sys.exit(2)


def run_tests():
    setup_ulimits()
    setup_timeout()
    cleanup_report_dir()
    log_environ()

    succeeded = True
    try:
        run_test_cases()
    except (subprocess.CalledProcessError,
            subprocess.TimeoutExpired,
            ProcessLookupError) as ex:
        LOG.error(f"Error while running test cases.\n{ex}")
        succeeded = False

    return succeeded


def setup_ulimits():
    try:
        hard_nofile_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
        resource.setrlimit(
            resource.RLIMIT_NOFILE,
            (hard_nofile_limit, hard_nofile_limit))
    except ValueError:
        pass


def setup_timeout():

    if RUN_TESTS_TIMEOUT > 0.:

        def handle_timeout(_signum, _frame):
            LOG.error(
                f"run_tests.py timeout out after {RUN_TESTS_TIMEOUT} "
                "seconds")
            terminate_childs()
            raise subprocess.TimeoutExpired("run_tests.py",
                                            RUN_TESTS_TIMEOUT)

        signal.setitimer(signal.ITIMER_REAL, RUN_TESTS_TIMEOUT)
        signal.signal(signal.SIGALRM, handle_timeout)
        LOG.debug(f'Run tests timeout set as {RUN_TESTS_TIMEOUT} seconds')


def terminate_childs():
    current_process = psutil.Process()
    children = current_process.children(recursive=False)
    for child in children:
        LOG.debug(f"Interrupt child process execution (pid={child.pid})")
        os.kill(child.pid, signal.SIGINT)
    for child in children:
        LOG.debug("Wait for top-child process termination "
                  f"(pid={child.pid})...")
        os.waitpid(child.pid, 0)


def cleanup_report_dir():
    for report_file in [REPORT_LOG, REPORT_HTML, REPORT_XML]:
        if not common.make_dir(os.path.dirname(report_file)):
            common.remove_file(report_file)


def log_environ():
    common.execute('env | sort >> "{log_file}"',
                   log_file=REPORT_LOG,
                   capture_stdout=False)


def run_test_cases():
    xdist_options = ''
    if NUM_PROCESSES != '1':
        xdist_options = f"--numprocesses '{NUM_PROCESSES}' --dist loadscope"
    rerun_options = ''
    if RERUNS:
        rerun_options = f"--reruns '{RERUNS}' --reruns-delay '{RERUNS_DELAY}'"
    cover_options = ''
    if COVER:
        cover_options = f"--cov=tobiko"

    # Pass environment variables to pytest command
    environ = dict(os.environ, TOBIKO_REPORT_NAME=REPORT_NAME)
    common.execute("pytest -v "
                   f"{xdist_options} "
                   f"{rerun_options} "
                   f"{cover_options} "
                   f"--log-file={REPORT_LOG} "
                   f"--junitxml={REPORT_XML} "
                   f"--html={REPORT_HTML} --self-contained-html "
                   f"{common.get_posargs()}",
                   environ=environ,
                   capture_stdout=False)


if __name__ == '__main__':
    main()
