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 functools 7import glob 8import hashlib 9import json 10import logging 11import os 12import pathlib 13import platform 14import posixpath 15import random 16import re 17import subprocess 18import sys 19import tarfile 20import tempfile 21import threading 22import time 23import zipfile 24 25import angle_path_util 26 27 28ANGLE_TRACE_TEST_SUITE = 'angle_trace_tests' 29 30 31class _Global(object): 32 initialized = False 33 is_android = False 34 current_suite = None 35 current_user = None 36 external_storage = None 37 has_adb_root = False 38 traces_outside_of_apk = False 39 base_dir = None 40 temp_dir = None 41 use_run_as = True 42 43 @classmethod 44 def IsMultiUser(cls): 45 assert cls.current_user != None, "Call _GetCurrentUser before using IsMultiUser" 46 return cls.current_user != '0' 47 48def _ApkPath(suite_name): 49 return os.path.join('%s_apk' % suite_name, '%s-debug.apk' % suite_name) 50 51 52def _RemovePrefix(str, prefix): 53 assert str.startswith(prefix), 'Expected prefix %s, got: %s' % (prefix, str) 54 return str[len(prefix):] 55 56 57def _InitializeAndroid(apk_path): 58 # Pull a few pieces of data with a single adb trip 59 shell_id, su_path, current_user, data_permissions = _AdbShell( 60 'id -u; which su || echo noroot; am get-current-user; stat --format %a /data').decode( 61 ).strip().split('\n') 62 63 # Populate globals with those results 64 _Global.has_adb_root = _GetAdbRoot(shell_id, su_path) 65 _Global.current_user = _GetCurrentUser(current_user) 66 _Global.use_run_as = _GetRunAs(data_permissions) 67 68 # Storage location varies by user 69 _Global.external_storage = '/storage/emulated/' + _Global.current_user + '/chromium_tests_root/' 70 71 # We use the app's home directory for storing several things 72 _Global.base_dir = '/data/user/' + _Global.current_user + '/com.android.angle.test/' 73 74 if _Global.has_adb_root: 75 # /data/local/tmp/ is not writable by apps.. So use the app path 76 _Global.temp_dir = _Global.base_dir + 'tmp/' 77 # Additionally, if we're not the default user, we need to use the app's dir for external storage 78 if _Global.IsMultiUser(): 79 # TODO(b/361388557): Switch to a content provider for this, i.e. `content write` 80 logging.warning( 81 '\n\n!!!!! Using app dir for external storage, may not work with chromium scripts, may require `setenforce 0` !!!!!\n' 82 ) 83 _Global.external_storage = _Global.base_dir + 'chromium_tests_root/' 84 else: 85 # /sdcard/ is slow (see https://crrev.com/c/3615081 for details) 86 # logging will be fully-buffered, can be truncated on crashes 87 _Global.temp_dir = '/storage/emulated/' + _Global.current_user + '/' 88 89 logging.debug('Temp dir: %s', _Global.temp_dir) 90 logging.debug('External storage: %s', _Global.external_storage) 91 92 with zipfile.ZipFile(apk_path) as zf: 93 apk_so_libs = [posixpath.basename(f) for f in zf.namelist() if f.endswith('.so')] 94 95 # When traces are outside of the apk this lib is also outside 96 interpreter_so_lib = 'libangle_trace_interpreter.so' 97 _Global.traces_outside_of_apk = interpreter_so_lib not in apk_so_libs 98 99 if logging.getLogger().isEnabledFor(logging.DEBUG): 100 logging.debug(_AdbShell('df -h').decode()) 101 102 103def Initialize(suite_name): 104 if _Global.initialized: 105 return 106 107 apk_path = _ApkPath(suite_name) 108 if os.path.exists(apk_path): 109 _Global.is_android = True 110 _InitializeAndroid(apk_path) 111 112 _Global.initialized = True 113 114 115def IsAndroid(): 116 assert _Global.initialized, 'Initialize not called' 117 return _Global.is_android 118 119 120def _EnsureTestSuite(suite_name): 121 assert IsAndroid() 122 123 if _Global.current_suite != suite_name: 124 PrepareTestSuite(suite_name) 125 _Global.current_suite = suite_name 126 127 128def _Run(cmd): 129 logging.debug('Executing command: %s', cmd) 130 startupinfo = None 131 if hasattr(subprocess, 'STARTUPINFO'): 132 # Prevent console window popping up on Windows 133 startupinfo = subprocess.STARTUPINFO() 134 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 135 startupinfo.wShowWindow = subprocess.SW_HIDE 136 output = subprocess.check_output(cmd, startupinfo=startupinfo) 137 return output 138 139 140@functools.lru_cache() 141def FindAdb(): 142 if platform.system() == 'Windows': 143 adb = 'adb.exe' # from PATH 144 else: 145 platform_tools = ( 146 pathlib.Path(angle_path_util.ANGLE_ROOT_DIR) / 'third_party' / 'android_sdk' / 147 'public' / 'platform-tools') 148 adb = str(platform_tools / 'adb') if platform_tools.exists() else 'adb' 149 150 adb_info = ', '.join(subprocess.check_output([adb, '--version']).decode().strip().split('\n')) 151 logging.info('adb --version: %s', adb_info) 152 return adb 153 154 155def _AdbRun(args): 156 return _Run([FindAdb()] + args) 157 158 159def _AdbShell(cmd): 160 output = _Run([FindAdb(), 'shell', cmd]) 161 if platform.system() == 'Windows': 162 return output.replace(b'\r\n', b'\n') 163 return output 164 165 166def _AdbShellWithRunAs(cmd): 167 if _Global.use_run_as: 168 cmd = "run-as com.android.angle.test sh -c '{cmd}'".format(cmd=cmd) 169 return _AdbShell(cmd) 170 171 172def _GetAdbRoot(shell_id, su_path): 173 if int(shell_id) == 0: 174 logging.info('adb already got root') 175 return True 176 177 if su_path == 'noroot': 178 logging.warning('adb root not available on this device') 179 return False 180 181 logging.info('Getting adb root (may take a few seconds)') 182 _AdbRun(['root']) 183 for _ in range(20): # `adb root` restarts adbd which can take quite a few seconds 184 time.sleep(0.5) 185 id_out = _AdbShell('id -u').decode('ascii').strip() 186 if id_out == '0': 187 logging.info('adb root succeeded') 188 return True 189 190 # Device has "su" but we couldn't get adb root. Something is wrong. 191 raise Exception('Failed to get adb root') 192 193 194def _GetRunAs(data_permissions): 195 # Determine run-as usage 196 if data_permissions.endswith('7'): 197 # run-as broken due to "/data readable or writable by others" 198 logging.warning('run-as not available due to /data permissions') 199 return False 200 201 if _Global.IsMultiUser(): 202 # run-as is failing is the presence of multiple users 203 logging.warning('Disabling run-as for non-default user') 204 return False 205 206 return True 207 208 209def _GetCurrentUser(current_user): 210 # Ensure current user is clean 211 assert current_user.isnumeric(), current_user 212 logging.debug('Current user: %s', current_user) 213 return current_user 214 215 216def _ReadDeviceFile(device_path): 217 with _TempLocalFile() as tempfile_path: 218 _AdbRun(['pull', device_path, tempfile_path]) 219 with open(tempfile_path, 'rb') as f: 220 return f.read() 221 222 223def _RemoveDeviceFile(device_path): 224 _AdbShell('rm -f ' + device_path + ' || true') # ignore errors 225 226 227def _MakeTar(path, patterns): 228 with _TempLocalFile() as tempfile_path: 229 with tarfile.open(tempfile_path, 'w', format=tarfile.GNU_FORMAT) as tar: 230 for p in patterns: 231 for f in glob.glob(p, recursive=True): 232 tar.add(f, arcname=f.replace('../../', '')) 233 _AdbRun(['push', tempfile_path, path]) 234 235 236def _AddRestrictedTracesJson(): 237 _MakeTar(_Global.external_storage + 't.tar', [ 238 '../../src/tests/restricted_traces/*/*.json', 239 'gen/trace_list.json', 240 ]) 241 _AdbShell( 242 'r=' + _Global.external_storage + 243 '; tar --no-same-permissions --no-same-owner -xf $r/t.tar -C $r/ && rm $r/t.tar && chmod -R o+r $r/' 244 ) 245 246 247def _AddDeqpFiles(suite_name): 248 patterns = [ 249 '../../third_party/VK-GL-CTS/src/external/openglcts/data/gl_cts/data/mustpass/*/*/main/*.txt', 250 '../../src/tests/deqp_support/*.txt' 251 ] 252 if '_gles2_' in suite_name: 253 patterns.append('gen/vk_gl_cts_data/data/gles2/**') 254 elif '_gles3_' in suite_name: 255 patterns.append('gen/vk_gl_cts_data/data/gles3/**') 256 patterns.append('gen/vk_gl_cts_data/data/gl_cts/data/gles3/**') 257 elif '_gles31_' in suite_name: 258 patterns.append('gen/vk_gl_cts_data/data/gles31/**') 259 patterns.append('gen/vk_gl_cts_data/data/gl_cts/data/gles31/**') 260 elif '_gles32_' in suite_name: 261 patterns.append('gen/vk_gl_cts_data/data/gl_cts/data/gles32/**') 262 else: 263 # Harness crashes if vk_gl_cts_data/data dir doesn't exist, so add a file 264 patterns.append('gen/vk_gl_cts_data/data/gles2/data/brick.png') 265 266 _MakeTar(_Global.external_storage + 'deqp.tar', patterns) 267 _AdbShell( 268 'r=' + _Global.external_storage + 269 '; tar --no-same-permissions --no-same-owner -xf $r/deqp.tar -C $r/ && rm $r/deqp.tar && chmod -R o+r $r/' 270 ) 271 272 273def _GetDeviceApkPath(): 274 pm_path = _AdbShell('pm path com.android.angle.test || true').decode().strip() 275 if not pm_path: 276 logging.debug('No installed path found for com.android.angle.test') 277 return None 278 device_apk_path = _RemovePrefix(pm_path, 'package:') 279 logging.debug('Device APK path is %s' % device_apk_path) 280 return device_apk_path 281 282 283def _LocalFileHash(local_path, gz_tail_size): 284 h = hashlib.sha256() 285 with open(local_path, 'rb') as f: 286 if local_path.endswith('.gz'): 287 # equivalent of tail -c {gz_tail_size} 288 offset = os.path.getsize(local_path) - gz_tail_size 289 if offset > 0: 290 f.seek(offset) 291 for data in iter(lambda: f.read(65536), b''): 292 h.update(data) 293 return h.hexdigest() 294 295 296def _CompareHashes(local_path, device_path): 297 # The last 8 bytes of gzip contain CRC-32 and the initial file size and the preceding 298 # bytes should be affected by changes in the middle if we happen to run into a collision 299 gz_tail_size = 4096 300 301 if local_path.endswith('.gz'): 302 cmd = 'test -f {path} && tail -c {gz_tail_size} {path} | sha256sum -b || true'.format( 303 path=device_path, gz_tail_size=gz_tail_size) 304 else: 305 cmd = 'test -f {path} && sha256sum -b {path} || true'.format(path=device_path) 306 307 if _Global.use_run_as and device_path.startswith('/data'): 308 # Use run-as for files that reside on /data, which aren't accessible without root 309 cmd = "run-as com.android.angle.test sh -c '{cmd}'".format(cmd=cmd) 310 311 device_hash = _AdbShell(cmd).decode().strip() 312 if not device_hash: 313 logging.debug('_CompareHashes: File not found on device') 314 return False # file not on device 315 316 return _LocalFileHash(local_path, gz_tail_size) == device_hash 317 318 319def _CheckSameApkInstalled(apk_path): 320 device_apk_path = _GetDeviceApkPath() 321 322 try: 323 if device_apk_path and _CompareHashes(apk_path, device_apk_path): 324 return True 325 except subprocess.CalledProcessError as e: 326 # non-debuggable test apk installed on device breaks run-as 327 logging.warning('_CompareHashes of apk failed: %s' % e) 328 329 return False 330 331 332def PrepareTestSuite(suite_name): 333 apk_path = _ApkPath(suite_name) 334 335 if _CheckSameApkInstalled(apk_path): 336 logging.info('Skipping APK install because host and device hashes match') 337 else: 338 logging.info('Installing apk path=%s size=%s' % (apk_path, os.path.getsize(apk_path))) 339 _AdbRun(['install', '-r', '-d', apk_path]) 340 341 permissions = [ 342 'android.permission.CAMERA', 'android.permission.CHANGE_CONFIGURATION', 343 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.RECORD_AUDIO', 344 'android.permission.WRITE_EXTERNAL_STORAGE' 345 ] 346 _AdbShell('for q in %s;do pm grant com.android.angle.test "$q";done;' % 347 (' '.join(permissions))) 348 349 _AdbShell('appops set com.android.angle.test MANAGE_EXTERNAL_STORAGE allow || true') 350 351 _AdbShell('mkdir -p ' + _Global.external_storage) 352 _AdbShell('mkdir -p %s' % _Global.temp_dir) 353 354 if suite_name == ANGLE_TRACE_TEST_SUITE: 355 _AddRestrictedTracesJson() 356 _AdbRun([ 357 'push', '../../src/tests/perf_tests/angle_trace_tests_expectations.txt', 358 _Global.external_storage + 'src/tests/perf_tests/angle_trace_tests_expectations.txt' 359 ]) 360 361 if '_deqp_' in suite_name: 362 _AddDeqpFiles(suite_name) 363 364 if suite_name == 'angle_end2end_tests': 365 _AdbRun([ 366 'push', '../../src/tests/angle_end2end_tests_expectations.txt', 367 _Global.external_storage + 'src/tests/angle_end2end_tests_expectations.txt' 368 ]) 369 370 371def PrepareRestrictedTraces(traces): 372 start = time.time() 373 total_size = 0 374 skipped = 0 375 376 # In order to get files to the app's home directory and loadable as libraries, we must first 377 # push them to tmp on the device. We then use `run-as` which allows copying files from tmp. 378 # Note that `mv` is not allowed with `run-as`. This means there will briefly be two copies 379 # of the trace on the device, so keep that in mind as space becomes a problem in the future. 380 app_tmp_path = '/data/local/tmp/angle_traces/' 381 382 if _Global.use_run_as: 383 _AdbShell('mkdir -p ' + app_tmp_path + 384 ' && run-as com.android.angle.test mkdir -p angle_traces') 385 else: 386 _AdbShell('mkdir -p ' + app_tmp_path + ' ' + _Global.base_dir + 'angle_traces/') 387 388 def _HashesMatch(local_path, device_path): 389 nonlocal total_size, skipped 390 if _CompareHashes(local_path, device_path): 391 skipped += 1 392 return True 393 else: 394 total_size += os.path.getsize(local_path) 395 return False 396 397 def _Push(local_path, path_from_root): 398 device_path = _Global.external_storage + path_from_root 399 if not _HashesMatch(local_path, device_path): 400 _AdbRun(['push', local_path, device_path]) 401 402 def _PushLibToAppDir(lib_name): 403 local_path = lib_name 404 if not os.path.exists(local_path): 405 print('Error: missing library: ' + local_path) 406 print('Is angle_restricted_traces set in gn args?') # b/294861737 407 sys.exit(1) 408 409 device_path = _Global.base_dir + 'angle_traces/' + lib_name 410 if _HashesMatch(local_path, device_path): 411 return 412 413 if _Global.use_run_as: 414 tmp_path = posixpath.join(app_tmp_path, lib_name) 415 logging.debug('_PushToAppDir: Pushing %s to %s' % (local_path, tmp_path)) 416 try: 417 _AdbRun(['push', local_path, tmp_path]) 418 _AdbShell('run-as com.android.angle.test cp ' + tmp_path + ' ./angle_traces/') 419 _AdbShell('rm ' + tmp_path) 420 finally: 421 _RemoveDeviceFile(tmp_path) 422 else: 423 _AdbRun(['push', local_path, _Global.base_dir + 'angle_traces/']) 424 425 # Set up each trace 426 for idx, trace in enumerate(sorted(traces)): 427 logging.info('Syncing %s trace (%d/%d)', trace, idx + 1, len(traces)) 428 429 path_from_root = 'src/tests/restricted_traces/' + trace + '/' + trace + '.angledata.gz' 430 _Push('../../' + path_from_root, path_from_root) 431 432 if _Global.traces_outside_of_apk: 433 lib_name = 'libangle_restricted_traces_' + trace + '.so' 434 _PushLibToAppDir(lib_name) 435 436 tracegz = 'gen/tracegz_' + trace + '.gz' 437 if os.path.exists(tracegz): # Requires angle_enable_tracegz 438 _Push(tracegz, tracegz) 439 440 # Push one additional file when running outside the APK 441 if _Global.traces_outside_of_apk: 442 _PushLibToAppDir('libangle_trace_interpreter.so') 443 444 logging.info('Synced files for %d traces (%.1fMB, %d files already ok) in %.1fs', len(traces), 445 total_size / 1e6, skipped, 446 time.time() - start) 447 448 449def _RandomHex(): 450 return hex(random.randint(0, 2**64))[2:] 451 452 453@contextlib.contextmanager 454def _TempDeviceDir(): 455 path = posixpath.join(_Global.temp_dir, 'temp_dir-%s' % _RandomHex()) 456 _AdbShell('mkdir -p ' + path) 457 try: 458 yield path 459 finally: 460 _AdbShell('rm -rf ' + path) 461 462 463@contextlib.contextmanager 464def _TempDeviceFile(): 465 path = posixpath.join(_Global.temp_dir, 'temp_file-%s' % _RandomHex()) 466 try: 467 yield path 468 finally: 469 _AdbShell('rm -f ' + path) 470 471 472@contextlib.contextmanager 473def _TempLocalFile(): 474 fd, path = tempfile.mkstemp() 475 os.close(fd) 476 try: 477 yield path 478 finally: 479 os.remove(path) 480 481 482def _SetCaptureProps(env, device_out_dir): 483 capture_var_map = { # src/libANGLE/capture/FrameCapture.cpp 484 'ANGLE_CAPTURE_ENABLED': 'debug.angle.capture.enabled', 485 'ANGLE_CAPTURE_FRAME_START': 'debug.angle.capture.frame_start', 486 'ANGLE_CAPTURE_FRAME_END': 'debug.angle.capture.frame_end', 487 'ANGLE_CAPTURE_TRIGGER': 'debug.angle.capture.trigger', 488 'ANGLE_CAPTURE_LABEL': 'debug.angle.capture.label', 489 'ANGLE_CAPTURE_COMPRESSION': 'debug.angle.capture.compression', 490 'ANGLE_CAPTURE_VALIDATION': 'debug.angle.capture.validation', 491 'ANGLE_CAPTURE_VALIDATION_EXPR': 'debug.angle.capture.validation_expr', 492 'ANGLE_CAPTURE_SOURCE_EXT': 'debug.angle.capture.source_ext', 493 'ANGLE_CAPTURE_SOURCE_SIZE': 'debug.angle.capture.source_size', 494 'ANGLE_CAPTURE_FORCE_SHADOW': 'debug.angle.capture.force_shadow', 495 } 496 empty_value = '""' 497 shell_cmds = [ 498 # out_dir is special because the corresponding env var is a host path not a device path 499 'setprop debug.angle.capture.out_dir ' + (device_out_dir or empty_value), 500 ] + [ 501 'setprop %s %s' % (v, env.get(k, empty_value)) for k, v in sorted(capture_var_map.items()) 502 ] 503 504 _AdbShell('\n'.join(shell_cmds)) 505 506 507def _RunInstrumentation(flags): 508 with _TempDeviceFile() as temp_device_file: 509 cmd = r''' 510am instrument --user {user} -w \ 511 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.StdoutFile {out} \ 512 -e org.chromium.native_test.NativeTest.CommandLineFlags "{flags}" \ 513 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardNanoTimeout "1000000000000000000" \ 514 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.NativeTestActivity \ 515 com.android.angle.test.AngleUnitTestActivity \ 516 com.android.angle.test/org.chromium.build.gtest_apk.NativeTestInstrumentationTestRunner 517 '''.format( 518 user=_Global.current_user, out=temp_device_file, flags=r' '.join(flags)).strip() 519 520 capture_out_dir = os.environ.get('ANGLE_CAPTURE_OUT_DIR') 521 if capture_out_dir: 522 assert os.path.isdir(capture_out_dir) 523 with _TempDeviceDir() as device_out_dir: 524 _SetCaptureProps(os.environ, device_out_dir) 525 try: 526 _AdbShell(cmd) 527 finally: 528 _SetCaptureProps({}, None) # reset 529 _PullDir(device_out_dir, capture_out_dir) 530 else: 531 _AdbShell(cmd) 532 return _ReadDeviceFile(temp_device_file) 533 534 535def AngleSystemInfo(args): 536 _EnsureTestSuite('angle_system_info_test') 537 538 with _TempDeviceDir() as temp_dir: 539 _RunInstrumentation(args + ['--render-test-output-dir=' + temp_dir]) 540 output_file = posixpath.join(temp_dir, 'angle_system_info.json') 541 return json.loads(_ReadDeviceFile(output_file)) 542 543 544def GetBuildFingerprint(): 545 return _AdbShell('getprop ro.build.fingerprint').decode('ascii').strip() 546 547 548def _PullDir(device_dir, local_dir): 549 files = _AdbShell('ls -1 %s' % device_dir).decode('ascii').split('\n') 550 for f in files: 551 f = f.strip() 552 if f: 553 _AdbRun(['pull', posixpath.join(device_dir, f), posixpath.join(local_dir, f)]) 554 555 556def _RemoveFlag(args, f): 557 matches = [a for a in args if a.startswith(f + '=')] 558 assert len(matches) <= 1 559 if matches: 560 original_value = matches[0].split('=')[1] 561 args.remove(matches[0]) 562 else: 563 original_value = None 564 565 return original_value 566 567 568def RunTests(test_suite, args, stdoutfile=None, log_output=True): 569 _EnsureTestSuite(test_suite) 570 571 args = args[:] 572 test_output_path = _RemoveFlag(args, '--isolated-script-test-output') 573 perf_output_path = _RemoveFlag(args, '--isolated-script-test-perf-output') 574 test_output_dir = _RemoveFlag(args, '--render-test-output-dir') 575 576 result = 0 577 output = b'' 578 output_json = {} 579 try: 580 with contextlib.ExitStack() as stack: 581 device_test_output_path = stack.enter_context(_TempDeviceFile()) 582 args.append('--isolated-script-test-output=' + device_test_output_path) 583 584 if perf_output_path: 585 device_perf_path = stack.enter_context(_TempDeviceFile()) 586 args.append('--isolated-script-test-perf-output=%s' % device_perf_path) 587 588 if test_output_dir: 589 assert os.path.isdir(test_output_dir), 'Dir does not exist: %s' % test_output_dir 590 device_output_dir = stack.enter_context(_TempDeviceDir()) 591 args.append('--render-test-output-dir=' + device_output_dir) 592 593 output = _RunInstrumentation(args) 594 595 if '--list-tests' in args: 596 # When listing tests, there may be no output file. We parse stdout anyways. 597 test_output = b'{"interrupted": false}' 598 else: 599 try: 600 test_output = _ReadDeviceFile(device_test_output_path) 601 except subprocess.CalledProcessError: 602 logging.error('Unable to read test json output. Stdout:\n%s', output.decode()) 603 result = 1 604 return result, output.decode(), None 605 606 if test_output_path: 607 with open(test_output_path, 'wb') as f: 608 f.write(test_output) 609 610 output_json = json.loads(test_output) 611 612 num_failures = output_json.get('num_failures_by_type', {}).get('FAIL', 0) 613 interrupted = output_json.get('interrupted', True) # Normally set to False 614 if num_failures != 0 or interrupted or output_json.get('is_unexpected', False): 615 logging.error('Tests failed: %s', test_output.decode()) 616 result = 1 617 618 if test_output_dir: 619 _PullDir(device_output_dir, test_output_dir) 620 621 if perf_output_path: 622 _AdbRun(['pull', device_perf_path, perf_output_path]) 623 624 if log_output or result: 625 logging.info(output.decode()) 626 627 if result: 628 logging.error('Tests failed, see stdout above') 629 630 if stdoutfile: 631 with open(stdoutfile, 'wb') as f: 632 f.write(output) 633 except Exception as e: 634 logging.exception(e) 635 result = 1 636 637 return result, output.decode(), output_json 638 639 640def GetTraceFromTestName(test_name): 641 if test_name.startswith('TraceTest.'): 642 return test_name[len('TraceTest.'):] 643 return None 644 645 646def GetTemps(): 647 temps = _AdbShell( 648 'cat /dev/thermal/tz-by-name/*_therm/temp 2>/dev/null || true').decode().split() 649 logging.debug('tz-by-name temps: %s' % ','.join(temps)) 650 651 temps_celsius = [] 652 for t in temps: 653 try: 654 temps_celsius.append(float(t) / 1e3) 655 except ValueError: 656 pass 657 658 return temps_celsius 659