xref: /aosp_15_r20/external/angle/src/tests/angle_android_test_runner.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#! /usr/bin/env vpython3
2#
3# Copyright 2023 The ANGLE Project Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# angle_android_test_runner.py:
8#   Runs (a subset of) ANGLE tests on Android using android_helper
9#   instead of chromium test_runner scripts. This script is integrated into
10#   gn builds via the android_test_runner_script var, which creates wrappers
11#   (out/<config>/angle_trace_tests) calling into this script
12#   with additional args like --output-directory=out/<config> etc
13
14import argparse
15import json
16import logging
17import os
18import pathlib
19import subprocess
20import sys
21
22PY_UTILS = str(pathlib.Path(__file__).resolve().parent / 'py_utils')
23if PY_UTILS not in sys.path:
24    os.stat(PY_UTILS) and sys.path.insert(0, PY_UTILS)
25import android_helper
26import angle_path_util
27import angle_test_util
28
29
30def AddCommonParserArgs(parser):
31    parser.add_argument(
32        '--suite',
33        help='Test suite to run.',
34        required=True,
35        choices=[
36            'angle_end2end_tests',
37            'angle_perftests',
38            'angle_trace_tests',
39            'angle_unittests',
40            # dEQP - grep \"angle_deqp infra/specs/gn_isolate_map.pyl
41            'angle_deqp_egl_tests',
42            'angle_deqp_gles2_tests',
43            'angle_deqp_gles31_rotate180_tests',
44            'angle_deqp_gles31_rotate270_tests',
45            'angle_deqp_gles31_rotate90_tests',
46            'angle_deqp_gles31_tests',
47            'angle_deqp_gles3_rotate180_tests',
48            'angle_deqp_gles3_rotate270_tests',
49            'angle_deqp_gles3_rotate90_tests',
50            'angle_deqp_gles3_tests',
51            'angle_deqp_khr_gles2_tests',
52            'angle_deqp_khr_gles31_tests',
53            'angle_deqp_khr_gles32_tests',
54            'angle_deqp_khr_gles3_tests',
55            'angle_deqp_khr_noctx_gles2_tests',
56            'angle_deqp_khr_noctx_gles32_tests',
57            'angle_deqp_khr_single_gles32_tests',
58        ])
59    parser.add_argument('-l', '--log', help='Logging level.', default='info')
60    parser.add_argument('--list-tests', help='List tests.', action='store_true')
61    parser.add_argument(
62        '-f',
63        '--filter',
64        '--isolated-script-test-filter',
65        '--gtest_filter',
66        type=str,
67        help='Test filter.')
68    parser.add_argument(
69        '--prepare-only',
70        help='Only prepare traces, but do not actually run them.',
71        action='store_true')
72
73
74def RunWithAngleTestRunner(args, extra_args):
75    angle_test_util.SetupLogging(args.log.upper())
76
77    android_helper.Initialize(args.suite)
78    assert android_helper.IsAndroid()
79
80    rc, output, _ = android_helper.RunTests(
81        args.suite, ['--list-tests', '--verbose'] + extra_args, log_output=False)
82    if rc != 0:
83        logging.fatal('Could not find test list from test output:\n%s' % output)
84        return rc
85
86    tests = angle_test_util.GetTestsFromOutput(output)
87    if not tests:
88        logging.fatal('Could not find test list from test output:\n%s' % output)
89        return 1
90
91    if args.filter:
92        tests = angle_test_util.FilterTests(tests, args.filter)
93
94    if args.list_tests:
95        print('\n'.join(['Tests list:'] + tests + ['End tests list.']))
96        return 0
97
98    if args.suite == 'angle_trace_tests':
99        traces = set(android_helper.GetTraceFromTestName(test) for test in tests)
100        android_helper.PrepareRestrictedTraces(traces)
101
102        if args.prepare_only:
103            print('Prepared traces: %s' % traces)
104            return 0
105
106    flags = ['--gtest_filter=' + args.filter] if args.filter else []
107    return android_helper.RunTests(args.suite, flags + extra_args)[0]
108
109
110def RunWithChromiumTestRunner():
111    # Import only here to avoid breaking trace bundles and other catapult imports
112    angle_path_util.AddDepsDirToPath('build/android')
113    import test_runner
114
115    # Workaround from test_runner's __main__
116    exit_code = test_runner.main()
117    if exit_code == test_runner.constants.INFRA_EXIT_CODE:
118        sys.stdout.flush()
119        sys.stderr.flush()
120        os._exit(exit_code)
121    return exit_code
122
123
124def CheckDeviceScreenState():
125    screen_state = subprocess.check_output(
126        [android_helper.FindAdb(), 'shell',
127         'dumpsys deviceidle | grep mScreen || true']).decode().split()
128    expected_screen_state = ['mScreenOn=true', 'mScreenLocked=false']
129    if not set(expected_screen_state).issubset(screen_state):
130        raise Exception('Unexpected device screen state: %s; expected: %s' %
131                        (screen_state, expected_screen_state))
132
133
134def main():
135    parser = argparse.ArgumentParser(add_help=False)
136    parser.add_argument('-h', '--help', action='store_true')
137    parser.add_argument('test_type', choices=['gtest'])
138    parser.add_argument('--output-directory', required=True)
139    parser.add_argument('--wrapper-script-args')
140    parser.add_argument('--runtime-deps-path')
141    AddCommonParserArgs(parser)
142
143    parser.add_argument(
144        '--angle-test-runner',
145        help='Use ANGLE test runner instead of Chromium/catapult',
146        action='store_true')
147
148    args, extra_args = parser.parse_known_args()
149
150    if not args.help:
151        CheckDeviceScreenState()
152
153    if args.angle_test_runner or args.suite == 'angle_trace_tests':
154        if args.help:
155            parser.print_help()
156        else:
157            os.chdir(args.output_directory)
158            return RunWithAngleTestRunner(args, extra_args)
159    else:
160        # Chromium runner parses args by itself, including --help
161        return RunWithChromiumTestRunner()
162
163
164if __name__ == "__main__":
165    sys.exit(main())
166