xref: /aosp_15_r20/build/make/ci/test_discovery_agent.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker# Copyright 2024, The Android Open Source Project
2*9e94795aSAndroid Build Coastguard Worker#
3*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*9e94795aSAndroid Build Coastguard Worker#
7*9e94795aSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*9e94795aSAndroid Build Coastguard Worker#
9*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*9e94795aSAndroid Build Coastguard Worker# limitations under the License.
14*9e94795aSAndroid Build Coastguard Worker"""Test discovery agent that uses TradeFed to discover test artifacts."""
15*9e94795aSAndroid Build Coastguard Workerimport glob
16*9e94795aSAndroid Build Coastguard Workerimport json
17*9e94795aSAndroid Build Coastguard Workerimport logging
18*9e94795aSAndroid Build Coastguard Workerimport os
19*9e94795aSAndroid Build Coastguard Workerimport subprocess
20*9e94795aSAndroid Build Coastguard Worker
21*9e94795aSAndroid Build Coastguard Worker
22*9e94795aSAndroid Build Coastguard Workerclass TestDiscoveryAgent:
23*9e94795aSAndroid Build Coastguard Worker  """Test discovery agent."""
24*9e94795aSAndroid Build Coastguard Worker
25*9e94795aSAndroid Build Coastguard Worker  _TRADEFED_PREBUILT_JAR_RELATIVE_PATH = (
26*9e94795aSAndroid Build Coastguard Worker      "vendor/google_tradefederation/prebuilts/filegroups/google-tradefed/"
27*9e94795aSAndroid Build Coastguard Worker  )
28*9e94795aSAndroid Build Coastguard Worker
29*9e94795aSAndroid Build Coastguard Worker  _TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY = "NoPossibleTestDiscovery"
30*9e94795aSAndroid Build Coastguard Worker
31*9e94795aSAndroid Build Coastguard Worker  _TRADEFED_TEST_ZIP_REGEXES_LIST_KEY = "TestZipRegexes"
32*9e94795aSAndroid Build Coastguard Worker
33*9e94795aSAndroid Build Coastguard Worker  _TRADEFED_DISCOVERY_OUTPUT_FILE_NAME = "test_discovery_agent.txt"
34*9e94795aSAndroid Build Coastguard Worker
35*9e94795aSAndroid Build Coastguard Worker  def __init__(
36*9e94795aSAndroid Build Coastguard Worker      self,
37*9e94795aSAndroid Build Coastguard Worker      tradefed_args: list[str],
38*9e94795aSAndroid Build Coastguard Worker      test_mapping_zip_path: str = "",
39*9e94795aSAndroid Build Coastguard Worker      tradefed_jar_revelant_files_path: str = _TRADEFED_PREBUILT_JAR_RELATIVE_PATH,
40*9e94795aSAndroid Build Coastguard Worker  ):
41*9e94795aSAndroid Build Coastguard Worker    self.tradefed_args = tradefed_args
42*9e94795aSAndroid Build Coastguard Worker    self.test_mapping_zip_path = test_mapping_zip_path
43*9e94795aSAndroid Build Coastguard Worker    self.tradefed_jar_relevant_files_path = tradefed_jar_revelant_files_path
44*9e94795aSAndroid Build Coastguard Worker
45*9e94795aSAndroid Build Coastguard Worker  def discover_test_zip_regexes(self) -> list[str]:
46*9e94795aSAndroid Build Coastguard Worker    """Discover test zip regexes from TradeFed.
47*9e94795aSAndroid Build Coastguard Worker
48*9e94795aSAndroid Build Coastguard Worker    Returns:
49*9e94795aSAndroid Build Coastguard Worker      A list of test zip regexes that TF is going to try to pull files from.
50*9e94795aSAndroid Build Coastguard Worker    """
51*9e94795aSAndroid Build Coastguard Worker    test_discovery_output_file_name = os.path.join(
52*9e94795aSAndroid Build Coastguard Worker        os.environ.get('TOP'), 'out', self._TRADEFED_DISCOVERY_OUTPUT_FILE_NAME
53*9e94795aSAndroid Build Coastguard Worker    )
54*9e94795aSAndroid Build Coastguard Worker    with open(
55*9e94795aSAndroid Build Coastguard Worker        test_discovery_output_file_name, mode="w+t"
56*9e94795aSAndroid Build Coastguard Worker    ) as test_discovery_output_file:
57*9e94795aSAndroid Build Coastguard Worker      java_args = []
58*9e94795aSAndroid Build Coastguard Worker      java_args.append("prebuilts/jdk/jdk21/linux-x86/bin/java")
59*9e94795aSAndroid Build Coastguard Worker      java_args.append("-cp")
60*9e94795aSAndroid Build Coastguard Worker      java_args.append(
61*9e94795aSAndroid Build Coastguard Worker          self.create_classpath(self.tradefed_jar_relevant_files_path)
62*9e94795aSAndroid Build Coastguard Worker      )
63*9e94795aSAndroid Build Coastguard Worker      java_args.append(
64*9e94795aSAndroid Build Coastguard Worker          "com.android.tradefed.observatory.TestZipDiscoveryExecutor"
65*9e94795aSAndroid Build Coastguard Worker      )
66*9e94795aSAndroid Build Coastguard Worker      java_args.extend(self.tradefed_args)
67*9e94795aSAndroid Build Coastguard Worker      env = os.environ.copy()
68*9e94795aSAndroid Build Coastguard Worker      env.update({"DISCOVERY_OUTPUT_FILE": test_discovery_output_file.name})
69*9e94795aSAndroid Build Coastguard Worker      logging.info(f"Calling test discovery with args: {java_args}")
70*9e94795aSAndroid Build Coastguard Worker      try:
71*9e94795aSAndroid Build Coastguard Worker        result = subprocess.run(args=java_args, env=env, text=True, check=True)
72*9e94795aSAndroid Build Coastguard Worker        logging.info(f"Test zip discovery output: {result.stdout}")
73*9e94795aSAndroid Build Coastguard Worker      except subprocess.CalledProcessError as e:
74*9e94795aSAndroid Build Coastguard Worker        raise TestDiscoveryError(
75*9e94795aSAndroid Build Coastguard Worker            f"Failed to run test discovery, strout: {e.stdout}, strerr:"
76*9e94795aSAndroid Build Coastguard Worker            f" {e.stderr}, returncode: {e.returncode}"
77*9e94795aSAndroid Build Coastguard Worker        )
78*9e94795aSAndroid Build Coastguard Worker      data = json.loads(test_discovery_output_file.read())
79*9e94795aSAndroid Build Coastguard Worker      logging.info(f"Test discovery result file content: {data}")
80*9e94795aSAndroid Build Coastguard Worker      if (
81*9e94795aSAndroid Build Coastguard Worker          self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY in data
82*9e94795aSAndroid Build Coastguard Worker          and data[self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY]
83*9e94795aSAndroid Build Coastguard Worker      ):
84*9e94795aSAndroid Build Coastguard Worker        raise TestDiscoveryError("No possible test discovery")
85*9e94795aSAndroid Build Coastguard Worker      if (
86*9e94795aSAndroid Build Coastguard Worker          data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY] is None
87*9e94795aSAndroid Build Coastguard Worker          or data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY] is []
88*9e94795aSAndroid Build Coastguard Worker      ):
89*9e94795aSAndroid Build Coastguard Worker        raise TestDiscoveryError("No test zip regexes returned")
90*9e94795aSAndroid Build Coastguard Worker      return data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY]
91*9e94795aSAndroid Build Coastguard Worker
92*9e94795aSAndroid Build Coastguard Worker  def discover_test_modules(self) -> list[str]:
93*9e94795aSAndroid Build Coastguard Worker    """Discover test modules from TradeFed.
94*9e94795aSAndroid Build Coastguard Worker
95*9e94795aSAndroid Build Coastguard Worker    Returns:
96*9e94795aSAndroid Build Coastguard Worker      A list of test modules that TradeFed is going to execute based on the
97*9e94795aSAndroid Build Coastguard Worker      TradeFed test args.
98*9e94795aSAndroid Build Coastguard Worker    """
99*9e94795aSAndroid Build Coastguard Worker    return []
100*9e94795aSAndroid Build Coastguard Worker
101*9e94795aSAndroid Build Coastguard Worker  def create_classpath(self, directory):
102*9e94795aSAndroid Build Coastguard Worker    """Creates a classpath string from all .jar files in the given directory.
103*9e94795aSAndroid Build Coastguard Worker
104*9e94795aSAndroid Build Coastguard Worker    Args:
105*9e94795aSAndroid Build Coastguard Worker      directory: The directory to search for .jar files.
106*9e94795aSAndroid Build Coastguard Worker
107*9e94795aSAndroid Build Coastguard Worker    Returns:
108*9e94795aSAndroid Build Coastguard Worker      A string representing the classpath, with jar files separated by the
109*9e94795aSAndroid Build Coastguard Worker      OS-specific path separator (e.g., ':' on Linux/macOS, ';' on Windows).
110*9e94795aSAndroid Build Coastguard Worker    """
111*9e94795aSAndroid Build Coastguard Worker    jar_files = glob.glob(os.path.join(directory, "*.jar"))
112*9e94795aSAndroid Build Coastguard Worker    return os.pathsep.join(jar_files)
113*9e94795aSAndroid Build Coastguard Worker
114*9e94795aSAndroid Build Coastguard Worker
115*9e94795aSAndroid Build Coastguard Workerclass TestDiscoveryError(Exception):
116*9e94795aSAndroid Build Coastguard Worker  """A TestDiscoveryErrorclass."""
117*9e94795aSAndroid Build Coastguard Worker
118*9e94795aSAndroid Build Coastguard Worker  def __init__(self, message):
119*9e94795aSAndroid Build Coastguard Worker    super().__init__(message)
120*9e94795aSAndroid Build Coastguard Worker    self.message = message
121