blob: 4099fbe030cf37113e96588734d04f69d1e93f71 [file] [log] [blame]
Mingjun Yangd448be22024-10-15 05:37:00 +00001# Copyright 2024, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000014"""Test discovery agent that uses TradeFed to discover test artifacts."""
15import glob
16import json
17import logging
18import os
19import subprocess
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000020
21
Mingjun Yangd448be22024-10-15 05:37:00 +000022class TestDiscoveryAgent:
23 """Test discovery agent."""
24
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000025 _TRADEFED_PREBUILT_JAR_RELATIVE_PATH = (
26 "vendor/google_tradefederation/prebuilts/filegroups/google-tradefed/"
Mingjun Yangd448be22024-10-15 05:37:00 +000027 )
28
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000029 _TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY = "NoPossibleTestDiscovery"
30
31 _TRADEFED_TEST_ZIP_REGEXES_LIST_KEY = "TestZipRegexes"
32
Mingjun Yang3db37902025-01-26 23:41:37 +000033 _TRADEFED_TEST_MODULES_LIST_KEY = "TestModules"
34
35 _TRADEFED_TEST_DEPENDENCIES_LIST_KEY = "TestDependencies"
36
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000037 _TRADEFED_DISCOVERY_OUTPUT_FILE_NAME = "test_discovery_agent.txt"
38
Mingjun Yangd448be22024-10-15 05:37:00 +000039 def __init__(
40 self,
41 tradefed_args: list[str],
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000042 test_mapping_zip_path: str = "",
43 tradefed_jar_revelant_files_path: str = _TRADEFED_PREBUILT_JAR_RELATIVE_PATH,
Mingjun Yangd448be22024-10-15 05:37:00 +000044 ):
45 self.tradefed_args = tradefed_args
46 self.test_mapping_zip_path = test_mapping_zip_path
47 self.tradefed_jar_relevant_files_path = tradefed_jar_revelant_files_path
48
49 def discover_test_zip_regexes(self) -> list[str]:
50 """Discover test zip regexes from TradeFed.
51
52 Returns:
53 A list of test zip regexes that TF is going to try to pull files from.
54 """
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000055 test_discovery_output_file_name = os.path.join(
Mingjun Yang3db37902025-01-26 23:41:37 +000056 os.environ.get("TOP"), "out", self._TRADEFED_DISCOVERY_OUTPUT_FILE_NAME
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000057 )
58 with open(
59 test_discovery_output_file_name, mode="w+t"
60 ) as test_discovery_output_file:
61 java_args = []
62 java_args.append("prebuilts/jdk/jdk21/linux-x86/bin/java")
63 java_args.append("-cp")
64 java_args.append(
65 self.create_classpath(self.tradefed_jar_relevant_files_path)
66 )
67 java_args.append(
68 "com.android.tradefed.observatory.TestZipDiscoveryExecutor"
69 )
70 java_args.extend(self.tradefed_args)
71 env = os.environ.copy()
72 env.update({"DISCOVERY_OUTPUT_FILE": test_discovery_output_file.name})
73 logging.info(f"Calling test discovery with args: {java_args}")
74 try:
Mingjun Yangf3fe91c2025-05-27 15:22:45 -070075 result = subprocess.run(args=java_args, env=env, text=True, check=True, stdout=subprocess.PIPE,
76 stderr=subprocess.PIPE)
Mingjun Yangd2cc6a62024-11-05 23:44:38 +000077 logging.info(f"Test zip discovery output: {result.stdout}")
78 except subprocess.CalledProcessError as e:
79 raise TestDiscoveryError(
80 f"Failed to run test discovery, strout: {e.stdout}, strerr:"
81 f" {e.stderr}, returncode: {e.returncode}"
82 )
83 data = json.loads(test_discovery_output_file.read())
84 logging.info(f"Test discovery result file content: {data}")
85 if (
86 self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY in data
87 and data[self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY]
88 ):
89 raise TestDiscoveryError("No possible test discovery")
90 if (
91 data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY] is None
92 or data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY] is []
93 ):
94 raise TestDiscoveryError("No test zip regexes returned")
95 return data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY]
Mingjun Yangd448be22024-10-15 05:37:00 +000096
Mingjun Yang3db37902025-01-26 23:41:37 +000097 def discover_test_mapping_test_modules(self) -> (list[str], list[str]):
98 """Discover test mapping test modules and dependencies from TradeFed.
Mingjun Yangd448be22024-10-15 05:37:00 +000099
100 Returns:
Mingjun Yang3db37902025-01-26 23:41:37 +0000101 A tuple that contains a list of test modules and a list of test
102 dependencies that TradeFed is going to execute based on the
Mingjun Yangd448be22024-10-15 05:37:00 +0000103 TradeFed test args.
104 """
Mingjun Yang3db37902025-01-26 23:41:37 +0000105 test_discovery_output_file_name = os.path.join(
106 os.environ.get("TOP"), "out", self._TRADEFED_DISCOVERY_OUTPUT_FILE_NAME
107 )
108 with open(
109 test_discovery_output_file_name, mode="w+t"
110 ) as test_discovery_output_file:
111 java_args = []
112 java_args.append("prebuilts/jdk/jdk21/linux-x86/bin/java")
113 java_args.append("-cp")
114 java_args.append(
115 self.create_classpath(self.tradefed_jar_relevant_files_path)
116 )
117 java_args.append(
118 "com.android.tradefed.observatory.TestMappingDiscoveryAgent"
119 )
120 java_args.extend(self.tradefed_args)
121 env = os.environ.copy()
Julien Desprezbe518142025-03-21 11:31:01 -0700122 env.update({"SKIP_JAVA_QUERY": "1"})
123 env.update({"ALLOW_EMPTY_TEST_MAPPING": "1"})
Mingjun Yang3db37902025-01-26 23:41:37 +0000124 env.update({"TF_TEST_MAPPING_ZIP_FILE": self.test_mapping_zip_path})
125 env.update({"DISCOVERY_OUTPUT_FILE": test_discovery_output_file.name})
126 logging.info(f"Calling test discovery with args: {java_args}")
127 try:
Julien Desprezbe518142025-03-21 11:31:01 -0700128 result = subprocess.run(args=java_args, env=env, text=True, check=True, stdout = subprocess.PIPE,
129 stderr = subprocess.PIPE)
Mingjun Yang3db37902025-01-26 23:41:37 +0000130 logging.info(f"Test discovery agent output: {result.stdout}")
131 except subprocess.CalledProcessError as e:
132 raise TestDiscoveryError(
Julien Desprezbe518142025-03-21 11:31:01 -0700133 f"Failed to run test discovery, stdout: {e.stdout}, stderr:"
Mingjun Yang3db37902025-01-26 23:41:37 +0000134 f" {e.stderr}, returncode: {e.returncode}"
135 )
136 data = json.loads(test_discovery_output_file.read())
137 logging.info(f"Test discovery result file content: {data}")
138 if (
139 self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY in data
140 and data[self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY]
141 ):
142 raise TestDiscoveryError("No possible test discovery")
143 if (
144 data[self._TRADEFED_TEST_MODULES_LIST_KEY] is None
145 or data[self._TRADEFED_TEST_MODULES_LIST_KEY] is []
146 ):
147 raise TestDiscoveryError("No test modules returned")
148 return (
149 data[self._TRADEFED_TEST_MODULES_LIST_KEY],
150 data[self._TRADEFED_TEST_DEPENDENCIES_LIST_KEY],
151 )
Mingjun Yangd2cc6a62024-11-05 23:44:38 +0000152
153 def create_classpath(self, directory):
154 """Creates a classpath string from all .jar files in the given directory.
155
156 Args:
157 directory: The directory to search for .jar files.
158
159 Returns:
160 A string representing the classpath, with jar files separated by the
161 OS-specific path separator (e.g., ':' on Linux/macOS, ';' on Windows).
162 """
163 jar_files = glob.glob(os.path.join(directory, "*.jar"))
164 return os.pathsep.join(jar_files)
165
166
167class TestDiscoveryError(Exception):
168 """A TestDiscoveryErrorclass."""
169
170 def __init__(self, message):
171 super().__init__(message)
172 self.message = message