#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2021-2024 Intel Corporation

"""Script for running a single project test.

This script is meant for Meson developers who want to run a single project
test, with all of the rules from the test.json file loaded.
"""

import argparse
import pathlib
import typing as T

from mesonbuild import mlog
from mesonbuild.mesonlib import is_windows
from run_tests import handle_meson_skip_test
from run_project_tests import TestDef, TestCategory, load_test_json, run_test, BuildStep
from run_project_tests import setup_commands, detect_system_compiler, detect_tools
from run_project_tests import scan_test_data_symlinks, setup_symlinks, clear_transitive_files

if T.TYPE_CHECKING:
    from run_project_tests import CompilerArgumentType

    class ArgumentType(CompilerArgumentType):

        """Typing information for command line arguments."""

        case: pathlib.Path
        subtests: T.List[int]
        backend: str
        extra_args: T.List[str]
        quick: bool


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument('case', type=pathlib.Path, help='The test case to run')
    parser.add_argument('extra_args', nargs='*',
                        help='arguments that are passed directly to Meson (remember to have -- before these).')
    parser.add_argument('--subtest', type=int, action='append', dest='subtests', help='which subtests to run')
    parser.add_argument('--backend', action='store', help="Which backend to use")
    parser.add_argument('--cross-file', action='store', help='File describing cross compilation environment.')
    parser.add_argument('--native-file', action='store', help='File describing native compilation environment.')
    parser.add_argument('--use-tmpdir', action='store_true', help='Use tmp directory for temporary files.')
    parser.add_argument('--quick', action='store_true', help='Skip some compiler and tool checking')
    args = T.cast('ArgumentType', parser.parse_args())

    if not is_windows():
        scan_test_data_symlinks()
        setup_symlinks()
    setup_commands(args.backend)
    if not args.quick:
        detect_system_compiler(args)
    detect_tools(not args.quick)

    cat = TestCategory(args.case.stem, args.case.stem, False, False)
    test = TestDef(args.case, None, [], False, cat)
    tests = load_test_json(test, cat)
    if args.subtests:
        tests = [t for i, t in enumerate(tests) if i in args.subtests]

    def should_fail(path: pathlib.Path) -> str:
        dir_ = path.parent.stem
        # FIXME: warning tets might not be handled correctly still…
        if dir_.startswith(('failing', 'warning')):
            if ' ' in dir_:
                return dir_.split(' ')[1]
            return 'meson'
        return ''

    results = [run_test(t, t.args + args.extra_args, should_fail(t.path), args.use_tmpdir) for t in tests]
    failed = False
    for test, result in zip(tests, results):
        if result is None:
            is_skipped = True
            skip_reason = 'not run because preconditions were not met'
        else:
            is_skipped, skip_reason = handle_meson_skip_test(result.stdo)

        if is_skipped:
            msg = mlog.yellow('SKIP:')
        elif result.msg:
            msg = mlog.red('FAIL:')
            failed = True
        else:
            msg = mlog.green('PASS:')
        mlog.log(msg, *test.display_name())
        if skip_reason:
            mlog.log(mlog.bold('Reason:'), skip_reason)
        if result is not None and result.msg and 'MESON_SKIP_TEST' not in result.stdo:
            mlog.log('reason:', result.msg)
            if result.step is BuildStep.configure:
                # For configure failures, instead of printing stdout,
                # print the meson log if available since it's a superset
                # of stdout and often has very useful information.
                mlog.log(result.mlog)
            else:
                mlog.log(result.stdo)
            for cmd_res in result.cicmds:
                mlog.log(cmd_res)
            mlog.log(result.stde)

    clear_transitive_files()
    exit(1 if failed else 0)

if __name__ == "__main__":
    main()
