xref: /aosp_15_r20/system/extras/simpleperf/scripts/test/do_test.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*288bf522SAndroid Build Coastguard Worker#
3*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2017 The Android Open Source Project
4*288bf522SAndroid Build Coastguard Worker#
5*288bf522SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*288bf522SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*288bf522SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*288bf522SAndroid Build Coastguard Worker#
9*288bf522SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*288bf522SAndroid Build Coastguard Worker#
11*288bf522SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*288bf522SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*288bf522SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*288bf522SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*288bf522SAndroid Build Coastguard Worker# limitations under the License.
16*288bf522SAndroid Build Coastguard Worker#
17*288bf522SAndroid Build Coastguard Worker"""Release test for simpleperf prebuilts.
18*288bf522SAndroid Build Coastguard Worker
19*288bf522SAndroid Build Coastguard WorkerIt includes below tests:
20*288bf522SAndroid Build Coastguard Worker1. Test profiling Android apps on different Android versions (starting from Android N).
21*288bf522SAndroid Build Coastguard Worker2. Test simpleperf python scripts on different Hosts (linux, darwin and windows) on x86_64.
22*288bf522SAndroid Build Coastguard Worker3. Test using both devices and emulators.
23*288bf522SAndroid Build Coastguard Worker4. Test using both `adb root` and `adb unroot`.
24*288bf522SAndroid Build Coastguard Worker
25*288bf522SAndroid Build Coastguard Worker"""
26*288bf522SAndroid Build Coastguard Worker
27*288bf522SAndroid Build Coastguard Workerimport argparse
28*288bf522SAndroid Build Coastguard Workerfrom dataclasses import dataclass
29*288bf522SAndroid Build Coastguard Workerimport fnmatch
30*288bf522SAndroid Build Coastguard Workerimport inspect
31*288bf522SAndroid Build Coastguard Workerimport multiprocessing as mp
32*288bf522SAndroid Build Coastguard Workerimport os
33*288bf522SAndroid Build Coastguard Workerfrom pathlib import Path
34*288bf522SAndroid Build Coastguard Workerimport re
35*288bf522SAndroid Build Coastguard Workerimport subprocess
36*288bf522SAndroid Build Coastguard Workerimport sys
37*288bf522SAndroid Build Coastguard Workerimport time
38*288bf522SAndroid Build Coastguard Workerfrom tqdm import tqdm
39*288bf522SAndroid Build Coastguard Workerimport types
40*288bf522SAndroid Build Coastguard Workerfrom typing import List, Optional
41*288bf522SAndroid Build Coastguard Workerimport unittest
42*288bf522SAndroid Build Coastguard Worker
43*288bf522SAndroid Build Coastguard Workerfrom simpleperf_utils import BaseArgumentParser, extant_dir, log_exit, remove, is_darwin
44*288bf522SAndroid Build Coastguard Worker
45*288bf522SAndroid Build Coastguard Workerfrom . api_profiler_test import *
46*288bf522SAndroid Build Coastguard Workerfrom . annotate_test import *
47*288bf522SAndroid Build Coastguard Workerfrom . app_profiler_test import *
48*288bf522SAndroid Build Coastguard Workerfrom . app_test import *
49*288bf522SAndroid Build Coastguard Workerfrom . binary_cache_builder_test import *
50*288bf522SAndroid Build Coastguard Workerfrom . cpp_app_test import *
51*288bf522SAndroid Build Coastguard Workerfrom . debug_unwind_reporter_test import *
52*288bf522SAndroid Build Coastguard Workerfrom . gecko_profile_generator_test import *
53*288bf522SAndroid Build Coastguard Workerfrom . inferno_test import *
54*288bf522SAndroid Build Coastguard Workerfrom . java_app_test import *
55*288bf522SAndroid Build Coastguard Workerfrom . kotlin_app_test import *
56*288bf522SAndroid Build Coastguard Workerfrom . pprof_proto_generator_test import *
57*288bf522SAndroid Build Coastguard Workerfrom . purgatorio_test import *
58*288bf522SAndroid Build Coastguard Workerfrom . report_html_test import *
59*288bf522SAndroid Build Coastguard Workerfrom . report_lib_test import *
60*288bf522SAndroid Build Coastguard Workerfrom . report_sample_test import *
61*288bf522SAndroid Build Coastguard Workerfrom . run_simpleperf_on_device_test import *
62*288bf522SAndroid Build Coastguard Workerfrom . sample_filter_test import *
63*288bf522SAndroid Build Coastguard Workerfrom . stackcollapse_test import *
64*288bf522SAndroid Build Coastguard Workerfrom . tools_test import *
65*288bf522SAndroid Build Coastguard Workerfrom . test_utils import TestHelper
66*288bf522SAndroid Build Coastguard Worker
67*288bf522SAndroid Build Coastguard Worker
68*288bf522SAndroid Build Coastguard Workerdef get_args() -> argparse.Namespace:
69*288bf522SAndroid Build Coastguard Worker    parser = BaseArgumentParser(description=__doc__)
70*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--browser', action='store_true', help='open report html file in browser.')
71*288bf522SAndroid Build Coastguard Worker    parser.add_argument(
72*288bf522SAndroid Build Coastguard Worker        '-d', '--device', nargs='+',
73*288bf522SAndroid Build Coastguard Worker        help='set devices used to run tests. Each device in format name:serial-number')
74*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--only-host-test', action='store_true', help='Only run host tests')
75*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--list-tests', action='store_true', help='List tests')
76*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--ndk-path', type=extant_dir, help='Set the path of a ndk release')
77*288bf522SAndroid Build Coastguard Worker    parser.add_argument('-p', '--pattern', nargs='+',
78*288bf522SAndroid Build Coastguard Worker                        help='Run tests matching the selected pattern.')
79*288bf522SAndroid Build Coastguard Worker    parser.add_argument('-r', '--repeat', type=int, default=1, help='times to repeat tests')
80*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--test-from', help='Run tests following the selected test.')
81*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--test-dir', default='test_dir', help='Directory to store test results')
82*288bf522SAndroid Build Coastguard Worker    return parser.parse_args()
83*288bf522SAndroid Build Coastguard Worker
84*288bf522SAndroid Build Coastguard Worker
85*288bf522SAndroid Build Coastguard Workerdef get_all_tests() -> List[str]:
86*288bf522SAndroid Build Coastguard Worker    tests = []
87*288bf522SAndroid Build Coastguard Worker    for name, value in globals().items():
88*288bf522SAndroid Build Coastguard Worker        if isinstance(value, type) and issubclass(value, unittest.TestCase):
89*288bf522SAndroid Build Coastguard Worker            for member_name, member in inspect.getmembers(value):
90*288bf522SAndroid Build Coastguard Worker                if isinstance(member, (types.MethodType, types.FunctionType)):
91*288bf522SAndroid Build Coastguard Worker                    if member_name.startswith('test'):
92*288bf522SAndroid Build Coastguard Worker                        tests.append(name + '.' + member_name)
93*288bf522SAndroid Build Coastguard Worker    return sorted(tests)
94*288bf522SAndroid Build Coastguard Worker
95*288bf522SAndroid Build Coastguard Worker
96*288bf522SAndroid Build Coastguard Workerdef get_host_tests() -> List[str]:
97*288bf522SAndroid Build Coastguard Worker    def filter_fn(test: str) -> bool:
98*288bf522SAndroid Build Coastguard Worker        return get_test_type(test) == 'host_test'
99*288bf522SAndroid Build Coastguard Worker    return list(filter(filter_fn, get_all_tests()))
100*288bf522SAndroid Build Coastguard Worker
101*288bf522SAndroid Build Coastguard Worker
102*288bf522SAndroid Build Coastguard Workerdef get_filtered_tests(
103*288bf522SAndroid Build Coastguard Worker        tests: List[str],
104*288bf522SAndroid Build Coastguard Worker        test_from: Optional[str],
105*288bf522SAndroid Build Coastguard Worker        test_pattern: Optional[List[str]]) -> List[str]:
106*288bf522SAndroid Build Coastguard Worker    if test_from:
107*288bf522SAndroid Build Coastguard Worker        try:
108*288bf522SAndroid Build Coastguard Worker            tests = tests[tests.index(test_from):]
109*288bf522SAndroid Build Coastguard Worker        except ValueError:
110*288bf522SAndroid Build Coastguard Worker            log_exit("Can't find test %s" % test_from)
111*288bf522SAndroid Build Coastguard Worker    if test_pattern:
112*288bf522SAndroid Build Coastguard Worker        patterns = [re.compile(fnmatch.translate(x)) for x in test_pattern]
113*288bf522SAndroid Build Coastguard Worker        tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)]
114*288bf522SAndroid Build Coastguard Worker        if not tests:
115*288bf522SAndroid Build Coastguard Worker            log_exit('No tests are matched.')
116*288bf522SAndroid Build Coastguard Worker    return tests
117*288bf522SAndroid Build Coastguard Worker
118*288bf522SAndroid Build Coastguard Worker
119*288bf522SAndroid Build Coastguard Workerdef get_test_type(test: str) -> Optional[str]:
120*288bf522SAndroid Build Coastguard Worker    testcase_name, test_name = test.split('.')
121*288bf522SAndroid Build Coastguard Worker    if test_name == 'test_run_simpleperf_without_usb_connection':
122*288bf522SAndroid Build Coastguard Worker        return 'device_serialized_test'
123*288bf522SAndroid Build Coastguard Worker    if testcase_name in (
124*288bf522SAndroid Build Coastguard Worker        'TestApiProfiler', 'TestNativeProfiling', 'TestNativeLibDownloader',
125*288bf522SAndroid Build Coastguard Worker            'TestRecordingRealApps', 'TestRunSimpleperfOnDevice'):
126*288bf522SAndroid Build Coastguard Worker        return 'device_test'
127*288bf522SAndroid Build Coastguard Worker    if testcase_name.startswith('TestExample'):
128*288bf522SAndroid Build Coastguard Worker        return 'device_test'
129*288bf522SAndroid Build Coastguard Worker    if testcase_name in ('TestAnnotate',
130*288bf522SAndroid Build Coastguard Worker                         'TestBinaryCacheBuilder',
131*288bf522SAndroid Build Coastguard Worker                         'TestDebugUnwindReporter',
132*288bf522SAndroid Build Coastguard Worker                         'TestInferno',
133*288bf522SAndroid Build Coastguard Worker                         'TestPprofProtoGenerator',
134*288bf522SAndroid Build Coastguard Worker                         'TestProtoFileReportLib',
135*288bf522SAndroid Build Coastguard Worker                         'TestPurgatorio',
136*288bf522SAndroid Build Coastguard Worker                         'TestReportHtml',
137*288bf522SAndroid Build Coastguard Worker                         'TestReportLib',
138*288bf522SAndroid Build Coastguard Worker                         'TestReportSample',
139*288bf522SAndroid Build Coastguard Worker                         'TestSampleFilter',
140*288bf522SAndroid Build Coastguard Worker                         'TestStackCollapse',
141*288bf522SAndroid Build Coastguard Worker                         'TestTools',
142*288bf522SAndroid Build Coastguard Worker                         'TestGeckoProfileGenerator'):
143*288bf522SAndroid Build Coastguard Worker        return 'host_test'
144*288bf522SAndroid Build Coastguard Worker    return None
145*288bf522SAndroid Build Coastguard Worker
146*288bf522SAndroid Build Coastguard Worker
147*288bf522SAndroid Build Coastguard Workerdef build_testdata(testdata_dir: Path):
148*288bf522SAndroid Build Coastguard Worker    """ Collect testdata in testdata_dir.
149*288bf522SAndroid Build Coastguard Worker        In system/extras/simpleperf/scripts, testdata comes from:
150*288bf522SAndroid Build Coastguard Worker            <script_dir>/../testdata, <script_dir>/test/script_testdata, <script_dir>/../demo
151*288bf522SAndroid Build Coastguard Worker        In prebuilts/simpleperf, testdata comes from:
152*288bf522SAndroid Build Coastguard Worker            <script_dir>/test/testdata
153*288bf522SAndroid Build Coastguard Worker    """
154*288bf522SAndroid Build Coastguard Worker    testdata_dir.mkdir()
155*288bf522SAndroid Build Coastguard Worker
156*288bf522SAndroid Build Coastguard Worker    script_test_dir = Path(__file__).resolve().parent
157*288bf522SAndroid Build Coastguard Worker    script_dir = script_test_dir.parent
158*288bf522SAndroid Build Coastguard Worker
159*288bf522SAndroid Build Coastguard Worker    source_dirs = [
160*288bf522SAndroid Build Coastguard Worker        script_test_dir / 'script_testdata',
161*288bf522SAndroid Build Coastguard Worker        script_test_dir / 'testdata',
162*288bf522SAndroid Build Coastguard Worker        script_dir.parent / 'testdata',
163*288bf522SAndroid Build Coastguard Worker        script_dir.parent / 'demo',
164*288bf522SAndroid Build Coastguard Worker        script_dir.parent / 'runtest',
165*288bf522SAndroid Build Coastguard Worker    ]
166*288bf522SAndroid Build Coastguard Worker
167*288bf522SAndroid Build Coastguard Worker    for source_dir in source_dirs:
168*288bf522SAndroid Build Coastguard Worker        if not source_dir.is_dir():
169*288bf522SAndroid Build Coastguard Worker            continue
170*288bf522SAndroid Build Coastguard Worker        for src_path in source_dir.iterdir():
171*288bf522SAndroid Build Coastguard Worker            dest_path = testdata_dir / src_path.name
172*288bf522SAndroid Build Coastguard Worker            if dest_path.exists():
173*288bf522SAndroid Build Coastguard Worker                continue
174*288bf522SAndroid Build Coastguard Worker            if src_path.is_file():
175*288bf522SAndroid Build Coastguard Worker                shutil.copyfile(src_path, dest_path)
176*288bf522SAndroid Build Coastguard Worker            elif src_path.is_dir():
177*288bf522SAndroid Build Coastguard Worker                shutil.copytree(src_path, dest_path)
178*288bf522SAndroid Build Coastguard Worker
179*288bf522SAndroid Build Coastguard Worker
180*288bf522SAndroid Build Coastguard Workerdef run_tests(tests: List[str]) -> bool:
181*288bf522SAndroid Build Coastguard Worker    argv = [sys.argv[0]] + tests
182*288bf522SAndroid Build Coastguard Worker    test_runner = unittest.TextTestRunner(stream=TestHelper.log_fh, verbosity=0)
183*288bf522SAndroid Build Coastguard Worker    test_program = unittest.main(argv=argv, testRunner=test_runner,
184*288bf522SAndroid Build Coastguard Worker                                 exit=False, verbosity=0, module='test.do_test')
185*288bf522SAndroid Build Coastguard Worker    return test_program.result.wasSuccessful()
186*288bf522SAndroid Build Coastguard Worker
187*288bf522SAndroid Build Coastguard Worker
188*288bf522SAndroid Build Coastguard Workerdef test_process_entry(tests: List[str], test_options: List[str], conn: mp.connection.Connection):
189*288bf522SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser()
190*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--browser', action='store_true')
191*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--device', help='android device serial number')
192*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--ndk-path', type=extant_dir)
193*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--testdata-dir', type=extant_dir)
194*288bf522SAndroid Build Coastguard Worker    parser.add_argument('--test-dir', help='directory to store test results')
195*288bf522SAndroid Build Coastguard Worker    args = parser.parse_args(test_options)
196*288bf522SAndroid Build Coastguard Worker
197*288bf522SAndroid Build Coastguard Worker    TestHelper.init(args.test_dir, args.testdata_dir,
198*288bf522SAndroid Build Coastguard Worker                    args.browser, args.ndk_path, args.device, conn)
199*288bf522SAndroid Build Coastguard Worker    run_tests(tests)
200*288bf522SAndroid Build Coastguard Worker
201*288bf522SAndroid Build Coastguard Worker
202*288bf522SAndroid Build Coastguard Worker@dataclass
203*288bf522SAndroid Build Coastguard Workerclass Device:
204*288bf522SAndroid Build Coastguard Worker    name: str
205*288bf522SAndroid Build Coastguard Worker    serial_number: str
206*288bf522SAndroid Build Coastguard Worker
207*288bf522SAndroid Build Coastguard Worker
208*288bf522SAndroid Build Coastguard Worker@dataclass
209*288bf522SAndroid Build Coastguard Workerclass TestResult:
210*288bf522SAndroid Build Coastguard Worker    try_time: int
211*288bf522SAndroid Build Coastguard Worker    status: str
212*288bf522SAndroid Build Coastguard Worker    duration: str
213*288bf522SAndroid Build Coastguard Worker
214*288bf522SAndroid Build Coastguard Worker    def __str__(self) -> str:
215*288bf522SAndroid Build Coastguard Worker        s = self.status
216*288bf522SAndroid Build Coastguard Worker        if s == 'FAILED':
217*288bf522SAndroid Build Coastguard Worker            s += f' (at try_time {self.try_time})'
218*288bf522SAndroid Build Coastguard Worker        s += f' {self.duration}'
219*288bf522SAndroid Build Coastguard Worker        return s
220*288bf522SAndroid Build Coastguard Worker
221*288bf522SAndroid Build Coastguard Worker
222*288bf522SAndroid Build Coastguard Workerclass TestProcess:
223*288bf522SAndroid Build Coastguard Worker    """ Create a test process to run selected tests on a device. """
224*288bf522SAndroid Build Coastguard Worker
225*288bf522SAndroid Build Coastguard Worker    TEST_MAX_TRY_TIME = 10
226*288bf522SAndroid Build Coastguard Worker    TEST_TIMEOUT_IN_SEC = 10 * 60
227*288bf522SAndroid Build Coastguard Worker
228*288bf522SAndroid Build Coastguard Worker    def __init__(
229*288bf522SAndroid Build Coastguard Worker            self, test_type: str, tests: List[str],
230*288bf522SAndroid Build Coastguard Worker            device: Optional[Device],
231*288bf522SAndroid Build Coastguard Worker            repeat_index: int,
232*288bf522SAndroid Build Coastguard Worker            test_options: List[str]):
233*288bf522SAndroid Build Coastguard Worker        self.test_type = test_type
234*288bf522SAndroid Build Coastguard Worker        self.tests = tests
235*288bf522SAndroid Build Coastguard Worker        self.device = device
236*288bf522SAndroid Build Coastguard Worker        self.repeat_index = repeat_index
237*288bf522SAndroid Build Coastguard Worker        self.test_options = test_options
238*288bf522SAndroid Build Coastguard Worker        self.try_time = 1
239*288bf522SAndroid Build Coastguard Worker        self.test_results: Dict[str, TestResult] = {}
240*288bf522SAndroid Build Coastguard Worker        self.parent_conn: Optional[mp.connection.Connection] = None
241*288bf522SAndroid Build Coastguard Worker        self.proc: Optional[mp.Process] = None
242*288bf522SAndroid Build Coastguard Worker        self.last_update_time = 0.0
243*288bf522SAndroid Build Coastguard Worker        self._start_test_process()
244*288bf522SAndroid Build Coastguard Worker
245*288bf522SAndroid Build Coastguard Worker    def _start_test_process(self):
246*288bf522SAndroid Build Coastguard Worker        unfinished_tests = [test for test in self.tests if test not in self.test_results]
247*288bf522SAndroid Build Coastguard Worker        self.parent_conn, child_conn = mp.Pipe(duplex=False)
248*288bf522SAndroid Build Coastguard Worker        test_options = self.test_options[:]
249*288bf522SAndroid Build Coastguard Worker        test_options += ['--test-dir', str(self.test_dir)]
250*288bf522SAndroid Build Coastguard Worker        if self.device:
251*288bf522SAndroid Build Coastguard Worker            test_options += ['--device', self.device.serial_number]
252*288bf522SAndroid Build Coastguard Worker        self.proc = mp.Process(target=test_process_entry, args=(
253*288bf522SAndroid Build Coastguard Worker            unfinished_tests, test_options, child_conn))
254*288bf522SAndroid Build Coastguard Worker        self.proc.start()
255*288bf522SAndroid Build Coastguard Worker        self.last_update_time = time.time()
256*288bf522SAndroid Build Coastguard Worker
257*288bf522SAndroid Build Coastguard Worker    @property
258*288bf522SAndroid Build Coastguard Worker    def name(self) -> str:
259*288bf522SAndroid Build Coastguard Worker        name = self.test_type
260*288bf522SAndroid Build Coastguard Worker        if self.device:
261*288bf522SAndroid Build Coastguard Worker            name += '_' + self.device.name
262*288bf522SAndroid Build Coastguard Worker        name += '_repeat_%d' % self.repeat_index
263*288bf522SAndroid Build Coastguard Worker        return name
264*288bf522SAndroid Build Coastguard Worker
265*288bf522SAndroid Build Coastguard Worker    @property
266*288bf522SAndroid Build Coastguard Worker    def test_dir(self) -> Path:
267*288bf522SAndroid Build Coastguard Worker        """ Directory to run the tests. """
268*288bf522SAndroid Build Coastguard Worker        return Path.cwd() / (self.name + '_try_%d' % self.try_time)
269*288bf522SAndroid Build Coastguard Worker
270*288bf522SAndroid Build Coastguard Worker    @property
271*288bf522SAndroid Build Coastguard Worker    def alive(self) -> bool:
272*288bf522SAndroid Build Coastguard Worker        """ Return if the test process is alive. """
273*288bf522SAndroid Build Coastguard Worker        return self.proc.is_alive()
274*288bf522SAndroid Build Coastguard Worker
275*288bf522SAndroid Build Coastguard Worker    @property
276*288bf522SAndroid Build Coastguard Worker    def finished(self) -> bool:
277*288bf522SAndroid Build Coastguard Worker        """ Return if all tests are finished. """
278*288bf522SAndroid Build Coastguard Worker        return len(self.test_results) == len(self.tests)
279*288bf522SAndroid Build Coastguard Worker
280*288bf522SAndroid Build Coastguard Worker    def check_update(self):
281*288bf522SAndroid Build Coastguard Worker        """ Check if there is any test update. """
282*288bf522SAndroid Build Coastguard Worker        try:
283*288bf522SAndroid Build Coastguard Worker            while self.parent_conn.poll():
284*288bf522SAndroid Build Coastguard Worker                msg = self.parent_conn.recv()
285*288bf522SAndroid Build Coastguard Worker                self._process_msg(msg)
286*288bf522SAndroid Build Coastguard Worker                self.last_update_time = time.time()
287*288bf522SAndroid Build Coastguard Worker        except (EOFError, BrokenPipeError) as e:
288*288bf522SAndroid Build Coastguard Worker            pass
289*288bf522SAndroid Build Coastguard Worker        if time.time() - self.last_update_time > TestProcess.TEST_TIMEOUT_IN_SEC:
290*288bf522SAndroid Build Coastguard Worker            self.proc.terminate()
291*288bf522SAndroid Build Coastguard Worker
292*288bf522SAndroid Build Coastguard Worker    def _process_msg(self, msg: str):
293*288bf522SAndroid Build Coastguard Worker        test_name, test_success, test_duration = msg.split()
294*288bf522SAndroid Build Coastguard Worker        self.test_results[test_name] = TestResult(self.try_time, test_success, test_duration)
295*288bf522SAndroid Build Coastguard Worker
296*288bf522SAndroid Build Coastguard Worker    def join(self):
297*288bf522SAndroid Build Coastguard Worker        self.proc.join()
298*288bf522SAndroid Build Coastguard Worker
299*288bf522SAndroid Build Coastguard Worker    def restart(self) -> bool:
300*288bf522SAndroid Build Coastguard Worker        """ Create a new test process to run unfinished tests. """
301*288bf522SAndroid Build Coastguard Worker        if self.finished:
302*288bf522SAndroid Build Coastguard Worker            return False
303*288bf522SAndroid Build Coastguard Worker        if self.try_time == self.TEST_MAX_TRY_TIME:
304*288bf522SAndroid Build Coastguard Worker            """ Exceed max try time. So mark left tests as failed. """
305*288bf522SAndroid Build Coastguard Worker            for test in self.tests:
306*288bf522SAndroid Build Coastguard Worker                if test not in self.test_results:
307*288bf522SAndroid Build Coastguard Worker                    test_duration = '%.3fs' % (time.time() - self.last_update_time)
308*288bf522SAndroid Build Coastguard Worker                    self.test_results[test] = TestResult(self.try_time, 'FAILED', test_duration)
309*288bf522SAndroid Build Coastguard Worker            return False
310*288bf522SAndroid Build Coastguard Worker
311*288bf522SAndroid Build Coastguard Worker        self.try_time += 1
312*288bf522SAndroid Build Coastguard Worker        self._start_test_process()
313*288bf522SAndroid Build Coastguard Worker        return True
314*288bf522SAndroid Build Coastguard Worker
315*288bf522SAndroid Build Coastguard Worker
316*288bf522SAndroid Build Coastguard Workerclass ProgressBar:
317*288bf522SAndroid Build Coastguard Worker    def __init__(self, total_count: int):
318*288bf522SAndroid Build Coastguard Worker        self.total_bar = tqdm(
319*288bf522SAndroid Build Coastguard Worker            total=total_count, desc='test progress', ascii=' ##',
320*288bf522SAndroid Build Coastguard Worker            bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}, {rate_fmt}", position=0)
321*288bf522SAndroid Build Coastguard Worker        self.test_process_bars: Dict[str, tqdm] = {}
322*288bf522SAndroid Build Coastguard Worker
323*288bf522SAndroid Build Coastguard Worker    def update(self, test_proc: TestProcess):
324*288bf522SAndroid Build Coastguard Worker        if test_proc.name not in self.test_process_bars:
325*288bf522SAndroid Build Coastguard Worker            bar = tqdm(total=len(test_proc.tests),
326*288bf522SAndroid Build Coastguard Worker                       desc=test_proc.name, ascii=' ##',
327*288bf522SAndroid Build Coastguard Worker                       bar_format="{l_bar}{bar} | {n_fmt}/{total_fmt} [{elapsed}]")
328*288bf522SAndroid Build Coastguard Worker            self.test_process_bars[test_proc.name] = bar
329*288bf522SAndroid Build Coastguard Worker        else:
330*288bf522SAndroid Build Coastguard Worker            bar = self.test_process_bars[test_proc.name]
331*288bf522SAndroid Build Coastguard Worker
332*288bf522SAndroid Build Coastguard Worker        add = len(test_proc.test_results) - bar.n
333*288bf522SAndroid Build Coastguard Worker        if add:
334*288bf522SAndroid Build Coastguard Worker            bar.update(add)
335*288bf522SAndroid Build Coastguard Worker            self.total_bar.update(add)
336*288bf522SAndroid Build Coastguard Worker
337*288bf522SAndroid Build Coastguard Worker    def end_test_proc(self, test_proc: TestProcess):
338*288bf522SAndroid Build Coastguard Worker        if test_proc.name in self.test_process_bars:
339*288bf522SAndroid Build Coastguard Worker            self.test_process_bars[test_proc.name].close()
340*288bf522SAndroid Build Coastguard Worker            del self.test_process_bars[test_proc.name]
341*288bf522SAndroid Build Coastguard Worker
342*288bf522SAndroid Build Coastguard Worker    def end_tests(self):
343*288bf522SAndroid Build Coastguard Worker        for bar in self.test_process_bars.values():
344*288bf522SAndroid Build Coastguard Worker            bar.close()
345*288bf522SAndroid Build Coastguard Worker        self.total_bar.close()
346*288bf522SAndroid Build Coastguard Worker
347*288bf522SAndroid Build Coastguard Worker
348*288bf522SAndroid Build Coastguard Workerclass TestSummary:
349*288bf522SAndroid Build Coastguard Worker    def __init__(
350*288bf522SAndroid Build Coastguard Worker            self, devices: List[Device],
351*288bf522SAndroid Build Coastguard Worker            device_tests: List[str],
352*288bf522SAndroid Build Coastguard Worker            repeat_count: int, host_tests: List[str]):
353*288bf522SAndroid Build Coastguard Worker        self.results: Dict[Tuple[str, str], Optional[TestResult]] = {}
354*288bf522SAndroid Build Coastguard Worker        for test in device_tests:
355*288bf522SAndroid Build Coastguard Worker            for device in devices:
356*288bf522SAndroid Build Coastguard Worker                for repeat_index in range(1, repeat_count + 1):
357*288bf522SAndroid Build Coastguard Worker                    self.results[(test, '%s_repeat_%d' % (device.name, repeat_index))] = None
358*288bf522SAndroid Build Coastguard Worker        for test in host_tests:
359*288bf522SAndroid Build Coastguard Worker            self.results[(test, 'host')] = None
360*288bf522SAndroid Build Coastguard Worker        self.write_summary()
361*288bf522SAndroid Build Coastguard Worker
362*288bf522SAndroid Build Coastguard Worker    @property
363*288bf522SAndroid Build Coastguard Worker    def test_count(self) -> int:
364*288bf522SAndroid Build Coastguard Worker        return len(self.results)
365*288bf522SAndroid Build Coastguard Worker
366*288bf522SAndroid Build Coastguard Worker    @property
367*288bf522SAndroid Build Coastguard Worker    def failed_test_count(self) -> int:
368*288bf522SAndroid Build Coastguard Worker        count = 0
369*288bf522SAndroid Build Coastguard Worker        for result in self.results.values():
370*288bf522SAndroid Build Coastguard Worker            if result is None or result.status == 'FAILED':
371*288bf522SAndroid Build Coastguard Worker                count += 1
372*288bf522SAndroid Build Coastguard Worker        return count
373*288bf522SAndroid Build Coastguard Worker
374*288bf522SAndroid Build Coastguard Worker    def update(self, test_proc: TestProcess):
375*288bf522SAndroid Build Coastguard Worker        if test_proc.device:
376*288bf522SAndroid Build Coastguard Worker            test_env = '%s_repeat_%d' % (test_proc.device.name, test_proc.repeat_index)
377*288bf522SAndroid Build Coastguard Worker        else:
378*288bf522SAndroid Build Coastguard Worker            test_env = 'host'
379*288bf522SAndroid Build Coastguard Worker        has_update = False
380*288bf522SAndroid Build Coastguard Worker        for test, result in test_proc.test_results.items():
381*288bf522SAndroid Build Coastguard Worker            key = (test, test_env)
382*288bf522SAndroid Build Coastguard Worker            if self.results[key] != result:
383*288bf522SAndroid Build Coastguard Worker                self.results[key] = result
384*288bf522SAndroid Build Coastguard Worker                has_update = True
385*288bf522SAndroid Build Coastguard Worker        if has_update:
386*288bf522SAndroid Build Coastguard Worker            self.write_summary()
387*288bf522SAndroid Build Coastguard Worker
388*288bf522SAndroid Build Coastguard Worker    def write_summary(self):
389*288bf522SAndroid Build Coastguard Worker        with open('test_summary.txt', 'w') as fh, \
390*288bf522SAndroid Build Coastguard Worker                open('failed_test_summary.txt', 'w') as failed_fh:
391*288bf522SAndroid Build Coastguard Worker            for key in sorted(self.results.keys()):
392*288bf522SAndroid Build Coastguard Worker                test_name, test_env = key
393*288bf522SAndroid Build Coastguard Worker                result = self.results[key]
394*288bf522SAndroid Build Coastguard Worker                message = f'{test_name}    {test_env}    {result}'
395*288bf522SAndroid Build Coastguard Worker                print(message, file=fh)
396*288bf522SAndroid Build Coastguard Worker                if not result or result.status == 'FAILED':
397*288bf522SAndroid Build Coastguard Worker                    print(message, file=failed_fh)
398*288bf522SAndroid Build Coastguard Worker
399*288bf522SAndroid Build Coastguard Worker
400*288bf522SAndroid Build Coastguard Workerclass TestManager:
401*288bf522SAndroid Build Coastguard Worker    """ Create test processes, monitor their status and log test progresses. """
402*288bf522SAndroid Build Coastguard Worker
403*288bf522SAndroid Build Coastguard Worker    def __init__(self, args: argparse.Namespace):
404*288bf522SAndroid Build Coastguard Worker        self.repeat_count = args.repeat
405*288bf522SAndroid Build Coastguard Worker        self.test_options = self._build_test_options(args)
406*288bf522SAndroid Build Coastguard Worker        self.devices = self._build_test_devices(args)
407*288bf522SAndroid Build Coastguard Worker        self.progress_bar: Optional[ProgressBar] = None
408*288bf522SAndroid Build Coastguard Worker        self.test_summary: Optional[TestSummary] = None
409*288bf522SAndroid Build Coastguard Worker
410*288bf522SAndroid Build Coastguard Worker    def _build_test_devices(self, args: argparse.Namespace) -> List[Device]:
411*288bf522SAndroid Build Coastguard Worker        devices = []
412*288bf522SAndroid Build Coastguard Worker        if args.device:
413*288bf522SAndroid Build Coastguard Worker            for s in args.device:
414*288bf522SAndroid Build Coastguard Worker                name, serial_number = s.split(':', 1)
415*288bf522SAndroid Build Coastguard Worker                devices.append(Device(name, serial_number))
416*288bf522SAndroid Build Coastguard Worker        else:
417*288bf522SAndroid Build Coastguard Worker            devices.append(Device('default', ''))
418*288bf522SAndroid Build Coastguard Worker        return devices
419*288bf522SAndroid Build Coastguard Worker
420*288bf522SAndroid Build Coastguard Worker    def _build_test_options(self, args: argparse.Namespace) -> List[str]:
421*288bf522SAndroid Build Coastguard Worker        test_options: List[str] = []
422*288bf522SAndroid Build Coastguard Worker        if args.browser:
423*288bf522SAndroid Build Coastguard Worker            test_options.append('--browser')
424*288bf522SAndroid Build Coastguard Worker        if args.ndk_path:
425*288bf522SAndroid Build Coastguard Worker            test_options += ['--ndk-path', args.ndk_path]
426*288bf522SAndroid Build Coastguard Worker        testdata_dir = Path('testdata').resolve()
427*288bf522SAndroid Build Coastguard Worker        test_options += ['--testdata-dir', str(testdata_dir)]
428*288bf522SAndroid Build Coastguard Worker        return test_options
429*288bf522SAndroid Build Coastguard Worker
430*288bf522SAndroid Build Coastguard Worker    def run_all_tests(self, tests: List[str]):
431*288bf522SAndroid Build Coastguard Worker        device_tests = []
432*288bf522SAndroid Build Coastguard Worker        device_serialized_tests = []
433*288bf522SAndroid Build Coastguard Worker        host_tests = []
434*288bf522SAndroid Build Coastguard Worker        for test in tests:
435*288bf522SAndroid Build Coastguard Worker            test_type = get_test_type(test)
436*288bf522SAndroid Build Coastguard Worker            assert test_type, f'No test type for test {test}'
437*288bf522SAndroid Build Coastguard Worker            if test_type == 'device_test':
438*288bf522SAndroid Build Coastguard Worker                device_tests.append(test)
439*288bf522SAndroid Build Coastguard Worker            if test_type == 'device_serialized_test':
440*288bf522SAndroid Build Coastguard Worker                device_serialized_tests.append(test)
441*288bf522SAndroid Build Coastguard Worker            if test_type == 'host_test':
442*288bf522SAndroid Build Coastguard Worker                host_tests.append(test)
443*288bf522SAndroid Build Coastguard Worker        total_test_count = (len(device_tests) + len(device_serialized_tests)
444*288bf522SAndroid Build Coastguard Worker                            ) * len(self.devices) * self.repeat_count + len(host_tests)
445*288bf522SAndroid Build Coastguard Worker        self.progress_bar = ProgressBar(total_test_count)
446*288bf522SAndroid Build Coastguard Worker        self.test_summary = TestSummary(self.devices, device_tests + device_serialized_tests,
447*288bf522SAndroid Build Coastguard Worker                                        self.repeat_count, host_tests)
448*288bf522SAndroid Build Coastguard Worker        if device_tests:
449*288bf522SAndroid Build Coastguard Worker            self.run_device_tests(device_tests)
450*288bf522SAndroid Build Coastguard Worker        if device_serialized_tests:
451*288bf522SAndroid Build Coastguard Worker            self.run_device_serialized_tests(device_serialized_tests)
452*288bf522SAndroid Build Coastguard Worker        if host_tests:
453*288bf522SAndroid Build Coastguard Worker            self.run_host_tests(host_tests)
454*288bf522SAndroid Build Coastguard Worker        self.progress_bar.end_tests()
455*288bf522SAndroid Build Coastguard Worker        self.progress_bar = None
456*288bf522SAndroid Build Coastguard Worker
457*288bf522SAndroid Build Coastguard Worker    def run_device_tests(self, tests: List[str]):
458*288bf522SAndroid Build Coastguard Worker        """ Tests can run in parallel on different devices. """
459*288bf522SAndroid Build Coastguard Worker        test_procs: List[TestProcess] = []
460*288bf522SAndroid Build Coastguard Worker        for device in self.devices:
461*288bf522SAndroid Build Coastguard Worker            test_procs.append(TestProcess('device_test', tests, device, 1, self.test_options))
462*288bf522SAndroid Build Coastguard Worker        self.wait_for_test_results(test_procs, self.repeat_count)
463*288bf522SAndroid Build Coastguard Worker
464*288bf522SAndroid Build Coastguard Worker    def run_device_serialized_tests(self, tests: List[str]):
465*288bf522SAndroid Build Coastguard Worker        """ Tests run on each device in order. """
466*288bf522SAndroid Build Coastguard Worker        for device in self.devices:
467*288bf522SAndroid Build Coastguard Worker            test_proc = TestProcess('device_serialized_test', tests, device, 1, self.test_options)
468*288bf522SAndroid Build Coastguard Worker            self.wait_for_test_results([test_proc], self.repeat_count)
469*288bf522SAndroid Build Coastguard Worker
470*288bf522SAndroid Build Coastguard Worker    def run_host_tests(self, tests: List[str]):
471*288bf522SAndroid Build Coastguard Worker        """ Tests run only once on host. """
472*288bf522SAndroid Build Coastguard Worker        test_proc = TestProcess('host_tests', tests, None, 1, self.test_options)
473*288bf522SAndroid Build Coastguard Worker        self.wait_for_test_results([test_proc], 1)
474*288bf522SAndroid Build Coastguard Worker
475*288bf522SAndroid Build Coastguard Worker    def wait_for_test_results(self, test_procs: List[TestProcess], repeat_count: int):
476*288bf522SAndroid Build Coastguard Worker        test_count = sum(len(test_proc.tests) for test_proc in test_procs)
477*288bf522SAndroid Build Coastguard Worker        while test_procs:
478*288bf522SAndroid Build Coastguard Worker            dead_procs: List[TestProcess] = []
479*288bf522SAndroid Build Coastguard Worker            # Check update.
480*288bf522SAndroid Build Coastguard Worker            for test_proc in test_procs:
481*288bf522SAndroid Build Coastguard Worker                if not test_proc.alive:
482*288bf522SAndroid Build Coastguard Worker                    dead_procs.append(test_proc)
483*288bf522SAndroid Build Coastguard Worker                test_proc.check_update()
484*288bf522SAndroid Build Coastguard Worker                self.progress_bar.update(test_proc)
485*288bf522SAndroid Build Coastguard Worker                self.test_summary.update(test_proc)
486*288bf522SAndroid Build Coastguard Worker
487*288bf522SAndroid Build Coastguard Worker            # Process dead procs.
488*288bf522SAndroid Build Coastguard Worker            for test_proc in dead_procs:
489*288bf522SAndroid Build Coastguard Worker                test_proc.join()
490*288bf522SAndroid Build Coastguard Worker                if not test_proc.finished:
491*288bf522SAndroid Build Coastguard Worker                    if test_proc.restart():
492*288bf522SAndroid Build Coastguard Worker                        continue
493*288bf522SAndroid Build Coastguard Worker                    else:
494*288bf522SAndroid Build Coastguard Worker                        self.progress_bar.update(test_proc)
495*288bf522SAndroid Build Coastguard Worker                        self.test_summary.update(test_proc)
496*288bf522SAndroid Build Coastguard Worker                self.progress_bar.end_test_proc(test_proc)
497*288bf522SAndroid Build Coastguard Worker                test_procs.remove(test_proc)
498*288bf522SAndroid Build Coastguard Worker                if test_proc.repeat_index < repeat_count:
499*288bf522SAndroid Build Coastguard Worker                    test_procs.append(
500*288bf522SAndroid Build Coastguard Worker                        TestProcess(test_proc.test_type, test_proc.tests, test_proc.device,
501*288bf522SAndroid Build Coastguard Worker                                    test_proc.repeat_index + 1, test_proc.test_options))
502*288bf522SAndroid Build Coastguard Worker            time.sleep(0.1)
503*288bf522SAndroid Build Coastguard Worker        return True
504*288bf522SAndroid Build Coastguard Worker
505*288bf522SAndroid Build Coastguard Worker
506*288bf522SAndroid Build Coastguard Workerdef run_tests_in_child_process(tests: List[str], args: argparse.Namespace) -> bool:
507*288bf522SAndroid Build Coastguard Worker    """ run tests in child processes, read test results through a pipe. """
508*288bf522SAndroid Build Coastguard Worker    mp.set_start_method('spawn')  # to be consistent on darwin, linux, windows
509*288bf522SAndroid Build Coastguard Worker    test_manager = TestManager(args)
510*288bf522SAndroid Build Coastguard Worker    test_manager.run_all_tests(tests)
511*288bf522SAndroid Build Coastguard Worker
512*288bf522SAndroid Build Coastguard Worker    total_test_count = test_manager.test_summary.test_count
513*288bf522SAndroid Build Coastguard Worker    failed_test_count = test_manager.test_summary.failed_test_count
514*288bf522SAndroid Build Coastguard Worker    if failed_test_count == 0:
515*288bf522SAndroid Build Coastguard Worker        print('All tests passed!')
516*288bf522SAndroid Build Coastguard Worker        return True
517*288bf522SAndroid Build Coastguard Worker    print('%d of %d tests failed. See %s/failed_test_summary.txt for details.' %
518*288bf522SAndroid Build Coastguard Worker          (failed_test_count, total_test_count, args.test_dir))
519*288bf522SAndroid Build Coastguard Worker    return False
520*288bf522SAndroid Build Coastguard Worker
521*288bf522SAndroid Build Coastguard Worker
522*288bf522SAndroid Build Coastguard Workerdef main() -> bool:
523*288bf522SAndroid Build Coastguard Worker    args = get_args()
524*288bf522SAndroid Build Coastguard Worker    tests = get_host_tests() if args.only_host_test else get_all_tests()
525*288bf522SAndroid Build Coastguard Worker    tests = get_filtered_tests(tests, args.test_from, args.pattern)
526*288bf522SAndroid Build Coastguard Worker
527*288bf522SAndroid Build Coastguard Worker    if args.list_tests:
528*288bf522SAndroid Build Coastguard Worker        print('\n'.join(tests))
529*288bf522SAndroid Build Coastguard Worker        return True
530*288bf522SAndroid Build Coastguard Worker
531*288bf522SAndroid Build Coastguard Worker    test_dir = Path(args.test_dir).resolve()
532*288bf522SAndroid Build Coastguard Worker    remove(test_dir)
533*288bf522SAndroid Build Coastguard Worker    test_dir.mkdir(parents=True)
534*288bf522SAndroid Build Coastguard Worker    # Switch to the test dir.
535*288bf522SAndroid Build Coastguard Worker    os.chdir(test_dir)
536*288bf522SAndroid Build Coastguard Worker    build_testdata(Path('testdata'))
537*288bf522SAndroid Build Coastguard Worker    return run_tests_in_child_process(tests, args)
538