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