• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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