blob: 192f50ee8de2ace77abe999ff9ecadeaeeda8e63 [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
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010030import json
31import os
32import platform
33import subprocess
34import sys
35
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020036# Some tests requires specific options. Consider fixing the upstream crate
37# before updating this dictionary.
38TEST_OPTIONS = {
Joel Galensona0d4c5e2021-04-06 09:36:47 -070039 "ring_device_test_tests_digest_tests": [{"test-timeout": "600000"}],
40 "ring_device_test_src_lib": [{"test-timeout": "100000"}],
41}
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020042
43# Excluded tests. These tests will be ignored by this script.
44TEST_EXCLUDE = [
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +010045 "aidl_test_rust_client",
46 "aidl_test_rust_service"
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020047]
48
49# Excluded modules.
50EXCLUDE_PATHS = [
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +010051 "//external/adhd",
52 "//external/crosvm",
53 "//external/libchromeos-rs",
54 "//external/vm_tools"
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020055]
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +010056
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +020057
58class UpdaterException(Exception):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020059 """Exception generated by this script."""
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +020060
61
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010062class Env(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020063 """Env captures the execution environment.
64
65 It ensures this script is executed within an AOSP repository.
66
67 Attributes:
68 ANDROID_BUILD_TOP: A string representing the absolute path to the top
69 of the repository.
70 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +020071 def __init__(self):
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010072 try:
73 self.ANDROID_BUILD_TOP = os.environ['ANDROID_BUILD_TOP']
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +020074 except KeyError:
75 raise UpdaterException('$ANDROID_BUILD_TOP is not defined; you '
76 'must first source build/envsetup.sh and '
77 'select a target.')
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +020078
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010079
80class Bazel(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020081 """Bazel wrapper.
82
83 The wrapper is used to call bazel queryview and generate the list of
84 reverse dependencies.
85
86 Attributes:
87 path: The path to the bazel executable.
88 """
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010089 def __init__(self, env):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +020090 """Constructor.
91
92 Note that the current directory is changed to ANDROID_BUILD_TOP.
93
94 Args:
95 env: An instance of Env.
96
97 Raises:
98 UpdaterException: an error occurred while calling soong_ui.
99 """
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200100 if platform.system() != 'Linux':
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200101 raise UpdaterException('This script has only been tested on Linux.')
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200102 self.path = os.path.join(env.ANDROID_BUILD_TOP, "tools", "bazel")
Thiébaud Weksteendf132d62021-06-10 08:45:37 +0200103 soong_ui = os.path.join(env.ANDROID_BUILD_TOP, "build", "soong", "soong_ui.bash")
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200104
105 # soong_ui requires to be at the root of the repository.
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100106 os.chdir(env.ANDROID_BUILD_TOP)
Thiébaud Weksteendf132d62021-06-10 08:45:37 +0200107 print("Generating Bazel files...")
108 cmd = [soong_ui, "--make-mode", "GENERATE_BAZEL_FILES=1", "nothing"]
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100109 try:
Thiébaud Weksteendf132d62021-06-10 08:45:37 +0200110 subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
111 except subprocess.CalledProcessError as e:
112 raise UpdaterException('Unable to generate bazel workspace: ' + e.output)
113
114 print("Building Bazel Queryview. This can take a couple of minutes...")
115 cmd = [soong_ui, "--build-mode", "--all-modules", "--dir=.", "queryview"]
116 try:
117 subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100118 except subprocess.CalledProcessError as e:
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200119 raise UpdaterException('Unable to update TEST_MAPPING: ' + e.output)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100120
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100121 def query_modules(self, path):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200122 """Returns all modules for a given path."""
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200123 cmd = self.path + " query --config=queryview /" + path + ":all"
Thiébaud Weksteen76c4e232021-06-10 07:35:19 +0200124 out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True).strip().split("\n")
125 modules = set()
126 for line in out:
127 # speed up by excluding unused modules.
128 if "windows_x86" in line:
129 continue
130 modules.add(line)
131 return modules
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100132
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100133 def query_rdeps(self, module):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200134 """Returns all reverse dependencies for a single module."""
Thiébaud Weksteen3e32afc2021-06-10 07:56:06 +0200135 cmd = (self.path + " query --config=queryview \'rdeps(//..., " +
Thiébaud Weksteen76c4e232021-06-10 07:35:19 +0200136 module + ")\' --output=label_kind")
137 out = (subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True)
138 .strip().split("\n"))
139 if '' in out:
140 out.remove('')
141 return out
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100142
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100143 def exclude_module(self, module):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200144 for path in EXCLUDE_PATHS:
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100145 if module.startswith(path):
146 return True
147 return False
148
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100149 def query_rdep_tests(self, modules):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200150 """Returns all reverse dependency tests for modules in this package."""
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100151 rdep_tests = set()
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100152 for module in modules:
153 for rdep in self.query_rdeps(module):
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200154 rule_type, _, mod = rdep.split(" ")
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100155 if rule_type == "rust_test_" or rule_type == "rust_test":
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100156 if self.exclude_module(mod) == False:
157 rdep_tests.add(mod.split(":")[1].split("--")[0])
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100158 return rdep_tests
159
160
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200161class Package(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200162 """A Bazel package.
163
164 Attributes:
165 dir: The absolute path to this package.
166 dir_rel: The relative path to this package.
167 rdep_tests: The list of computed reverse dependencies.
168 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200169 def __init__(self, path, env, bazel):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200170 """Constructor.
171
172 Note that the current directory is changed to the package location when
173 called.
174
175 Args:
176 path: Path to the package.
177 env: An instance of Env.
178 bazel: An instance of Bazel.
179
180 Raises:
181 UpdaterException: the package does not appear to belong to the
182 current repository.
183 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200184 if path == None:
185 self.dir = os.getcwd()
186 else:
187 self.dir = path
188 try:
189 self.dir_rel = self.dir.split(env.ANDROID_BUILD_TOP)[1]
190 except IndexError:
191 raise UpdaterException('The path ' + self.dir + ' is not under ' +
192 env.ANDROID_BUILD_TOP + '; You must be in the '
193 'directory of a crate or pass its absolute path '
194 'as first argument.')
195
196 # Move to the package_directory.
197 os.chdir(self.dir)
198 modules = bazel.query_modules(self.dir_rel)
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200199 self.rdep_tests = bazel.query_rdep_tests(modules)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100200
201 def get_rdep_tests(self):
202 return self.rdep_tests
203
204
205class TestMapping(object):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200206 """A TEST_MAPPING file.
207
208 Attributes:
209 package: The package associated with this TEST_MAPPING file.
210 """
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100211 def __init__(self, path):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200212 """Constructor.
213
214 Args:
215 path: The absolute path to the package.
216 """
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200217 env = Env()
218 bazel = Bazel(env)
219 self.package = Package(path, env, bazel)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100220
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200221 def create(self):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200222 """Generates the TEST_MAPPING file."""
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200223 tests = self.package.get_rdep_tests()
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100224 if not bool(tests):
225 return
226 test_mapping = self.tests_to_mapping(tests)
227 self.write_test_mapping(test_mapping)
228
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100229 def tests_to_mapping(self, tests):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200230 """Translate the test list into a dictionary."""
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100231 test_mapping = {"presubmit": []}
232 for test in tests:
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200233 if test in TEST_EXCLUDE:
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100234 continue
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200235 if test in TEST_OPTIONS:
236 test_mapping["presubmit"].append({"name": test, "options": TEST_OPTIONS[test]})
Jeff Vander Stoep0b0e24f2021-01-24 20:50:26 +0100237 else:
238 test_mapping["presubmit"].append({"name": test})
Joel Galenson4f9d11f2021-04-06 10:18:21 -0700239 test_mapping["presubmit"] = sorted(test_mapping["presubmit"], key=lambda t: t["name"])
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100240 return test_mapping
241
242 def write_test_mapping(self, test_mapping):
Thiébaud Weksteen3604b752021-06-10 14:22:00 +0200243 """Writes the TEST_MAPPING file."""
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100244 with open("TEST_MAPPING", "w") as json_file:
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100245 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 +0100246 json.dump(test_mapping, json_file, indent=2, separators=(',', ': '), sort_keys=True)
247 json_file.write("\n")
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100248 print("TEST_MAPPING successfully updated!")
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100249
Thiébaud Weksteenfc485b22021-06-10 13:30:20 +0200250
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100251def main():
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +0100252 if len(sys.argv) == 2:
253 path = sys.argv[1]
254 else:
255 path = None
Thiébaud Weksteen5212f8a2021-06-10 08:18:32 +0200256 try:
257 test_mapping = TestMapping(path)
258 except UpdaterException as err:
259 sys.exit("Error: " + str(err))
Thiébaud Weksteen2e532bb2021-06-10 09:01:34 +0200260 test_mapping.create()
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +0100261
262if __name__ == '__main__':
263 main()