1#!/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. 16"""Add tests to TEST_MAPPING. Include tests for reverse dependencies.""" 17import json 18import os 19import platform 20import subprocess 21import sys 22 23test_options = { 24 "ring_device_test_tests_digest_tests": [{"test-timeout": "600000"}], 25 "ring_device_test_src_lib": [{"test-timeout": "100000"}], 26} 27test_exclude = [ 28 "aidl_test_rust_client", 29 "aidl_test_rust_service" 30 ] 31exclude_paths = [ 32 "//external/adhd", 33 "//external/crosvm", 34 "//external/libchromeos-rs", 35 "//external/vm_tools" 36 ] 37 38class Env(object): 39 def __init__(self, path): 40 try: 41 self.ANDROID_BUILD_TOP = os.environ['ANDROID_BUILD_TOP'] 42 except: 43 sys.exit('ERROR: this script must be run from an Android tree.') 44 if path == None: 45 self.cwd = os.getcwd() 46 else: 47 self.cwd = path 48 try: 49 self.cwd_relative = self.cwd.split(self.ANDROID_BUILD_TOP)[1] 50 self.setup = True 51 except: 52 # Mark setup as failed if a path to a rust crate is not provided. 53 self.setup = False 54 55class Bazel(object): 56 # set up the Bazel queryview 57 def __init__(self, env): 58 os.chdir(env.ANDROID_BUILD_TOP) 59 print("Building Bazel Queryview. This can take a couple of minutes...") 60 cmd = "./build/soong/soong_ui.bash --build-mode --all-modules --dir=. queryview" 61 try: 62 out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) 63 self.setup = True 64 except subprocess.CalledProcessError as e: 65 print("Error: Unable to update TEST_MAPPING due to the following build error:") 66 print(e.output) 67 # Mark setup as failed if the Bazel queryview fails to build. 68 self.setup = False 69 os.chdir(env.cwd) 70 71 def path(self): 72 # Only tested on Linux. 73 if platform.system() != 'Linux': 74 sys.exit('ERROR: this script has only been tested on Linux.') 75 return "/usr/bin/bazel" 76 77 # Return all modules for a given path. 78 def query_modules(self, path): 79 with open(os.devnull, 'wb') as DEVNULL: 80 cmd = self.path() + " query --config=queryview /" + path + ":all" 81 out = subprocess.check_output(cmd, shell=True, stderr=DEVNULL, text=True).strip().split("\n") 82 modules = set() 83 for line in out: 84 # speed up by excluding unused modules. 85 if "windows_x86" in line: 86 continue 87 modules.add(line) 88 return modules 89 90 # Return all reverse dependencies for a single module. 91 def query_rdeps(self, module): 92 with open(os.devnull, 'wb') as DEVNULL: 93 cmd = (self.path() + " query --config=queryview \'rdeps(//..., " + 94 module + ")\' --output=label_kind") 95 out = (subprocess.check_output(cmd, shell=True, stderr=DEVNULL, text=True) 96 .strip().split("\n")) 97 if '' in out: 98 out.remove('') 99 return out 100 101 def exclude_module(self, module): 102 for path in exclude_paths: 103 if module.startswith(path): 104 return True 105 return False 106 107 # Return all reverse dependency tests for modules in this package. 108 def query_rdep_tests(self, modules): 109 rdep_tests = set() 110 for module in modules: 111 for rdep in self.query_rdeps(module): 112 rule_type, tmp, mod = rdep.split(" ") 113 if rule_type == "rust_test_" or rule_type == "rust_test": 114 if self.exclude_module(mod) == False: 115 rdep_tests.add(mod.split(":")[1].split("--")[0]) 116 return rdep_tests 117 118 119class Crate(object): 120 def __init__(self, path, bazel): 121 self.modules = bazel.query_modules(path) 122 self.rdep_tests = bazel.query_rdep_tests(self.modules) 123 124 def get_rdep_tests(self): 125 return self.rdep_tests 126 127 128class TestMapping(object): 129 def __init__(self, path): 130 self.env = Env(path) 131 self.bazel = Bazel(self.env) 132 133 def create_test_mapping(self, path): 134 if self.env.setup == False or self.bazel.setup == False: 135 return 136 tests = self.get_tests(path) 137 if not bool(tests): 138 return 139 test_mapping = self.tests_to_mapping(tests) 140 self.write_test_mapping(test_mapping) 141 142 def get_tests(self, path): 143 # for each path collect local Rust modules. 144 if path is not None and path != "": 145 return Crate(self.env.cwd_relative + "/" + path, self.bazel).get_rdep_tests() 146 else: 147 return Crate(self.env.cwd_relative, self.bazel).get_rdep_tests() 148 149 def tests_to_mapping(self, tests): 150 test_mapping = {"presubmit": []} 151 for test in tests: 152 if test in test_exclude: 153 continue 154 if test in test_options: 155 test_mapping["presubmit"].append({"name": test, "options": test_options[test]}) 156 else: 157 test_mapping["presubmit"].append({"name": test}) 158 test_mapping["presubmit"] = sorted(test_mapping["presubmit"], key=lambda t: t["name"]) 159 return test_mapping 160 161 def write_test_mapping(self, test_mapping): 162 with open("TEST_MAPPING", "w") as json_file: 163 json_file.write("// Generated by update_crate_tests.py for tests that depend on this crate.\n") 164 json.dump(test_mapping, json_file, indent=2, separators=(',', ': '), sort_keys=True) 165 json_file.write("\n") 166 print("TEST_MAPPING successfully updated!") 167 168def main(): 169 if len(sys.argv) == 2: 170 path = sys.argv[1] 171 else: 172 path = None 173 TestMapping(path).create_test_mapping(None) 174 175if __name__ == '__main__': 176 main() 177