1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*288bf522SAndroid Build Coastguard Worker# 3*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2021 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"""test_utils.py: utils for testing. 18*288bf522SAndroid Build Coastguard Worker""" 19*288bf522SAndroid Build Coastguard Worker 20*288bf522SAndroid Build Coastguard Workerimport logging 21*288bf522SAndroid Build Coastguard Workerfrom multiprocessing.connection import Connection 22*288bf522SAndroid Build Coastguard Workerimport os 23*288bf522SAndroid Build Coastguard Workerfrom pathlib import Path 24*288bf522SAndroid Build Coastguard Workerimport re 25*288bf522SAndroid Build Coastguard Workerimport shutil 26*288bf522SAndroid Build Coastguard Workerimport sys 27*288bf522SAndroid Build Coastguard Workerimport subprocess 28*288bf522SAndroid Build Coastguard Workerimport time 29*288bf522SAndroid Build Coastguard Workerfrom typing import List, Optional, Tuple, Union 30*288bf522SAndroid Build Coastguard Workerimport unittest 31*288bf522SAndroid Build Coastguard Worker 32*288bf522SAndroid Build Coastguard Workerfrom simpleperf_utils import remove, get_script_dir, AdbHelper, is_windows, bytes_to_str 33*288bf522SAndroid Build Coastguard Worker 34*288bf522SAndroid Build Coastguard WorkerINFERNO_SCRIPT = str(Path(__file__).parents[1] / ('inferno.bat' if is_windows() else 'inferno.sh')) 35*288bf522SAndroid Build Coastguard Worker 36*288bf522SAndroid Build Coastguard Worker 37*288bf522SAndroid Build Coastguard Workerclass TestHelper: 38*288bf522SAndroid Build Coastguard Worker """ Keep global test options. """ 39*288bf522SAndroid Build Coastguard Worker 40*288bf522SAndroid Build Coastguard Worker @classmethod 41*288bf522SAndroid Build Coastguard Worker def init( 42*288bf522SAndroid Build Coastguard Worker cls, test_dir: str, testdata_dir: str, use_browser: bool, ndk_path: Optional[str], 43*288bf522SAndroid Build Coastguard Worker device_serial_number: Optional[str], 44*288bf522SAndroid Build Coastguard Worker progress_conn: Optional[Connection]): 45*288bf522SAndroid Build Coastguard Worker """ 46*288bf522SAndroid Build Coastguard Worker When device_serial_number is None, no Android device is used. 47*288bf522SAndroid Build Coastguard Worker When device_serial_number is '', use the default Android device. 48*288bf522SAndroid Build Coastguard Worker When device_serial_number is not empty, select Android device by serial number. 49*288bf522SAndroid Build Coastguard Worker """ 50*288bf522SAndroid Build Coastguard Worker cls.script_dir = Path(__file__).resolve().parents[1] 51*288bf522SAndroid Build Coastguard Worker cls.test_base_dir = Path(test_dir).resolve() 52*288bf522SAndroid Build Coastguard Worker cls.test_base_dir.mkdir(parents=True, exist_ok=True) 53*288bf522SAndroid Build Coastguard Worker cls.testdata_dir = Path(testdata_dir).resolve() 54*288bf522SAndroid Build Coastguard Worker cls.browser_option = [] if use_browser else ['--no_browser'] 55*288bf522SAndroid Build Coastguard Worker cls.ndk_path = ndk_path 56*288bf522SAndroid Build Coastguard Worker cls.progress_conn = progress_conn 57*288bf522SAndroid Build Coastguard Worker 58*288bf522SAndroid Build Coastguard Worker # Logs can come from multiple processes. So use append mode to avoid overwrite. 59*288bf522SAndroid Build Coastguard Worker cls.log_fh = open(cls.test_base_dir / 'test.log', 'a') 60*288bf522SAndroid Build Coastguard Worker logging.getLogger().handlers.clear() 61*288bf522SAndroid Build Coastguard Worker logging.getLogger().addHandler(logging.StreamHandler(cls.log_fh)) 62*288bf522SAndroid Build Coastguard Worker os.close(sys.stderr.fileno()) 63*288bf522SAndroid Build Coastguard Worker os.dup2(cls.log_fh.fileno(), sys.stderr.fileno()) 64*288bf522SAndroid Build Coastguard Worker 65*288bf522SAndroid Build Coastguard Worker if device_serial_number is not None: 66*288bf522SAndroid Build Coastguard Worker if device_serial_number: 67*288bf522SAndroid Build Coastguard Worker os.environ['ANDROID_SERIAL'] = device_serial_number 68*288bf522SAndroid Build Coastguard Worker cls.adb = AdbHelper(enable_switch_to_root=True) 69*288bf522SAndroid Build Coastguard Worker cls.android_version = cls.adb.get_android_version() 70*288bf522SAndroid Build Coastguard Worker cls.device_features = None 71*288bf522SAndroid Build Coastguard Worker 72*288bf522SAndroid Build Coastguard Worker @classmethod 73*288bf522SAndroid Build Coastguard Worker def log(cls, s: str): 74*288bf522SAndroid Build Coastguard Worker cls.log_fh.write(s + '\n') 75*288bf522SAndroid Build Coastguard Worker # Child processes can also write to log file, so flush it immediately to keep the order. 76*288bf522SAndroid Build Coastguard Worker cls.log_fh.flush() 77*288bf522SAndroid Build Coastguard Worker 78*288bf522SAndroid Build Coastguard Worker @classmethod 79*288bf522SAndroid Build Coastguard Worker def testdata_path(cls, testdata_name: str) -> str: 80*288bf522SAndroid Build Coastguard Worker """ Return the path of a test data. """ 81*288bf522SAndroid Build Coastguard Worker return str(cls.testdata_dir / testdata_name) 82*288bf522SAndroid Build Coastguard Worker 83*288bf522SAndroid Build Coastguard Worker @classmethod 84*288bf522SAndroid Build Coastguard Worker def get_test_dir(cls, test_name: str) -> Path: 85*288bf522SAndroid Build Coastguard Worker """ Return the dir to run a test. """ 86*288bf522SAndroid Build Coastguard Worker return cls.test_base_dir / test_name 87*288bf522SAndroid Build Coastguard Worker 88*288bf522SAndroid Build Coastguard Worker @classmethod 89*288bf522SAndroid Build Coastguard Worker def script_path(cls, script_name: str) -> str: 90*288bf522SAndroid Build Coastguard Worker """ Return the dir of python scripts. """ 91*288bf522SAndroid Build Coastguard Worker return str(cls.script_dir / script_name) 92*288bf522SAndroid Build Coastguard Worker 93*288bf522SAndroid Build Coastguard Worker @classmethod 94*288bf522SAndroid Build Coastguard Worker def get_device_features(cls): 95*288bf522SAndroid Build Coastguard Worker if cls.device_features is None: 96*288bf522SAndroid Build Coastguard Worker args = [sys.executable, cls.script_path( 97*288bf522SAndroid Build Coastguard Worker 'run_simpleperf_on_device.py'), 'list', '--show-features'] 98*288bf522SAndroid Build Coastguard Worker output = subprocess.check_output(args, stderr=TestHelper.log_fh) 99*288bf522SAndroid Build Coastguard Worker output = bytes_to_str(output) 100*288bf522SAndroid Build Coastguard Worker cls.device_features = output.split() 101*288bf522SAndroid Build Coastguard Worker return cls.device_features 102*288bf522SAndroid Build Coastguard Worker 103*288bf522SAndroid Build Coastguard Worker @classmethod 104*288bf522SAndroid Build Coastguard Worker def is_trace_offcpu_supported(cls): 105*288bf522SAndroid Build Coastguard Worker return 'trace-offcpu' in cls.get_device_features() 106*288bf522SAndroid Build Coastguard Worker 107*288bf522SAndroid Build Coastguard Worker @classmethod 108*288bf522SAndroid Build Coastguard Worker def get_32bit_abi(cls): 109*288bf522SAndroid Build Coastguard Worker return cls.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0] 110*288bf522SAndroid Build Coastguard Worker 111*288bf522SAndroid Build Coastguard Worker @classmethod 112*288bf522SAndroid Build Coastguard Worker def get_kernel_version(cls) -> Tuple[int]: 113*288bf522SAndroid Build Coastguard Worker output = cls.adb.check_run_and_return_output(['shell', 'uname', '-r']) 114*288bf522SAndroid Build Coastguard Worker m = re.search(r'^(\d+)\.(\d+)', output) 115*288bf522SAndroid Build Coastguard Worker assert m 116*288bf522SAndroid Build Coastguard Worker return (int(m.group(1)), int(m.group(2))) 117*288bf522SAndroid Build Coastguard Worker 118*288bf522SAndroid Build Coastguard Worker @classmethod 119*288bf522SAndroid Build Coastguard Worker def write_progress(cls, progress: str): 120*288bf522SAndroid Build Coastguard Worker if cls.progress_conn: 121*288bf522SAndroid Build Coastguard Worker cls.progress_conn.send(progress) 122*288bf522SAndroid Build Coastguard Worker 123*288bf522SAndroid Build Coastguard Worker 124*288bf522SAndroid Build Coastguard Workerclass TestBase(unittest.TestCase): 125*288bf522SAndroid Build Coastguard Worker def setUp(self): 126*288bf522SAndroid Build Coastguard Worker """ Run each test in a separate dir. """ 127*288bf522SAndroid Build Coastguard Worker self.test_dir = TestHelper.get_test_dir( 128*288bf522SAndroid Build Coastguard Worker '%s.%s' % (self.__class__.__name__, self._testMethodName)) 129*288bf522SAndroid Build Coastguard Worker self.test_dir.mkdir() 130*288bf522SAndroid Build Coastguard Worker os.chdir(self.test_dir) 131*288bf522SAndroid Build Coastguard Worker TestHelper.log('begin test %s.%s' % (self.__class__.__name__, self._testMethodName)) 132*288bf522SAndroid Build Coastguard Worker 133*288bf522SAndroid Build Coastguard Worker def run(self, result=None): 134*288bf522SAndroid Build Coastguard Worker start_time = time.time() 135*288bf522SAndroid Build Coastguard Worker ret = super(TestBase, self).run(result) 136*288bf522SAndroid Build Coastguard Worker if result.errors and result.errors[-1][0] == self: 137*288bf522SAndroid Build Coastguard Worker status = 'FAILED' 138*288bf522SAndroid Build Coastguard Worker err_info = result.errors[-1][1] 139*288bf522SAndroid Build Coastguard Worker elif result.failures and result.failures[-1][0] == self: 140*288bf522SAndroid Build Coastguard Worker status = 'FAILED' 141*288bf522SAndroid Build Coastguard Worker err_info = result.failures[-1][1] 142*288bf522SAndroid Build Coastguard Worker elif result.skipped and result.skipped[-1][0] == self: 143*288bf522SAndroid Build Coastguard Worker status = 'SKIPPED' 144*288bf522SAndroid Build Coastguard Worker else: 145*288bf522SAndroid Build Coastguard Worker status = 'OK' 146*288bf522SAndroid Build Coastguard Worker 147*288bf522SAndroid Build Coastguard Worker time_taken = time.time() - start_time 148*288bf522SAndroid Build Coastguard Worker TestHelper.log( 149*288bf522SAndroid Build Coastguard Worker 'end test %s.%s %s (%.3fs)' % 150*288bf522SAndroid Build Coastguard Worker (self.__class__.__name__, self._testMethodName, status, time_taken)) 151*288bf522SAndroid Build Coastguard Worker if status == 'FAILED': 152*288bf522SAndroid Build Coastguard Worker TestHelper.log(err_info) 153*288bf522SAndroid Build Coastguard Worker 154*288bf522SAndroid Build Coastguard Worker # Remove test data for passed tests to save space. 155*288bf522SAndroid Build Coastguard Worker if status == 'OK': 156*288bf522SAndroid Build Coastguard Worker remove(self.test_dir) 157*288bf522SAndroid Build Coastguard Worker TestHelper.write_progress( 158*288bf522SAndroid Build Coastguard Worker '%s.%s %s %.3fs' % 159*288bf522SAndroid Build Coastguard Worker (self.__class__.__name__, self._testMethodName, status, time_taken)) 160*288bf522SAndroid Build Coastguard Worker return ret 161*288bf522SAndroid Build Coastguard Worker 162*288bf522SAndroid Build Coastguard Worker def run_cmd(self, args: List[str], return_output=False, drop_output=True) -> str: 163*288bf522SAndroid Build Coastguard Worker if args[0] == 'report_html.py' or args[0] == INFERNO_SCRIPT: 164*288bf522SAndroid Build Coastguard Worker args += TestHelper.browser_option 165*288bf522SAndroid Build Coastguard Worker if TestHelper.ndk_path: 166*288bf522SAndroid Build Coastguard Worker if args[0] in ['app_profiler.py', 'binary_cache_builder.py', 'pprof_proto_generator.py', 167*288bf522SAndroid Build Coastguard Worker 'report_html.py', 'annotate.py']: 168*288bf522SAndroid Build Coastguard Worker args += ['--ndk_path', TestHelper.ndk_path] 169*288bf522SAndroid Build Coastguard Worker if args[0].endswith('.py'): 170*288bf522SAndroid Build Coastguard Worker args = [sys.executable, TestHelper.script_path(args[0])] + args[1:] 171*288bf522SAndroid Build Coastguard Worker use_shell = args[0].endswith('.bat') 172*288bf522SAndroid Build Coastguard Worker try: 173*288bf522SAndroid Build Coastguard Worker if return_output: 174*288bf522SAndroid Build Coastguard Worker stdout_fd = subprocess.PIPE 175*288bf522SAndroid Build Coastguard Worker drop_output = False 176*288bf522SAndroid Build Coastguard Worker elif drop_output: 177*288bf522SAndroid Build Coastguard Worker stdout_fd = subprocess.DEVNULL 178*288bf522SAndroid Build Coastguard Worker else: 179*288bf522SAndroid Build Coastguard Worker stdout_fd = None 180*288bf522SAndroid Build Coastguard Worker 181*288bf522SAndroid Build Coastguard Worker subproc = subprocess.Popen(args, stdout=stdout_fd, 182*288bf522SAndroid Build Coastguard Worker stderr=TestHelper.log_fh, shell=use_shell) 183*288bf522SAndroid Build Coastguard Worker stdout_data, _ = subproc.communicate() 184*288bf522SAndroid Build Coastguard Worker output_data = bytes_to_str(stdout_data) 185*288bf522SAndroid Build Coastguard Worker returncode = subproc.returncode 186*288bf522SAndroid Build Coastguard Worker 187*288bf522SAndroid Build Coastguard Worker except OSError: 188*288bf522SAndroid Build Coastguard Worker returncode = None 189*288bf522SAndroid Build Coastguard Worker self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args) 190*288bf522SAndroid Build Coastguard Worker if return_output: 191*288bf522SAndroid Build Coastguard Worker return output_data 192*288bf522SAndroid Build Coastguard Worker return '' 193*288bf522SAndroid Build Coastguard Worker 194*288bf522SAndroid Build Coastguard Worker def check_strings_in_file(self, filename, strings: List[Union[str, re.Pattern]]): 195*288bf522SAndroid Build Coastguard Worker self.check_exist(filename=filename) 196*288bf522SAndroid Build Coastguard Worker with open(filename, 'r') as fh: 197*288bf522SAndroid Build Coastguard Worker self.check_strings_in_content(fh.read(), strings) 198*288bf522SAndroid Build Coastguard Worker 199*288bf522SAndroid Build Coastguard Worker def check_exist(self, filename=None, dirname=None): 200*288bf522SAndroid Build Coastguard Worker if filename: 201*288bf522SAndroid Build Coastguard Worker self.assertTrue(os.path.isfile(filename), filename) 202*288bf522SAndroid Build Coastguard Worker if dirname: 203*288bf522SAndroid Build Coastguard Worker self.assertTrue(os.path.isdir(dirname), dirname) 204*288bf522SAndroid Build Coastguard Worker 205*288bf522SAndroid Build Coastguard Worker def check_strings_in_content(self, content: str, strings: List[Union[str, re.Pattern]]): 206*288bf522SAndroid Build Coastguard Worker fulfilled = [] 207*288bf522SAndroid Build Coastguard Worker for s in strings: 208*288bf522SAndroid Build Coastguard Worker if isinstance(s, re.Pattern): 209*288bf522SAndroid Build Coastguard Worker fulfilled.append(s.search(content)) 210*288bf522SAndroid Build Coastguard Worker else: 211*288bf522SAndroid Build Coastguard Worker fulfilled.append(s in content) 212*288bf522SAndroid Build Coastguard Worker self.check_fulfilled_entries(fulfilled, strings) 213*288bf522SAndroid Build Coastguard Worker 214*288bf522SAndroid Build Coastguard Worker def check_fulfilled_entries(self, fulfilled, entries): 215*288bf522SAndroid Build Coastguard Worker failed_entries = [] 216*288bf522SAndroid Build Coastguard Worker for ok, entry in zip(fulfilled, entries): 217*288bf522SAndroid Build Coastguard Worker if not ok: 218*288bf522SAndroid Build Coastguard Worker failed_entries.append(entry) 219*288bf522SAndroid Build Coastguard Worker 220*288bf522SAndroid Build Coastguard Worker if failed_entries: 221*288bf522SAndroid Build Coastguard Worker self.fail('failed in below entries: %s' % (failed_entries,)) 222