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