xref: /aosp_15_r20/development/scripts/update_crate_tests.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*90c8c64dSAndroid Build Coastguard Worker#
3*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project
4*90c8c64dSAndroid Build Coastguard Worker#
5*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*90c8c64dSAndroid Build Coastguard Worker#
9*90c8c64dSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*90c8c64dSAndroid Build Coastguard Worker#
11*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*90c8c64dSAndroid Build Coastguard Worker# limitations under the License.
16*90c8c64dSAndroid Build Coastguard Worker"""Add or update tests to TEST_MAPPING.
17*90c8c64dSAndroid Build Coastguard Worker
18*90c8c64dSAndroid Build Coastguard WorkerThis script uses Bazel to find reverse dependencies on a crate and generates a
19*90c8c64dSAndroid Build Coastguard WorkerTEST_MAPPING file. It accepts the absolute path to a crate as argument. If no
20*90c8c64dSAndroid Build Coastguard Workerargument is provided, it assumes the crate is the current directory.
21*90c8c64dSAndroid Build Coastguard Worker
22*90c8c64dSAndroid Build Coastguard Worker  Usage:
23*90c8c64dSAndroid Build Coastguard Worker  $ . build/envsetup.sh
24*90c8c64dSAndroid Build Coastguard Worker  $ lunch aosp_arm64-eng
25*90c8c64dSAndroid Build Coastguard Worker  $ update_crate_tests.py $ANDROID_BUILD_TOP/external/rust/crates/libc
26*90c8c64dSAndroid Build Coastguard Worker
27*90c8c64dSAndroid Build Coastguard WorkerThis script is automatically called by external_updater.
28*90c8c64dSAndroid Build Coastguard Worker
29*90c8c64dSAndroid Build Coastguard WorkerA test_mapping_config.json file can be defined in the project directory to
30*90c8c64dSAndroid Build Coastguard Workerconfigure the generated TEST_MAPPING file, for example:
31*90c8c64dSAndroid Build Coastguard Worker
32*90c8c64dSAndroid Build Coastguard Worker    {
33*90c8c64dSAndroid Build Coastguard Worker        // Run tests in postsubmit instead of presubmit.
34*90c8c64dSAndroid Build Coastguard Worker        "postsubmit_tests":["foo"]
35*90c8c64dSAndroid Build Coastguard Worker    }
36*90c8c64dSAndroid Build Coastguard Worker
37*90c8c64dSAndroid Build Coastguard Worker"""
38*90c8c64dSAndroid Build Coastguard Worker
39*90c8c64dSAndroid Build Coastguard Workerimport argparse
40*90c8c64dSAndroid Build Coastguard Workerimport glob
41*90c8c64dSAndroid Build Coastguard Workerimport json
42*90c8c64dSAndroid Build Coastguard Workerimport os
43*90c8c64dSAndroid Build Coastguard Workerimport platform
44*90c8c64dSAndroid Build Coastguard Workerimport re
45*90c8c64dSAndroid Build Coastguard Workerimport subprocess
46*90c8c64dSAndroid Build Coastguard Workerimport sys
47*90c8c64dSAndroid Build Coastguard Workerfrom datetime import datetime
48*90c8c64dSAndroid Build Coastguard Workerfrom pathlib import Path
49*90c8c64dSAndroid Build Coastguard Worker
50*90c8c64dSAndroid Build Coastguard Worker# Some tests requires specific options. Consider fixing the upstream crate
51*90c8c64dSAndroid Build Coastguard Worker# before updating this dictionary.
52*90c8c64dSAndroid Build Coastguard WorkerTEST_OPTIONS = {
53*90c8c64dSAndroid Build Coastguard Worker    "ring_test_tests_digest_tests": [{"test-timeout": "600000"}],
54*90c8c64dSAndroid Build Coastguard Worker    "ring_test_src_lib": [{"test-timeout": "100000"}],
55*90c8c64dSAndroid Build Coastguard Worker}
56*90c8c64dSAndroid Build Coastguard Worker
57*90c8c64dSAndroid Build Coastguard Worker# Groups to add tests to. "presubmit" runs x86_64 device tests+host tests, and
58*90c8c64dSAndroid Build Coastguard Worker# "presubmit-rust" runs arm64 device tests on physical devices.
59*90c8c64dSAndroid Build Coastguard WorkerTEST_GROUPS = [
60*90c8c64dSAndroid Build Coastguard Worker    "presubmit",
61*90c8c64dSAndroid Build Coastguard Worker    "presubmit-rust",
62*90c8c64dSAndroid Build Coastguard Worker    "postsubmit",
63*90c8c64dSAndroid Build Coastguard Worker]
64*90c8c64dSAndroid Build Coastguard Worker
65*90c8c64dSAndroid Build Coastguard Worker# Excluded tests. These tests will be ignored by this script.
66*90c8c64dSAndroid Build Coastguard WorkerTEST_EXCLUDE = [
67*90c8c64dSAndroid Build Coastguard Worker        "ash_test_src_lib",
68*90c8c64dSAndroid Build Coastguard Worker        "ash_test_tests_constant_size_arrays",
69*90c8c64dSAndroid Build Coastguard Worker        "ash_test_tests_display",
70*90c8c64dSAndroid Build Coastguard Worker        "shared_library_test_src_lib",
71*90c8c64dSAndroid Build Coastguard Worker        "vulkano_test_src_lib",
72*90c8c64dSAndroid Build Coastguard Worker
73*90c8c64dSAndroid Build Coastguard Worker        # These are helper binaries for aidl_integration_test
74*90c8c64dSAndroid Build Coastguard Worker        # and aren't actually meant to run as individual tests.
75*90c8c64dSAndroid Build Coastguard Worker        "aidl_test_rust_client",
76*90c8c64dSAndroid Build Coastguard Worker        "aidl_test_rust_service",
77*90c8c64dSAndroid Build Coastguard Worker        "aidl_test_rust_service_async",
78*90c8c64dSAndroid Build Coastguard Worker
79*90c8c64dSAndroid Build Coastguard Worker        # This is a helper binary for AuthFsHostTest and shouldn't
80*90c8c64dSAndroid Build Coastguard Worker        # be run directly.
81*90c8c64dSAndroid Build Coastguard Worker        "open_then_run",
82*90c8c64dSAndroid Build Coastguard Worker
83*90c8c64dSAndroid Build Coastguard Worker        # TODO: Remove when b/198197213 is closed.
84*90c8c64dSAndroid Build Coastguard Worker        "diced_client_test",
85*90c8c64dSAndroid Build Coastguard Worker
86*90c8c64dSAndroid Build Coastguard Worker        "CoverageRustSmokeTest",
87*90c8c64dSAndroid Build Coastguard Worker        "libtrusty-rs-tests",
88*90c8c64dSAndroid Build Coastguard Worker        "terminal-size_test_src_lib",
89*90c8c64dSAndroid Build Coastguard Worker]
90*90c8c64dSAndroid Build Coastguard Worker
91*90c8c64dSAndroid Build Coastguard Worker# Excluded modules.
92*90c8c64dSAndroid Build Coastguard WorkerEXCLUDE_PATHS = [
93*90c8c64dSAndroid Build Coastguard Worker        "//external/adhd",
94*90c8c64dSAndroid Build Coastguard Worker        "//external/crosvm",
95*90c8c64dSAndroid Build Coastguard Worker        "//external/libchromeos-rs",
96*90c8c64dSAndroid Build Coastguard Worker        "//external/vm_tools"
97*90c8c64dSAndroid Build Coastguard Worker]
98*90c8c64dSAndroid Build Coastguard Worker
99*90c8c64dSAndroid Build Coastguard WorkerLABEL_PAT = re.compile('^//(.*):.*$')
100*90c8c64dSAndroid Build Coastguard WorkerEXTERNAL_PAT = re.compile('^//external/rust/')
101*90c8c64dSAndroid Build Coastguard Worker
102*90c8c64dSAndroid Build Coastguard Worker
103*90c8c64dSAndroid Build Coastguard Workerclass UpdaterException(Exception):
104*90c8c64dSAndroid Build Coastguard Worker    """Exception generated by this script."""
105*90c8c64dSAndroid Build Coastguard Worker
106*90c8c64dSAndroid Build Coastguard Worker
107*90c8c64dSAndroid Build Coastguard Workerclass Env(object):
108*90c8c64dSAndroid Build Coastguard Worker    """Env captures the execution environment.
109*90c8c64dSAndroid Build Coastguard Worker
110*90c8c64dSAndroid Build Coastguard Worker    It ensures this script is executed within an AOSP repository.
111*90c8c64dSAndroid Build Coastguard Worker
112*90c8c64dSAndroid Build Coastguard Worker    Attributes:
113*90c8c64dSAndroid Build Coastguard Worker      ANDROID_BUILD_TOP: A string representing the absolute path to the top
114*90c8c64dSAndroid Build Coastguard Worker        of the repository.
115*90c8c64dSAndroid Build Coastguard Worker    """
116*90c8c64dSAndroid Build Coastguard Worker    def __init__(self):
117*90c8c64dSAndroid Build Coastguard Worker        try:
118*90c8c64dSAndroid Build Coastguard Worker            self.ANDROID_BUILD_TOP = os.environ['ANDROID_BUILD_TOP']
119*90c8c64dSAndroid Build Coastguard Worker        except KeyError:
120*90c8c64dSAndroid Build Coastguard Worker            raise UpdaterException('$ANDROID_BUILD_TOP is not defined; you '
121*90c8c64dSAndroid Build Coastguard Worker                                   'must first source build/envsetup.sh and '
122*90c8c64dSAndroid Build Coastguard Worker                                   'select a target.')
123*90c8c64dSAndroid Build Coastguard Worker
124*90c8c64dSAndroid Build Coastguard Worker
125*90c8c64dSAndroid Build Coastguard Workerclass Bazel(object):
126*90c8c64dSAndroid Build Coastguard Worker    """Bazel wrapper.
127*90c8c64dSAndroid Build Coastguard Worker
128*90c8c64dSAndroid Build Coastguard Worker    The wrapper is used to call bazel queryview and generate the list of
129*90c8c64dSAndroid Build Coastguard Worker    reverse dependencies.
130*90c8c64dSAndroid Build Coastguard Worker
131*90c8c64dSAndroid Build Coastguard Worker    Attributes:
132*90c8c64dSAndroid Build Coastguard Worker      path: The path to the bazel executable.
133*90c8c64dSAndroid Build Coastguard Worker    """
134*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, env):
135*90c8c64dSAndroid Build Coastguard Worker        """Constructor.
136*90c8c64dSAndroid Build Coastguard Worker
137*90c8c64dSAndroid Build Coastguard Worker        Note that the current directory is changed to ANDROID_BUILD_TOP.
138*90c8c64dSAndroid Build Coastguard Worker
139*90c8c64dSAndroid Build Coastguard Worker        Args:
140*90c8c64dSAndroid Build Coastguard Worker          env: An instance of Env.
141*90c8c64dSAndroid Build Coastguard Worker
142*90c8c64dSAndroid Build Coastguard Worker        Raises:
143*90c8c64dSAndroid Build Coastguard Worker          UpdaterException: an error occurred while calling soong_ui.
144*90c8c64dSAndroid Build Coastguard Worker        """
145*90c8c64dSAndroid Build Coastguard Worker        if platform.system() != 'Linux':
146*90c8c64dSAndroid Build Coastguard Worker            raise UpdaterException('This script has only been tested on Linux.')
147*90c8c64dSAndroid Build Coastguard Worker        self.path = os.path.join(env.ANDROID_BUILD_TOP, "build", "bazel", "bin", "bazel")
148*90c8c64dSAndroid Build Coastguard Worker        soong_ui = os.path.join(env.ANDROID_BUILD_TOP, "build", "soong", "soong_ui.bash")
149*90c8c64dSAndroid Build Coastguard Worker
150*90c8c64dSAndroid Build Coastguard Worker        # soong_ui requires to be at the root of the repository.
151*90c8c64dSAndroid Build Coastguard Worker        os.chdir(env.ANDROID_BUILD_TOP)
152*90c8c64dSAndroid Build Coastguard Worker
153*90c8c64dSAndroid Build Coastguard Worker        print("Building Bazel Queryview. This can take a couple of minutes...")
154*90c8c64dSAndroid Build Coastguard Worker        cmd = [soong_ui, "--build-mode", "--all-modules", "--dir=.", "queryview"]
155*90c8c64dSAndroid Build Coastguard Worker        try:
156*90c8c64dSAndroid Build Coastguard Worker            subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
157*90c8c64dSAndroid Build Coastguard Worker        except subprocess.CalledProcessError as e:
158*90c8c64dSAndroid Build Coastguard Worker            raise UpdaterException('Unable to update TEST_MAPPING: ' + e.output)
159*90c8c64dSAndroid Build Coastguard Worker
160*90c8c64dSAndroid Build Coastguard Worker    def query_modules(self, path):
161*90c8c64dSAndroid Build Coastguard Worker        """Returns all modules for a given path."""
162*90c8c64dSAndroid Build Coastguard Worker        cmd = self.path + " query --config=queryview /" + path + ":all"
163*90c8c64dSAndroid Build Coastguard Worker        out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True).strip().split("\n")
164*90c8c64dSAndroid Build Coastguard Worker        modules = set()
165*90c8c64dSAndroid Build Coastguard Worker        for line in out:
166*90c8c64dSAndroid Build Coastguard Worker            # speed up by excluding unused modules.
167*90c8c64dSAndroid Build Coastguard Worker            if "windows_x86" in line:
168*90c8c64dSAndroid Build Coastguard Worker                continue
169*90c8c64dSAndroid Build Coastguard Worker            modules.add(line)
170*90c8c64dSAndroid Build Coastguard Worker        return modules
171*90c8c64dSAndroid Build Coastguard Worker
172*90c8c64dSAndroid Build Coastguard Worker    def query_rdeps(self, module):
173*90c8c64dSAndroid Build Coastguard Worker        """Returns all reverse dependencies for a single module."""
174*90c8c64dSAndroid Build Coastguard Worker        cmd = (self.path + " query --config=queryview \'rdeps(//..., " +
175*90c8c64dSAndroid Build Coastguard Worker                module + ")\' --output=label_kind")
176*90c8c64dSAndroid Build Coastguard Worker        out = (subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True)
177*90c8c64dSAndroid Build Coastguard Worker                .strip().split("\n"))
178*90c8c64dSAndroid Build Coastguard Worker        if '' in out:
179*90c8c64dSAndroid Build Coastguard Worker            out.remove('')
180*90c8c64dSAndroid Build Coastguard Worker        return out
181*90c8c64dSAndroid Build Coastguard Worker
182*90c8c64dSAndroid Build Coastguard Worker    def exclude_module(self, module):
183*90c8c64dSAndroid Build Coastguard Worker        for path in EXCLUDE_PATHS:
184*90c8c64dSAndroid Build Coastguard Worker            if module.startswith(path):
185*90c8c64dSAndroid Build Coastguard Worker                return True
186*90c8c64dSAndroid Build Coastguard Worker        return False
187*90c8c64dSAndroid Build Coastguard Worker
188*90c8c64dSAndroid Build Coastguard Worker    # Return all the TEST_MAPPING files within a given path.
189*90c8c64dSAndroid Build Coastguard Worker    def find_all_test_mapping_files(self, path):
190*90c8c64dSAndroid Build Coastguard Worker        result = []
191*90c8c64dSAndroid Build Coastguard Worker        for root, dirs, files in os.walk(path):
192*90c8c64dSAndroid Build Coastguard Worker            if "TEST_MAPPING" in files:
193*90c8c64dSAndroid Build Coastguard Worker                result.append(os.path.join(root, "TEST_MAPPING"))
194*90c8c64dSAndroid Build Coastguard Worker        return result
195*90c8c64dSAndroid Build Coastguard Worker
196*90c8c64dSAndroid Build Coastguard Worker    # For a given test, return the TEST_MAPPING file where the test is mapped.
197*90c8c64dSAndroid Build Coastguard Worker    # This limits the search to the directory specified in "path" along with its subdirs.
198*90c8c64dSAndroid Build Coastguard Worker    def test_to_test_mapping(self, env, path, test):
199*90c8c64dSAndroid Build Coastguard Worker        test_mapping_files = self.find_all_test_mapping_files(env.ANDROID_BUILD_TOP + path)
200*90c8c64dSAndroid Build Coastguard Worker        for file in test_mapping_files:
201*90c8c64dSAndroid Build Coastguard Worker            with open(file) as fd:
202*90c8c64dSAndroid Build Coastguard Worker                if "\""+ test + "\"" in fd.read():
203*90c8c64dSAndroid Build Coastguard Worker                    mapping_path = file.split("/TEST_MAPPING")[0].split("//")[1]
204*90c8c64dSAndroid Build Coastguard Worker                    return mapping_path
205*90c8c64dSAndroid Build Coastguard Worker
206*90c8c64dSAndroid Build Coastguard Worker        return None
207*90c8c64dSAndroid Build Coastguard Worker
208*90c8c64dSAndroid Build Coastguard Worker    # Returns:
209*90c8c64dSAndroid Build Coastguard Worker    # rdep_test: for tests specified locally.
210*90c8c64dSAndroid Build Coastguard Worker    # rdep_dirs: for paths to TEST_MAPPING files for reverse dependencies.
211*90c8c64dSAndroid Build Coastguard Worker    #
212*90c8c64dSAndroid Build Coastguard Worker    # We import directories for non-local tests because including tests directly has proven to be
213*90c8c64dSAndroid Build Coastguard Worker    # fragile and burdensome. For example, whenever a project removes or renames a test, all the
214*90c8c64dSAndroid Build Coastguard Worker    # TEST_MAPPING files for its reverse dependencies must be updated or we get test breakages.
215*90c8c64dSAndroid Build Coastguard Worker    # That can be many tens of projects that must updated to prevent the reported breakage of tests
216*90c8c64dSAndroid Build Coastguard Worker    # that no longer exist. Similarly when a test is added, it won't be run when the reverse
217*90c8c64dSAndroid Build Coastguard Worker    # dependencies change unless/until update_crate_tests.py is run for its depenencies.
218*90c8c64dSAndroid Build Coastguard Worker    # Importing TEST_MAPPING files instead of tests solves both of these problems. When tests are
219*90c8c64dSAndroid Build Coastguard Worker    # removed, renamed, or added, only files local to the project need to be modified.
220*90c8c64dSAndroid Build Coastguard Worker    # The downside is that we potentially miss some tests. But this seems like a reasonable
221*90c8c64dSAndroid Build Coastguard Worker    # tradeoff.
222*90c8c64dSAndroid Build Coastguard Worker    def query_rdep_tests_dirs(self, env, modules, path, exclude_dir):
223*90c8c64dSAndroid Build Coastguard Worker        """Returns all reverse dependency tests for modules in this package."""
224*90c8c64dSAndroid Build Coastguard Worker        rdep_tests = set()
225*90c8c64dSAndroid Build Coastguard Worker        rdep_dirs = set()
226*90c8c64dSAndroid Build Coastguard Worker        path_pat = re.compile("^/%s:.*$" % path)
227*90c8c64dSAndroid Build Coastguard Worker        for module in modules:
228*90c8c64dSAndroid Build Coastguard Worker            for rdep in self.query_rdeps(module):
229*90c8c64dSAndroid Build Coastguard Worker                rule_type, _, mod = rdep.split(" ")
230*90c8c64dSAndroid Build Coastguard Worker                if rule_type == "rust_test_" or rule_type == "rust_test":
231*90c8c64dSAndroid Build Coastguard Worker                    if self.exclude_module(mod):
232*90c8c64dSAndroid Build Coastguard Worker                        continue
233*90c8c64dSAndroid Build Coastguard Worker                    path_match = path_pat.match(mod)
234*90c8c64dSAndroid Build Coastguard Worker                    if path_match or not EXTERNAL_PAT.match(mod):
235*90c8c64dSAndroid Build Coastguard Worker                        rdep_path = mod.split(":")[0]
236*90c8c64dSAndroid Build Coastguard Worker                        rdep_test = mod.split(":")[1].split("--")[0]
237*90c8c64dSAndroid Build Coastguard Worker                        mapping_path = self.test_to_test_mapping(env, rdep_path, rdep_test)
238*90c8c64dSAndroid Build Coastguard Worker                        # Only include tests directly if they're local to the project.
239*90c8c64dSAndroid Build Coastguard Worker                        if (mapping_path is not None) and exclude_dir.endswith(mapping_path):
240*90c8c64dSAndroid Build Coastguard Worker                            rdep_tests.add(rdep_test)
241*90c8c64dSAndroid Build Coastguard Worker                        # All other tests are included by path.
242*90c8c64dSAndroid Build Coastguard Worker                        elif mapping_path is not None:
243*90c8c64dSAndroid Build Coastguard Worker                            rdep_dirs.add(mapping_path)
244*90c8c64dSAndroid Build Coastguard Worker                    else:
245*90c8c64dSAndroid Build Coastguard Worker                        label_match = LABEL_PAT.match(mod)
246*90c8c64dSAndroid Build Coastguard Worker                        if label_match:
247*90c8c64dSAndroid Build Coastguard Worker                            rdep_dirs.add(label_match.group(1))
248*90c8c64dSAndroid Build Coastguard Worker        return (rdep_tests, rdep_dirs)
249*90c8c64dSAndroid Build Coastguard Worker
250*90c8c64dSAndroid Build Coastguard Worker
251*90c8c64dSAndroid Build Coastguard Workerclass Package(object):
252*90c8c64dSAndroid Build Coastguard Worker    """A Bazel package.
253*90c8c64dSAndroid Build Coastguard Worker
254*90c8c64dSAndroid Build Coastguard Worker    Attributes:
255*90c8c64dSAndroid Build Coastguard Worker      dir: The absolute path to this package.
256*90c8c64dSAndroid Build Coastguard Worker      dir_rel: The relative path to this package.
257*90c8c64dSAndroid Build Coastguard Worker      rdep_tests: The list of computed reverse dependencies.
258*90c8c64dSAndroid Build Coastguard Worker      rdep_dirs: The list of computed reverse dependency directories.
259*90c8c64dSAndroid Build Coastguard Worker    """
260*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, path, env, bazel):
261*90c8c64dSAndroid Build Coastguard Worker        """Constructor.
262*90c8c64dSAndroid Build Coastguard Worker
263*90c8c64dSAndroid Build Coastguard Worker        Note that the current directory is changed to the package location when
264*90c8c64dSAndroid Build Coastguard Worker        called.
265*90c8c64dSAndroid Build Coastguard Worker
266*90c8c64dSAndroid Build Coastguard Worker        Args:
267*90c8c64dSAndroid Build Coastguard Worker          path: Path to the package.
268*90c8c64dSAndroid Build Coastguard Worker          env: An instance of Env.
269*90c8c64dSAndroid Build Coastguard Worker          bazel: An instance of Bazel.
270*90c8c64dSAndroid Build Coastguard Worker
271*90c8c64dSAndroid Build Coastguard Worker        Raises:
272*90c8c64dSAndroid Build Coastguard Worker          UpdaterException: the package does not appear to belong to the
273*90c8c64dSAndroid Build Coastguard Worker            current repository.
274*90c8c64dSAndroid Build Coastguard Worker        """
275*90c8c64dSAndroid Build Coastguard Worker        self.dir = path
276*90c8c64dSAndroid Build Coastguard Worker        try:
277*90c8c64dSAndroid Build Coastguard Worker            self.dir_rel = self.dir.split(env.ANDROID_BUILD_TOP)[1]
278*90c8c64dSAndroid Build Coastguard Worker        except IndexError:
279*90c8c64dSAndroid Build Coastguard Worker            raise UpdaterException('The path ' + self.dir + ' is not under ' +
280*90c8c64dSAndroid Build Coastguard Worker                            env.ANDROID_BUILD_TOP + '; You must be in the '
281*90c8c64dSAndroid Build Coastguard Worker                            'directory of a crate or pass its absolute path '
282*90c8c64dSAndroid Build Coastguard Worker                            'as the argument.')
283*90c8c64dSAndroid Build Coastguard Worker
284*90c8c64dSAndroid Build Coastguard Worker        # Move to the package_directory.
285*90c8c64dSAndroid Build Coastguard Worker        os.chdir(self.dir)
286*90c8c64dSAndroid Build Coastguard Worker        modules = bazel.query_modules(self.dir_rel)
287*90c8c64dSAndroid Build Coastguard Worker        (self.rdep_tests, self.rdep_dirs) = bazel.query_rdep_tests_dirs(env, modules,
288*90c8c64dSAndroid Build Coastguard Worker                                                                        self.dir_rel, self.dir)
289*90c8c64dSAndroid Build Coastguard Worker
290*90c8c64dSAndroid Build Coastguard Worker    def get_rdep_tests_dirs(self):
291*90c8c64dSAndroid Build Coastguard Worker        return (self.rdep_tests, self.rdep_dirs)
292*90c8c64dSAndroid Build Coastguard Worker
293*90c8c64dSAndroid Build Coastguard Worker
294*90c8c64dSAndroid Build Coastguard Workerclass TestMapping(object):
295*90c8c64dSAndroid Build Coastguard Worker    """A TEST_MAPPING file.
296*90c8c64dSAndroid Build Coastguard Worker
297*90c8c64dSAndroid Build Coastguard Worker    Attributes:
298*90c8c64dSAndroid Build Coastguard Worker      package: The package associated with this TEST_MAPPING file.
299*90c8c64dSAndroid Build Coastguard Worker    """
300*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, env, bazel, path):
301*90c8c64dSAndroid Build Coastguard Worker        """Constructor.
302*90c8c64dSAndroid Build Coastguard Worker
303*90c8c64dSAndroid Build Coastguard Worker        Args:
304*90c8c64dSAndroid Build Coastguard Worker          env: An instance of Env.
305*90c8c64dSAndroid Build Coastguard Worker          bazel: An instance of Bazel.
306*90c8c64dSAndroid Build Coastguard Worker          path: The absolute path to the package.
307*90c8c64dSAndroid Build Coastguard Worker        """
308*90c8c64dSAndroid Build Coastguard Worker        self.package = Package(path, env, bazel)
309*90c8c64dSAndroid Build Coastguard Worker
310*90c8c64dSAndroid Build Coastguard Worker    def create(self):
311*90c8c64dSAndroid Build Coastguard Worker        """Generates the TEST_MAPPING file."""
312*90c8c64dSAndroid Build Coastguard Worker        (tests, dirs) = self.package.get_rdep_tests_dirs()
313*90c8c64dSAndroid Build Coastguard Worker        if not bool(tests) and not bool(dirs):
314*90c8c64dSAndroid Build Coastguard Worker            if os.path.isfile('TEST_MAPPING'):
315*90c8c64dSAndroid Build Coastguard Worker                os.remove('TEST_MAPPING')
316*90c8c64dSAndroid Build Coastguard Worker            return
317*90c8c64dSAndroid Build Coastguard Worker        test_mapping = self.tests_dirs_to_mapping(tests, dirs)
318*90c8c64dSAndroid Build Coastguard Worker        self.write_test_mapping(test_mapping)
319*90c8c64dSAndroid Build Coastguard Worker
320*90c8c64dSAndroid Build Coastguard Worker    def tests_dirs_to_mapping(self, tests, dirs):
321*90c8c64dSAndroid Build Coastguard Worker        """Translate the test list into a dictionary."""
322*90c8c64dSAndroid Build Coastguard Worker        test_mapping = {"imports": []}
323*90c8c64dSAndroid Build Coastguard Worker        config = None
324*90c8c64dSAndroid Build Coastguard Worker        if os.path.isfile(os.path.join(self.package.dir, "test_mapping_config.json")):
325*90c8c64dSAndroid Build Coastguard Worker            with open(os.path.join(self.package.dir, "test_mapping_config.json"), 'r') as fd:
326*90c8c64dSAndroid Build Coastguard Worker                config = json.load(fd)
327*90c8c64dSAndroid Build Coastguard Worker
328*90c8c64dSAndroid Build Coastguard Worker        for test_group in TEST_GROUPS:
329*90c8c64dSAndroid Build Coastguard Worker            test_mapping[test_group] = []
330*90c8c64dSAndroid Build Coastguard Worker            for test in tests:
331*90c8c64dSAndroid Build Coastguard Worker                if test in TEST_EXCLUDE:
332*90c8c64dSAndroid Build Coastguard Worker                    continue
333*90c8c64dSAndroid Build Coastguard Worker                if config and 'postsubmit_tests' in config:
334*90c8c64dSAndroid Build Coastguard Worker                    if test in config['postsubmit_tests'] and 'postsubmit' not in test_group:
335*90c8c64dSAndroid Build Coastguard Worker                        continue
336*90c8c64dSAndroid Build Coastguard Worker                    if test not in config['postsubmit_tests'] and 'postsubmit' in test_group:
337*90c8c64dSAndroid Build Coastguard Worker                        continue
338*90c8c64dSAndroid Build Coastguard Worker                else:
339*90c8c64dSAndroid Build Coastguard Worker                    if 'postsubmit' in test_group:
340*90c8c64dSAndroid Build Coastguard Worker                        # If postsubmit_tests is not configured, do not place
341*90c8c64dSAndroid Build Coastguard Worker                        # anything in postsubmit - presubmit groups are
342*90c8c64dSAndroid Build Coastguard Worker                        # automatically included in postsubmit in CI.
343*90c8c64dSAndroid Build Coastguard Worker                        continue
344*90c8c64dSAndroid Build Coastguard Worker                if test in TEST_OPTIONS:
345*90c8c64dSAndroid Build Coastguard Worker                    test_mapping[test_group].append({"name": test, "options": TEST_OPTIONS[test]})
346*90c8c64dSAndroid Build Coastguard Worker                else:
347*90c8c64dSAndroid Build Coastguard Worker                    test_mapping[test_group].append({"name": test})
348*90c8c64dSAndroid Build Coastguard Worker            test_mapping[test_group] = sorted(test_mapping[test_group], key=lambda t: t["name"])
349*90c8c64dSAndroid Build Coastguard Worker
350*90c8c64dSAndroid Build Coastguard Worker        for dir in dirs:
351*90c8c64dSAndroid Build Coastguard Worker            test_mapping["imports"].append({"path": dir})
352*90c8c64dSAndroid Build Coastguard Worker        test_mapping["imports"] = sorted(test_mapping["imports"], key=lambda t: t["path"])
353*90c8c64dSAndroid Build Coastguard Worker        test_mapping = {section: entry for (section, entry) in test_mapping.items() if entry}
354*90c8c64dSAndroid Build Coastguard Worker        return test_mapping
355*90c8c64dSAndroid Build Coastguard Worker
356*90c8c64dSAndroid Build Coastguard Worker    def write_test_mapping(self, test_mapping):
357*90c8c64dSAndroid Build Coastguard Worker        """Writes the TEST_MAPPING file."""
358*90c8c64dSAndroid Build Coastguard Worker        with open("TEST_MAPPING", "w") as json_file:
359*90c8c64dSAndroid Build Coastguard Worker            json_file.write("// Generated by update_crate_tests.py for tests that depend on this crate.\n")
360*90c8c64dSAndroid Build Coastguard Worker            json.dump(test_mapping, json_file, indent=2, separators=(',', ': '), sort_keys=True)
361*90c8c64dSAndroid Build Coastguard Worker            json_file.write("\n")
362*90c8c64dSAndroid Build Coastguard Worker        print("TEST_MAPPING successfully updated for %s!" % self.package.dir_rel)
363*90c8c64dSAndroid Build Coastguard Worker
364*90c8c64dSAndroid Build Coastguard Worker
365*90c8c64dSAndroid Build Coastguard Workerdef parse_args():
366*90c8c64dSAndroid Build Coastguard Worker    parser = argparse.ArgumentParser('update_crate_tests')
367*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument('paths',
368*90c8c64dSAndroid Build Coastguard Worker                        nargs='*',
369*90c8c64dSAndroid Build Coastguard Worker                        help='Absolute or relative paths of the projects as globs.')
370*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument('--branch_and_commit',
371*90c8c64dSAndroid Build Coastguard Worker                        action='store_true',
372*90c8c64dSAndroid Build Coastguard Worker                        help='Starts a new branch and commit changes.')
373*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument('--push_change',
374*90c8c64dSAndroid Build Coastguard Worker                        action='store_true',
375*90c8c64dSAndroid Build Coastguard Worker                        help='Pushes change to Gerrit.')
376*90c8c64dSAndroid Build Coastguard Worker    return parser.parse_args()
377*90c8c64dSAndroid Build Coastguard Worker
378*90c8c64dSAndroid Build Coastguard Workerdef main():
379*90c8c64dSAndroid Build Coastguard Worker    args = parse_args()
380*90c8c64dSAndroid Build Coastguard Worker    paths = args.paths if len(args.paths) > 0 else [os.getcwd()]
381*90c8c64dSAndroid Build Coastguard Worker    # We want to use glob to get all the paths, so we first convert to absolute.
382*90c8c64dSAndroid Build Coastguard Worker    paths = [Path(path).resolve() for path in paths]
383*90c8c64dSAndroid Build Coastguard Worker    paths = sorted([path for abs_path in paths
384*90c8c64dSAndroid Build Coastguard Worker                    for path in glob.glob(str(abs_path))])
385*90c8c64dSAndroid Build Coastguard Worker
386*90c8c64dSAndroid Build Coastguard Worker    env = Env()
387*90c8c64dSAndroid Build Coastguard Worker    bazel = Bazel(env)
388*90c8c64dSAndroid Build Coastguard Worker    for path in paths:
389*90c8c64dSAndroid Build Coastguard Worker        try:
390*90c8c64dSAndroid Build Coastguard Worker            test_mapping = TestMapping(env, bazel, path)
391*90c8c64dSAndroid Build Coastguard Worker            test_mapping.create()
392*90c8c64dSAndroid Build Coastguard Worker            changed = (subprocess.call(['git', 'diff', '--quiet']) == 1)
393*90c8c64dSAndroid Build Coastguard Worker            untracked = (os.path.isfile('TEST_MAPPING') and
394*90c8c64dSAndroid Build Coastguard Worker                         (subprocess.run(['git', 'ls-files', '--error-unmatch', 'TEST_MAPPING'],
395*90c8c64dSAndroid Build Coastguard Worker                                         stderr=subprocess.DEVNULL,
396*90c8c64dSAndroid Build Coastguard Worker                                         stdout=subprocess.DEVNULL).returncode == 1))
397*90c8c64dSAndroid Build Coastguard Worker            if args.branch_and_commit and (changed or untracked):
398*90c8c64dSAndroid Build Coastguard Worker                subprocess.check_output(['repo', 'start',
399*90c8c64dSAndroid Build Coastguard Worker                                         'tmp_auto_test_mapping', '.'])
400*90c8c64dSAndroid Build Coastguard Worker                subprocess.check_output(['git', 'add', 'TEST_MAPPING'])
401*90c8c64dSAndroid Build Coastguard Worker                # test_mapping_config.json is not always present
402*90c8c64dSAndroid Build Coastguard Worker                subprocess.call(['git', 'add', 'test_mapping_config.json'],
403*90c8c64dSAndroid Build Coastguard Worker                                stderr=subprocess.DEVNULL,
404*90c8c64dSAndroid Build Coastguard Worker                                stdout=subprocess.DEVNULL)
405*90c8c64dSAndroid Build Coastguard Worker                subprocess.check_output(['git', 'commit', '-m',
406*90c8c64dSAndroid Build Coastguard Worker                                         'Update TEST_MAPPING\n\nTest: None'])
407*90c8c64dSAndroid Build Coastguard Worker            if args.push_change and (changed or untracked):
408*90c8c64dSAndroid Build Coastguard Worker                date = datetime.today().strftime('%m-%d')
409*90c8c64dSAndroid Build Coastguard Worker                subprocess.check_output(['git', 'push', 'aosp', 'HEAD:refs/for/master',
410*90c8c64dSAndroid Build Coastguard Worker                                         '-o', 'topic=test-mapping-%s' % date])
411*90c8c64dSAndroid Build Coastguard Worker        except (UpdaterException, subprocess.CalledProcessError) as err:
412*90c8c64dSAndroid Build Coastguard Worker            sys.exit("Error: " + str(err))
413*90c8c64dSAndroid Build Coastguard Worker
414*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__':
415*90c8c64dSAndroid Build Coastguard Worker  main()
416