• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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