xref: /aosp_15_r20/cts/hostsidetests/multidevices/tools/run_all_tests.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1# Copyright 2024 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import argparse
16import json
17import logging
18import multi_device_utils
19import os
20import os.path
21from pathlib import Path
22import re
23import subprocess
24import tempfile
25import time
26import yaml
27
28
29RESULT_KEY = 'result'
30RESULT_PASS = 'PASS'
31RESULT_FAIL = 'FAIL'
32CONFIG_FILE = os.path.join(os.getcwd(), 'config.yml')
33TESTS_DIR = os.path.join(os.getcwd(), 'tests')
34CTS_VERIFIER_PACKAGE_NAME = 'com.android.cts.verifier'
35MOBLY_TEST_SUMMARY_TXT_FILE = 'test_mobly_summary.txt'
36MULTI_DEVICE_TEST_ACTIVITY = (
37    'com.android.cts.verifier/.multidevice.MultiDeviceTestsActivity'
38)
39ACTION_HOST_TEST_RESULT = 'com.android.cts.verifier.ACTION_HOST_TEST_RESULT'
40EXTRA_VERSION = 'com.android.cts.verifier.extra.HOST_TEST_RESULT'
41ACTIVITY_START_WAIT = 2  # seconds
42
43
44def get_config_file_contents():
45  """Read the config file contents from a YML file.
46
47  Args: None
48
49  Returns:
50    config_file_contents: a dict read from config.yml
51  """
52  with open(CONFIG_FILE) as file:
53    config_file_contents = yaml.safe_load(file)
54  return config_file_contents
55
56
57def get_device_serial_number(config_file_contents):
58  """Returns the serial number of the dut devices.
59
60  Args:
61      config_file_contents: dict read from config.yml file.
62
63  Returns:
64      The serial numbers (str) or None if the device is not found.
65  """
66
67  device_serial_numbers = []
68  for _, testbed_data in config_file_contents.items():
69    for data_dict in testbed_data:
70      android_devices = data_dict.get('Controllers', {}).get(
71          'AndroidDevice', []
72      )
73
74      for device_dict in android_devices:
75        device_serial_numbers.append(device_dict.get('serial'))
76  return device_serial_numbers
77
78
79def report_result(device_id, results):
80  """Sends a pass/fail result to the device, via an intent.
81
82  Args:
83   device_id: the serial number of the device.
84   results: a dictionary contains all multi-device test names as key and
85     result/summary of current test run.
86  """
87  adb = f'adb -s {device_id}'
88
89  # Start MultiDeviceTestsActivity to receive test results
90  cmd = (
91      f'{adb} shell am start'
92      f' {MULTI_DEVICE_TEST_ACTIVITY} --activity-brought-to-front'
93  )
94  multi_device_utils.run(cmd)
95  time.sleep(ACTIVITY_START_WAIT)
96
97  json_results = json.dumps(results)
98  cmd = (
99      f'{adb} shell am broadcast -a {ACTION_HOST_TEST_RESULT} --es'
100      f" {EXTRA_VERSION} '{json_results}'"
101  )
102  if len(cmd) > 4095:
103    logging.info('Command string might be too long! len:%s', len(cmd))
104  multi_device_utils.run(cmd)
105
106
107def main():
108  """Run all Multi-device Mobly tests and collect results."""
109
110  logging.basicConfig(level=logging.INFO)
111  topdir = tempfile.mkdtemp(prefix='MultiDevice_')
112  subprocess.call(['chmod', 'g+rx', topdir])  # Add permissions
113
114  # Parse command-line arguments
115  parser = argparse.ArgumentParser()
116  parser.add_argument(
117      '--test_cases',
118      nargs='+',
119      help='Specific test cases to run (space-separated)')
120  parser.add_argument(
121      '--test_files',
122      nargs='+',
123      help='Filter test files by name (substring match, space-separated)')
124  args = parser.parse_args()
125
126  config_file_contents = get_config_file_contents()
127  device_ids = get_device_serial_number(config_file_contents)
128
129  test_results = {}
130  test_summary_file_list = []
131
132  # Run tests
133  for root, _, files in os.walk(TESTS_DIR):
134    for test_file in files:
135      if test_file.endswith('-py-ctsv') and (
136          args.test_files is None or
137          test_file in args.test_files
138      ):
139        test_file_path = os.path.join(root, test_file)
140        logging.info('Start running test: %s', test_file)
141        cmd = [
142            test_file_path,  # Use the full path to the test file
143            '-c',
144            CONFIG_FILE,
145            '--testbed',
146            test_file,
147        ]
148
149        if args.test_cases:
150          cmd.extend(['--tests'])
151          for test_case in args.test_cases:
152            cmd.extend([test_case])
153
154        summary_file_path = os.path.join(topdir, MOBLY_TEST_SUMMARY_TXT_FILE)
155
156        test_completed = False
157        with open(summary_file_path, 'w+') as fp:
158          subprocess.run(cmd, stdout=fp, check=False)
159          fp.seek(0)
160          for line in fp:
161            if line.startswith('Test summary saved in'):
162              match = re.search(r'"(.*?)"', line)  # Get test artifacts file path
163              if match:
164                test_summary = Path(match.group(1))
165                test_artifact = test_summary.parent
166                logging.info(
167                    'Please check the test artifacts of %s under: %s', test_file, test_artifact
168                )
169                if test_summary.exists():
170                  test_summary_file_list.append(test_summary)
171                  test_completed = True
172                  break
173        if not test_completed:
174          logging.error('Failed to get test summary file path')
175        os.remove(os.path.join(topdir, MOBLY_TEST_SUMMARY_TXT_FILE))
176
177  # Parse test summary files
178  for test_summary_file in test_summary_file_list:
179    with open(test_summary_file) as file:
180      test_summary_content = yaml.safe_load_all(file)
181      for doc in test_summary_content:
182        if doc['Type'] == 'Record':
183          test_key = f"{doc['Test Class']}#{doc['Test Name']}"
184          result = (
185              RESULT_PASS if doc['Result'] in ('PASS', 'SKIP') else RESULT_FAIL
186          )
187          test_results.setdefault(test_key, {RESULT_KEY: result})
188
189  for device_id in device_ids:
190    report_result(device_id, test_results)
191
192  logging.info('Test execution completed. Results: %s', test_results)
193
194
195if __name__ == '__main__':
196  main()
197