blob: 8a45dd3892394522d2d2fa3284648a08460e5e4e [file] [log] [blame]
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +01001#!/usr/bin/env python3
2#
3# Copyright (C) 2020 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020016"""Add or update tests to TEST_MAPPING.
17
18This script uses Bazel to find reverse dependencies on a crate and generates a
19TEST_MAPPING file. It accepts the absolute path to a crate as argument. If no
20argument is provided, it assumes the crate is the current directory.
21
22 Usage:
23 $ . build/envsetup.sh
24 $ lunch aosp_arm64-eng
25 $ update_crate_tests.py $ANDROID_BUILD_TOP/external/rust/crates/libc
26
27This script is automatically called by external_updater.
28"""
29
Joel Galenson08352442021-08-20 11:39:48 -070030import argparse
31import glob
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010032import json
33import os
34import platform
Joel Galenson4a08c642021-10-14 13:25:57 -070035import re
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010036import subprocess
37import sys
Joel Galenson4a2a3a82021-08-20 12:26:37 -070038from datetime import datetime
Joel Galenson08352442021-08-20 11:39:48 -070039from pathlib import Path
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010040
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020041# Some tests requires specific options. Consider fixing the upstream crate
42# before updating this dictionary.
43TEST_OPTIONS = {
Joel Galenson54d65532021-08-31 14:08:05 -070044 "ring_test_tests_digest_tests": [{"test-timeout": "600000"}],
45 "ring_test_src_lib": [{"test-timeout": "100000"}],
Joel Galensona0d4c5e2021-04-06 09:36:47 -070046}
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020047
Matthew Maurer24758f82021-12-10 18:51:22 +000048# Groups to add tests to. "presubmit" runs x86_64 device tests+host tests, and
49# "presubmit-rust" runs arm64 device tests on physical devices.
50TEST_GROUPS = [
51 "presubmit",
52 "presubmit-rust"
53]
54
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020055# Excluded tests. These tests will be ignored by this script.
56TEST_EXCLUDE = [
Joel Galenson54d65532021-08-31 14:08:05 -070057 "ash_test_src_lib",
58 "ash_test_tests_constant_size_arrays",
59 "ash_test_tests_display",
60 "shared_library_test_src_lib",
Ivan Lozano69030b32022-03-04 14:03:47 -050061 "vulkano_test_src_lib",
62
63 # These are helper binaries for aidl_integration_test
64 # and aren't actually meant to run as individual tests.
65 "aidl_test_rust_client",
66 "aidl_test_rust_service",
67 "aidl_test_rust_service_async",
68
David LeGared3bbf8c2022-03-15 16:01:49 +000069 # This is a helper binary for AuthFsHostTest and shouldn't
70 # be run directly.
71 "open_then_run",
72
Ivan Lozano69030b32022-03-04 14:03:47 -050073 # TODO: Remove when b/198197213 is closed.
74 "diced_client_test",
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020075]
76
77# Excluded modules.
78EXCLUDE_PATHS = [
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +010079 "//external/adhd",
80 "//external/crosvm",
81 "//external/libchromeos-rs",
82 "//external/vm_tools"
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020083]
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +010084
Joel Galenson4a08c642021-10-14 13:25:57 -070085LABEL_PAT = re.compile('^//(.*):.*$')
86EXTERNAL_PAT = re.compile('^//external/rust/')
87
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +020088
89class UpdaterException(Exception):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020090 """Exception generated by this script."""
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +020091
92
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010093class Env(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020094 """Env captures the execution environment.
95
96 It ensures this script is executed within an AOSP repository.
97
98 Attributes:
99 ANDROID_BUILD_TOP: A string representing the absolute path to the top
100 of the repository.
101 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200102 def __init__(self):
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100103 try:
104 self.ANDROID_BUILD_TOP = os.environ['ANDROID_BUILD_TOP']
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200105 except KeyError:
106 raise UpdaterException('$ANDROID_BUILD_TOP is not defined; you '
107 'must first source build/envsetup.sh and '
108 'select a target.')
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200109
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100110
111class Bazel(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200112 """Bazel wrapper.
113
114 The wrapper is used to call bazel queryview and generate the list of
115 reverse dependencies.
116
117 Attributes:
118 path: The path to the bazel executable.
119 """
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100120 def __init__(self, env):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200121 """Constructor.
122
123 Note that the current directory is changed to ANDROID_BUILD_TOP.
124
125 Args:
126 env: An instance of Env.
127
128 Raises:
129 UpdaterException: an error occurred while calling soong_ui.
130 """
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200131 if platform.system() != 'Linux':
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200132 raise UpdaterException('This script has only been tested on Linux.')
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200133 self.path = os.path.join(env.ANDROID_BUILD_TOP, "tools", "bazel")
Thiébaud Weksteendf132d62021-06-10 08:45:37 +0200134 soong_ui = os.path.join(env.ANDROID_BUILD_TOP, "build", "soong", "soong_ui.bash")
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200135
136 # soong_ui requires to be at the root of the repository.
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100137 os.chdir(env.ANDROID_BUILD_TOP)
Thiébaud Weksteendf132d62021-06-10 08:45:37 +0200138 print("Generating Bazel files...")
Lukacs T. Berki9206f1c2021-09-02 17:51:13 +0200139 cmd = [soong_ui, "--make-mode", "bp2build"]
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100140 try:
Thiébaud Weksteendf132d62021-06-10 08:45:37 +0200141 subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
142 except subprocess.CalledProcessError as e:
143 raise UpdaterException('Unable to generate bazel workspace: ' + e.output)
144
145 print("Building Bazel Queryview. This can take a couple of minutes...")
146 cmd = [soong_ui, "--build-mode", "--all-modules", "--dir=.", "queryview"]
147 try:
148 subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100149 except subprocess.CalledProcessError as e:
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200150 raise UpdaterException('Unable to update TEST_MAPPING: ' + e.output)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100151
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100152 def query_modules(self, path):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200153 """Returns all modules for a given path."""
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200154 cmd = self.path + " query --config=queryview /" + path + ":all"
Thiébaud Weksteen76c4e232021-06-10 07:35:19 +0200155 out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True).strip().split("\n")
156 modules = set()
157 for line in out:
158 # speed up by excluding unused modules.
159 if "windows_x86" in line:
160 continue
161 modules.add(line)
162 return modules
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100163
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100164 def query_rdeps(self, module):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200165 """Returns all reverse dependencies for a single module."""
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200166 cmd = (self.path + " query --config=queryview \'rdeps(//..., " +
Thiébaud Weksteen76c4e232021-06-10 07:35:19 +0200167 module + ")\' --output=label_kind")
168 out = (subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True)
169 .strip().split("\n"))
170 if '' in out:
171 out.remove('')
172 return out
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100173
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100174 def exclude_module(self, module):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200175 for path in EXCLUDE_PATHS:
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100176 if module.startswith(path):
177 return True
178 return False
179
Joel Galenson4a08c642021-10-14 13:25:57 -0700180 def query_rdep_tests_dirs(self, modules, path):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200181 """Returns all reverse dependency tests for modules in this package."""
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100182 rdep_tests = set()
Joel Galenson4a08c642021-10-14 13:25:57 -0700183 rdep_dirs = set()
184 path_pat = re.compile("^/%s:.*$" % path)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100185 for module in modules:
186 for rdep in self.query_rdeps(module):
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200187 rule_type, _, mod = rdep.split(" ")
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100188 if rule_type == "rust_test_" or rule_type == "rust_test":
Joel Galenson4a08c642021-10-14 13:25:57 -0700189 if self.exclude_module(mod):
190 continue
191 path_match = path_pat.match(mod)
192 if path_match or not EXTERNAL_PAT.match(mod):
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100193 rdep_tests.add(mod.split(":")[1].split("--")[0])
Joel Galenson4a08c642021-10-14 13:25:57 -0700194 else:
195 label_match = LABEL_PAT.match(mod)
196 if label_match:
197 rdep_dirs.add(label_match.group(1))
198 return (rdep_tests, rdep_dirs)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100199
200
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200201class Package(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200202 """A Bazel package.
203
204 Attributes:
205 dir: The absolute path to this package.
206 dir_rel: The relative path to this package.
207 rdep_tests: The list of computed reverse dependencies.
Joel Galenson4a08c642021-10-14 13:25:57 -0700208 rdep_dirs: The list of computed reverse dependency directories.
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200209 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200210 def __init__(self, path, env, bazel):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200211 """Constructor.
212
213 Note that the current directory is changed to the package location when
214 called.
215
216 Args:
217 path: Path to the package.
218 env: An instance of Env.
219 bazel: An instance of Bazel.
220
221 Raises:
222 UpdaterException: the package does not appear to belong to the
223 current repository.
224 """
Joel Galenson17791042021-06-17 14:59:15 -0700225 self.dir = path
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200226 try:
227 self.dir_rel = self.dir.split(env.ANDROID_BUILD_TOP)[1]
228 except IndexError:
229 raise UpdaterException('The path ' + self.dir + ' is not under ' +
230 env.ANDROID_BUILD_TOP + '; You must be in the '
231 'directory of a crate or pass its absolute path '
Joel Galenson17791042021-06-17 14:59:15 -0700232 'as the argument.')
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200233
234 # Move to the package_directory.
235 os.chdir(self.dir)
236 modules = bazel.query_modules(self.dir_rel)
Joel Galenson4a08c642021-10-14 13:25:57 -0700237 (self.rdep_tests, self.rdep_dirs) = bazel.query_rdep_tests_dirs(modules, self.dir_rel)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100238
Joel Galenson4a08c642021-10-14 13:25:57 -0700239 def get_rdep_tests_dirs(self):
240 return (self.rdep_tests, self.rdep_dirs)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100241
242
243class TestMapping(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200244 """A TEST_MAPPING file.
245
246 Attributes:
247 package: The package associated with this TEST_MAPPING file.
248 """
Joel Galenson17791042021-06-17 14:59:15 -0700249 def __init__(self, env, bazel, path):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200250 """Constructor.
251
252 Args:
Joel Galenson17791042021-06-17 14:59:15 -0700253 env: An instance of Env.
254 bazel: An instance of Bazel.
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200255 path: The absolute path to the package.
256 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200257 self.package = Package(path, env, bazel)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100258
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200259 def create(self):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200260 """Generates the TEST_MAPPING file."""
Joel Galenson4a08c642021-10-14 13:25:57 -0700261 (tests, dirs) = self.package.get_rdep_tests_dirs()
262 if not bool(tests) and not bool(dirs):
263 if os.path.isfile('TEST_MAPPING'):
264 os.remove('TEST_MAPPING')
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100265 return
Joel Galenson4a08c642021-10-14 13:25:57 -0700266 test_mapping = self.tests_dirs_to_mapping(tests, dirs)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100267 self.write_test_mapping(test_mapping)
268
Joel Galenson4a08c642021-10-14 13:25:57 -0700269 def tests_dirs_to_mapping(self, tests, dirs):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200270 """Translate the test list into a dictionary."""
Matthew Maurer24758f82021-12-10 18:51:22 +0000271 test_mapping = {"imports": []}
272 for test_group in TEST_GROUPS:
273 test_mapping[test_group] = []
274 for test in tests:
275 if test in TEST_EXCLUDE:
276 continue
277 if test in TEST_OPTIONS:
278 test_mapping[test_group].append({"name": test, "options": TEST_OPTIONS[test]})
279 else:
280 test_mapping[test_group].append({"name": test})
281 test_mapping[test_group] = sorted(test_mapping[test_group], key=lambda t: t["name"])
Joel Galenson4a08c642021-10-14 13:25:57 -0700282 for dir in dirs:
283 test_mapping["imports"].append({"path": dir})
Joel Galenson4a08c642021-10-14 13:25:57 -0700284 test_mapping["imports"] = sorted(test_mapping["imports"], key=lambda t: t["path"])
Matthew Maurer24758f82021-12-10 18:51:22 +0000285 test_mapping = {section: entry for (section, entry) in test_mapping.items() if entry}
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100286 return test_mapping
287
288 def write_test_mapping(self, test_mapping):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200289 """Writes the TEST_MAPPING file."""
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100290 with open("TEST_MAPPING", "w") as json_file:
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100291 json_file.write("// Generated by update_crate_tests.py for tests that depend on this crate.\n")
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100292 json.dump(test_mapping, json_file, indent=2, separators=(',', ': '), sort_keys=True)
293 json_file.write("\n")
Joel Galenson17791042021-06-17 14:59:15 -0700294 print("TEST_MAPPING successfully updated for %s!" % self.package.dir_rel)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100295
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200296
Joel Galenson08352442021-08-20 11:39:48 -0700297def parse_args():
298 parser = argparse.ArgumentParser('update_crate_tests')
Joel Galenson4a2a3a82021-08-20 12:26:37 -0700299 parser.add_argument('paths',
300 nargs='*',
301 help='Absolute or relative paths of the projects as globs.')
302 parser.add_argument('--branch_and_commit',
303 action='store_true',
304 help='Starts a new branch and commit changes.')
305 parser.add_argument('--push_change',
306 action='store_true',
307 help='Pushes change to Gerrit.')
Joel Galenson08352442021-08-20 11:39:48 -0700308 return parser.parse_args()
309
310
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100311def main():
Joel Galenson08352442021-08-20 11:39:48 -0700312 args = parse_args()
313 paths = args.paths if len(args.paths) > 0 else [os.getcwd()]
314 # We want to use glob to get all the paths, so we first convert to absolute.
315 paths = [Path(path).resolve() for path in paths]
316 paths = sorted([path for abs_path in paths
317 for path in glob.glob(str(abs_path))])
318
Joel Galenson17791042021-06-17 14:59:15 -0700319 env = Env()
320 bazel = Bazel(env)
321 for path in paths:
322 try:
323 test_mapping = TestMapping(env, bazel, path)
Joel Galenson4a2a3a82021-08-20 12:26:37 -0700324 test_mapping.create()
325 changed = (subprocess.call(['git', 'diff', '--quiet']) == 1)
Joel Galensonbdf3ab42021-08-30 08:57:18 -0700326 untracked = (os.path.isfile('TEST_MAPPING') and
327 (subprocess.run(['git', 'ls-files', '--error-unmatch', 'TEST_MAPPING'],
328 stderr=subprocess.DEVNULL,
329 stdout=subprocess.DEVNULL).returncode == 1))
330 if args.branch_and_commit and (changed or untracked):
Joel Galenson4a2a3a82021-08-20 12:26:37 -0700331 subprocess.check_output(['repo', 'start',
332 'tmp_auto_test_mapping', '.'])
333 subprocess.check_output(['git', 'add', 'TEST_MAPPING'])
334 subprocess.check_output(['git', 'commit', '-m',
335 'Update TEST_MAPPING\n\nTest: None'])
Joel Galensonbdf3ab42021-08-30 08:57:18 -0700336 if args.push_change and (changed or untracked):
Joel Galenson4a2a3a82021-08-20 12:26:37 -0700337 date = datetime.today().strftime('%m-%d')
338 subprocess.check_output(['git', 'push', 'aosp', 'HEAD:refs/for/master',
339 '-o', 'topic=test-mapping-%s' % date])
340 except (UpdaterException, subprocess.CalledProcessError) as err:
Joel Galenson17791042021-06-17 14:59:15 -0700341 sys.exit("Error: " + str(err))
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100342
343if __name__ == '__main__':
344 main()