xref: /aosp_15_r20/external/angle/build/fuchsia/test/bundled_test_runner.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1# Copyright 2024 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Provides a way to run multiple tests as a bundle. It forwards all the
5   arguments to the run_test.py, but overrides the test runner it uses.
6
7   Since this class runs in a higher level as the regular run_test.py, it would
8   be more clear to not include it into the run_test.py and avoid
9   cycle-dependency.
10
11   Use of this test runner may break sharding."""
12
13import argparse
14import logging
15import os
16import sys
17
18from subprocess import CompletedProcess
19from typing import List, NamedTuple, Optional
20from urllib.parse import urlparse
21
22import run_test
23from run_executable_test import ExecutableTestRunner
24from test_runner import TestRunner
25
26
27class TestCase(NamedTuple):
28    """Defines a TestCase, it executes the package with optional arguments."""
29
30    # The test package in the format of fuchsia-pkg://...#meta/...cm.
31    package: str
32
33    # Optional arguments to pass to the test run. It can include multiple
34    # arguments separated by any whitespaces.
35    args: str = ''
36
37
38class _BundledTestRunner(TestRunner):
39    """A TestRunner implementation to run multiple test cases. It always run all
40       tests even some of them failed. The return code is the return code of the
41       last non-zero test run."""
42
43    # private, use run_tests.get_test_runner function instead.
44    # Keep the order of parameters consistent with ExecutableTestRunner.
45    # pylint: disable=too-many-arguments
46    # TODO(crbug.com/346806329): May consider using a structure to capture the
47    # arguments.
48    def __init__(self, out_dir: str, tests: List[TestCase],
49                 target_id: Optional[str], code_coverage_dir: Optional[str],
50                 logs_dir: Optional[str], package_deps: List[str],
51                 test_realm: Optional[str]):
52        super().__init__(
53            out_dir, [], [], target_id,
54            _BundledTestRunner._merge_packages(tests, package_deps))
55        assert tests
56        self._tests = tests
57        self._code_coverage_dir = code_coverage_dir
58        self._logs_dir = logs_dir
59        self._test_realm = test_realm
60
61    @staticmethod
62    def _merge_packages(tests: List[TestCase],
63                        package_deps: List[str]) -> List[str]:
64        packages = list(package_deps)
65        # Include test packages if they have not been defined in the
66        # package_deps.
67        packages.extend(
68            {urlparse(x.package).path.lstrip('/') + '.far'
69             for x in tests} - {os.path.basename(x)
70                                for x in packages})
71        return packages
72
73    def run_test(self) -> CompletedProcess:
74        returncode = 0
75        for test in self._tests:
76            assert test.package.endswith('.cm')
77            test_runner = ExecutableTestRunner(
78                self._out_dir, test.args.split(), test.package,
79                self._target_id, self._code_coverage_dir, self._logs_dir,
80                self._package_deps, self._test_realm)
81            # It's a little bit wasteful to resolve all the packages once per
82            # test package, but it's easier.
83            result = test_runner.run_test().returncode
84            logging.info('Result of test %s is %s', test, result)
85            if result != 0:
86                returncode = result
87        return CompletedProcess(args='', returncode=returncode)
88
89
90def run_tests(tests: List[TestCase]) -> int:
91    """Runs multiple tests.
92
93       Args:
94         tests: The definition of each test case.
95
96       Note:
97         All the packages in tests will always be included, and it's expected
98         that the far files sharing the same name as the package in TestCase
99         except for the suffix. E.g. test1.far is the far file of
100         fuchsia-pkg://fuchsia.com/test1#meta/some.cm.
101
102         Duplicated packages in either --packages or tests are allowed as long
103         as they are targeting the same file; otherwise the test run would
104         trigger an assertion failure.
105
106         Far files in the --packages can be either absolute paths or relative
107         paths starting from --out-dir."""
108    # The 'bundled-tests' is a place holder and has no specific meaning; the
109    # run_test._get_test_runner is overridden.
110    sys.argv.append('bundled-tests')
111
112    def get_test_runner(runner_args: argparse.Namespace, *_) -> TestRunner:
113        # test_args are not used, and each TestCase should have its own args.
114        return _BundledTestRunner(runner_args.out_dir, tests,
115                                  runner_args.target_id,
116                                  runner_args.code_coverage_dir,
117                                  runner_args.logs_dir, runner_args.packages,
118                                  runner_args.test_realm)
119
120    # pylint: disable=protected-access
121    run_test._get_test_runner = get_test_runner
122    return run_test.main()
123