1# Copyright 2022 The ANGLE Project Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import contextlib 6import glob 7import json 8import logging 9import os 10import posixpath 11import random 12import subprocess 13import tarfile 14import tempfile 15import time 16 17import angle_path_util 18 19 20def _ApkPath(suite_name): 21 return os.path.join('%s_apk' % suite_name, '%s-debug.apk' % suite_name) 22 23 24def ApkFileExists(suite_name): 25 return os.path.exists(_ApkPath(suite_name)) 26 27 28def _Run(cmd): 29 logging.debug('Executing command: %s', cmd) 30 startupinfo = None 31 if hasattr(subprocess, 'STARTUPINFO'): 32 # Prevent console window popping up on Windows 33 startupinfo = subprocess.STARTUPINFO() 34 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 35 startupinfo.wShowWindow = subprocess.SW_HIDE 36 output = subprocess.check_output(cmd, startupinfo=startupinfo) 37 return output 38 39 40class Adb(object): 41 42 def __init__(self, adb_path=None): 43 if not adb_path: 44 adb_path = os.path.join(angle_path_util.ANGLE_ROOT_DIR, 45 'third_party/android_sdk/public/platform-tools/adb') 46 self._adb_path = adb_path 47 48 def Run(self, args): 49 return _Run([self._adb_path] + args) 50 51 def Shell(self, cmd): 52 return _Run([self._adb_path, 'shell', cmd]) 53 54 55def _GetAdbRoot(adb): 56 adb.Run(['root']) 57 58 for _ in range(20): 59 time.sleep(0.5) 60 try: 61 id_out = adb.Shell(adb_path, 'id').decode('ascii') 62 if 'uid=0(root)' in id_out: 63 return 64 except Exception: 65 continue 66 raise Exception("adb root failed") 67 68 69def _ReadDeviceFile(adb, device_path): 70 out_wc = adb.Shell('cat %s | wc -c' % device_path) 71 expected_size = int(out_wc.decode('ascii').strip()) 72 out = adb.Run(['exec-out', 'cat %s' % device_path]) 73 assert len(out) == expected_size, 'exec-out mismatch' 74 return out 75 76 77def _RemoveDeviceFile(adb, device_path): 78 adb.Shell('rm -f ' + device_path + ' || true') # ignore errors 79 80 81def _AddRestrictedTracesJson(adb): 82 adb.Shell('mkdir -p /sdcard/chromium_tests_root/') 83 84 def add(tar, fn): 85 assert (fn.startswith('../../')) 86 tar.add(fn, arcname=fn.replace('../../', '')) 87 88 with _TempLocalFile() as tempfile_path: 89 with tarfile.open(tempfile_path, 'w', format=tarfile.GNU_FORMAT) as tar: 90 for f in glob.glob('../../src/tests/restricted_traces/*/*.json', recursive=True): 91 add(tar, f) 92 add(tar, '../../src/tests/restricted_traces/restricted_traces.json') 93 adb.Run(['push', tempfile_path, '/sdcard/chromium_tests_root/t.tar']) 94 95 adb.Shell('r=/sdcard/chromium_tests_root; tar -xf $r/t.tar -C $r/ && rm $r/t.tar') 96 97 98def PrepareTestSuite(adb, suite_name): 99 apk_path = _ApkPath(suite_name) 100 logging.info('Installing apk path=%s size=%s' % (apk_path, os.path.getsize(apk_path))) 101 102 adb.Run(['install', '-r', '-d', apk_path]) 103 104 permissions = [ 105 'android.permission.CAMERA', 'android.permission.CHANGE_CONFIGURATION', 106 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.RECORD_AUDIO', 107 'android.permission.WRITE_EXTERNAL_STORAGE' 108 ] 109 adb.Shell('p=com.android.angle.test;' 110 'for q in %s;do pm grant "$p" "$q";done;' % ' '.join(permissions)) 111 112 if suite_name == 'angle_perftests': 113 _AddRestrictedTracesJson(adb) 114 115 116def PrepareRestrictedTraces(adb, traces): 117 start = time.time() 118 total_size = 0 119 for trace in traces: 120 path_from_root = 'src/tests/restricted_traces/' + trace + '/' + trace + '.angledata.gz' 121 local_path = '../../' + path_from_root 122 total_size += os.path.getsize(local_path) 123 adb.Run(['push', local_path, '/sdcard/chromium_tests_root/' + path_from_root]) 124 125 logging.info('Pushed %d trace files (%.1fMB) in %.1fs', len(traces), total_size / 1e6, 126 time.time() - start) 127 128 129def _RandomHex(): 130 return hex(random.randint(0, 2**64))[2:] 131 132 133@contextlib.contextmanager 134def _TempDeviceDir(adb): 135 path = '/sdcard/Download/temp_dir-%s' % _RandomHex() 136 adb.Shell('mkdir -p ' + path) 137 try: 138 yield path 139 finally: 140 adb.Shell('rm -rf ' + path) 141 142 143@contextlib.contextmanager 144def _TempDeviceFile(adb): 145 path = '/sdcard/Download/temp_file-%s' % _RandomHex() 146 try: 147 yield path 148 finally: 149 adb.Shell('rm -f ' + path) 150 151 152@contextlib.contextmanager 153def _TempLocalFile(): 154 fd, path = tempfile.mkstemp() 155 os.close(fd) 156 try: 157 yield path 158 finally: 159 os.remove(path) 160 161 162def _RunInstrumentation(adb, flags): 163 with _TempDeviceFile(adb) as temp_device_file: 164 cmd = ' '.join([ 165 'p=com.android.angle.test;', 166 'ntr=org.chromium.native_test.NativeTestInstrumentationTestRunner;', 167 'am instrument -w', 168 '-e $ntr.NativeTestActivity "$p".AngleUnitTestActivity', 169 '-e $ntr.ShardNanoTimeout 2400000000000', 170 '-e org.chromium.native_test.NativeTest.CommandLineFlags "%s"' % ' '.join(flags), 171 '-e $ntr.StdoutFile ' + temp_device_file, 172 '"$p"/org.chromium.build.gtest_apk.NativeTestInstrumentationTestRunner', 173 ]) 174 175 adb.Shell(cmd) 176 return _ReadDeviceFile(adb, temp_device_file) 177 178 179def AngleSystemInfo(adb, args): 180 PrepareTestSuite(adb, 'angle_system_info_test') 181 182 with _TempDeviceDir(adb) as temp_dir: 183 _RunInstrumentation(adb, args + ['--render-test-output-dir=' + temp_dir]) 184 output_file = posixpath.join(temp_dir, 'angle_system_info.json') 185 return json.loads(_ReadDeviceFile(adb, output_file)) 186 187 188def ListTests(adb): 189 out_lines = _RunInstrumentation(adb, ["--list-tests"]).decode('ascii').split('\n') 190 191 start = out_lines.index('Tests list:') 192 end = out_lines.index('End tests list.') 193 return out_lines[start + 1:end] 194 195 196def _PullDir(adb, device_dir, local_dir): 197 files = adb.Shell('ls -1 %s' % device_dir).decode('ascii').split('\n') 198 for f in files: 199 f = f.strip() 200 if f: 201 adb.Run(['pull', posixpath.join(device_dir, f), posixpath.join(local_dir, f)]) 202 203 204def RunTests(adb, test_suite, args, stdoutfile, output_dir): 205 with _TempDeviceDir(adb) as temp_dir: 206 output = _RunInstrumentation(adb, args + ['--render-test-output-dir=' + temp_dir]) 207 with open(stdoutfile, 'wb') as f: 208 f.write(output) 209 logging.info(output.decode()) 210 _PullDir(adb, temp_dir, output_dir) 211