xref: /aosp_15_r20/tools/asuite/atest/cli_translator.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2017, The Android Open Source Project
2*c2e18aaaSAndroid Build Coastguard Worker#
3*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*c2e18aaaSAndroid Build Coastguard Worker#
7*c2e18aaaSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*c2e18aaaSAndroid Build Coastguard Worker#
9*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License.
14*c2e18aaaSAndroid Build Coastguard Worker
15*c2e18aaaSAndroid Build Coastguard Worker"""Command Line Translator for atest."""
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=too-many-lines
18*c2e18aaaSAndroid Build Coastguard Worker
19*c2e18aaaSAndroid Build Coastguard Workerfrom __future__ import print_function
20*c2e18aaaSAndroid Build Coastguard Worker
21*c2e18aaaSAndroid Build Coastguard Workerfrom dataclasses import dataclass
22*c2e18aaaSAndroid Build Coastguard Workerimport fnmatch
23*c2e18aaaSAndroid Build Coastguard Workerimport functools
24*c2e18aaaSAndroid Build Coastguard Workerimport json
25*c2e18aaaSAndroid Build Coastguard Workerimport logging
26*c2e18aaaSAndroid Build Coastguard Workerimport os
27*c2e18aaaSAndroid Build Coastguard Workerfrom pathlib import Path
28*c2e18aaaSAndroid Build Coastguard Workerimport re
29*c2e18aaaSAndroid Build Coastguard Workerimport sys
30*c2e18aaaSAndroid Build Coastguard Workerimport threading
31*c2e18aaaSAndroid Build Coastguard Workerimport time
32*c2e18aaaSAndroid Build Coastguard Workerfrom typing import List, Set
33*c2e18aaaSAndroid Build Coastguard Worker
34*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_error
35*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_utils
36*c2e18aaaSAndroid Build Coastguard Workerfrom atest import bazel_mode
37*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants
38*c2e18aaaSAndroid Build Coastguard Workerfrom atest import rollout_control
39*c2e18aaaSAndroid Build Coastguard Workerfrom atest import test_finder_handler
40*c2e18aaaSAndroid Build Coastguard Workerfrom atest import test_mapping
41*c2e18aaaSAndroid Build Coastguard Workerfrom atest.atest_enum import DetectType, ExitCode
42*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import metrics
43*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import metrics_utils
44*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_finders import module_finder
45*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_finders import test_finder_utils
46*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_finders import test_info
47*c2e18aaaSAndroid Build Coastguard Workerfrom atest.tools import indexing
48*c2e18aaaSAndroid Build Coastguard Worker
49*c2e18aaaSAndroid Build Coastguard WorkerFUZZY_FINDER = 'FUZZY'
50*c2e18aaaSAndroid Build Coastguard WorkerCACHE_FINDER = 'CACHE'
51*c2e18aaaSAndroid Build Coastguard WorkerTESTNAME_CHARS = {'#', ':', '/'}
52*c2e18aaaSAndroid Build Coastguard Worker
53*c2e18aaaSAndroid Build Coastguard WorkerMAINLINE_LOCAL_DOC = 'go/mainline-local-build'
54*c2e18aaaSAndroid Build Coastguard Worker
55*c2e18aaaSAndroid Build Coastguard Worker# Pattern used to identify comments start with '//' or '#' in TEST_MAPPING.
56*c2e18aaaSAndroid Build Coastguard Worker_COMMENTS_RE = re.compile(r'(?m)[\s\t]*(#|//).*|(\".*?\")')
57*c2e18aaaSAndroid Build Coastguard Worker_COMMENTS = frozenset(['//', '#'])
58*c2e18aaaSAndroid Build Coastguard Worker
59*c2e18aaaSAndroid Build Coastguard Worker
60*c2e18aaaSAndroid Build Coastguard Worker@dataclass
61*c2e18aaaSAndroid Build Coastguard Workerclass TestIdentifier:
62*c2e18aaaSAndroid Build Coastguard Worker  """Class that stores test and the corresponding mainline modules (if any)."""
63*c2e18aaaSAndroid Build Coastguard Worker
64*c2e18aaaSAndroid Build Coastguard Worker  test_name: str
65*c2e18aaaSAndroid Build Coastguard Worker  module_names: List[str]
66*c2e18aaaSAndroid Build Coastguard Worker  binary_names: List[str]
67*c2e18aaaSAndroid Build Coastguard Worker
68*c2e18aaaSAndroid Build Coastguard Worker
69*c2e18aaaSAndroid Build Coastguard Workerclass CLITranslator:
70*c2e18aaaSAndroid Build Coastguard Worker  """CLITranslator class contains public method translate() and some private
71*c2e18aaaSAndroid Build Coastguard Worker
72*c2e18aaaSAndroid Build Coastguard Worker  helper methods. The atest tool can call the translate() method with a list
73*c2e18aaaSAndroid Build Coastguard Worker  of strings, each string referencing a test to run. Translate() will
74*c2e18aaaSAndroid Build Coastguard Worker  "translate" this list of test strings into a list of build targets and a
75*c2e18aaaSAndroid Build Coastguard Worker  list of TradeFederation run commands.
76*c2e18aaaSAndroid Build Coastguard Worker
77*c2e18aaaSAndroid Build Coastguard Worker  Translation steps for a test string reference:
78*c2e18aaaSAndroid Build Coastguard Worker      1. Narrow down the type of reference the test string could be, i.e.
79*c2e18aaaSAndroid Build Coastguard Worker         whether it could be referencing a Module, Class, Package, etc.
80*c2e18aaaSAndroid Build Coastguard Worker      2. Try to find the test files assuming the test string is one of these
81*c2e18aaaSAndroid Build Coastguard Worker         types of reference.
82*c2e18aaaSAndroid Build Coastguard Worker      3. If test files found, generate Build Targets and the Run Command.
83*c2e18aaaSAndroid Build Coastguard Worker  """
84*c2e18aaaSAndroid Build Coastguard Worker
85*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
86*c2e18aaaSAndroid Build Coastguard Worker      self,
87*c2e18aaaSAndroid Build Coastguard Worker      mod_info=None,
88*c2e18aaaSAndroid Build Coastguard Worker      print_cache_msg=True,
89*c2e18aaaSAndroid Build Coastguard Worker      bazel_mode_enabled=False,
90*c2e18aaaSAndroid Build Coastguard Worker      host=False,
91*c2e18aaaSAndroid Build Coastguard Worker      bazel_mode_features: List[bazel_mode.Features] = None,
92*c2e18aaaSAndroid Build Coastguard Worker      indexing_thread: threading.Thread = None,
93*c2e18aaaSAndroid Build Coastguard Worker  ):
94*c2e18aaaSAndroid Build Coastguard Worker    """CLITranslator constructor
95*c2e18aaaSAndroid Build Coastguard Worker
96*c2e18aaaSAndroid Build Coastguard Worker    Args:
97*c2e18aaaSAndroid Build Coastguard Worker        mod_info: ModuleInfo class that has cached module-info.json.
98*c2e18aaaSAndroid Build Coastguard Worker        print_cache_msg: Boolean whether printing clear cache message or not.
99*c2e18aaaSAndroid Build Coastguard Worker          True will print message while False won't print.
100*c2e18aaaSAndroid Build Coastguard Worker        bazel_mode_enabled: Boolean of args.bazel_mode.
101*c2e18aaaSAndroid Build Coastguard Worker        host: Boolean of args.host.
102*c2e18aaaSAndroid Build Coastguard Worker        bazel_mode_features: List of args.bazel_mode_features.
103*c2e18aaaSAndroid Build Coastguard Worker        indexing_thread: Thread of indexing.
104*c2e18aaaSAndroid Build Coastguard Worker    """
105*c2e18aaaSAndroid Build Coastguard Worker    self.mod_info = mod_info
106*c2e18aaaSAndroid Build Coastguard Worker    self.root_dir = os.getenv(constants.ANDROID_BUILD_TOP, os.sep)
107*c2e18aaaSAndroid Build Coastguard Worker    self._bazel_mode = (
108*c2e18aaaSAndroid Build Coastguard Worker        bazel_mode_enabled
109*c2e18aaaSAndroid Build Coastguard Worker        and not rollout_control.deprecate_bazel_mode.is_enabled()
110*c2e18aaaSAndroid Build Coastguard Worker    )
111*c2e18aaaSAndroid Build Coastguard Worker    self._bazel_mode_features = bazel_mode_features or []
112*c2e18aaaSAndroid Build Coastguard Worker    self._host = host
113*c2e18aaaSAndroid Build Coastguard Worker    self.enable_file_patterns = False
114*c2e18aaaSAndroid Build Coastguard Worker    self.msg = ''
115*c2e18aaaSAndroid Build Coastguard Worker    if print_cache_msg:
116*c2e18aaaSAndroid Build Coastguard Worker      self.msg = (
117*c2e18aaaSAndroid Build Coastguard Worker          '(Test info has been cached for speeding up the next '
118*c2e18aaaSAndroid Build Coastguard Worker          'run, if test info needs to be updated, please add -c '
119*c2e18aaaSAndroid Build Coastguard Worker          'to clean the old cache.)'
120*c2e18aaaSAndroid Build Coastguard Worker      )
121*c2e18aaaSAndroid Build Coastguard Worker    self.fuzzy_search = True
122*c2e18aaaSAndroid Build Coastguard Worker    self._indexing_thread = indexing_thread
123*c2e18aaaSAndroid Build Coastguard Worker
124*c2e18aaaSAndroid Build Coastguard Worker  @functools.cache
125*c2e18aaaSAndroid Build Coastguard Worker  def _wait_for_index_if_needed(self) -> None:
126*c2e18aaaSAndroid Build Coastguard Worker    """Checks indexing status and wait for it to complete if necessary."""
127*c2e18aaaSAndroid Build Coastguard Worker    if (
128*c2e18aaaSAndroid Build Coastguard Worker        not self._indexing_thread
129*c2e18aaaSAndroid Build Coastguard Worker        or not self._indexing_thread.is_alive()
130*c2e18aaaSAndroid Build Coastguard Worker        or indexing.Indices().has_all_indices()
131*c2e18aaaSAndroid Build Coastguard Worker    ):
132*c2e18aaaSAndroid Build Coastguard Worker      return
133*c2e18aaaSAndroid Build Coastguard Worker    start_wait_for_indexing = time.time()
134*c2e18aaaSAndroid Build Coastguard Worker    print('Waiting for the module indexing to complete.')
135*c2e18aaaSAndroid Build Coastguard Worker    self._indexing_thread.join()
136*c2e18aaaSAndroid Build Coastguard Worker    metrics.LocalDetectEvent(
137*c2e18aaaSAndroid Build Coastguard Worker        detect_type=DetectType.WAIT_FOR_INDEXING_MS,
138*c2e18aaaSAndroid Build Coastguard Worker        result=int(round((time.time() - start_wait_for_indexing) * 1000)),
139*c2e18aaaSAndroid Build Coastguard Worker    )
140*c2e18aaaSAndroid Build Coastguard Worker
141*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-locals
142*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-branches
143*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-statements
144*c2e18aaaSAndroid Build Coastguard Worker  def _find_test_infos(
145*c2e18aaaSAndroid Build Coastguard Worker      self, test: str, tm_test_detail: test_mapping.TestDetail
146*c2e18aaaSAndroid Build Coastguard Worker  ) -> List[test_info.TestInfo]:
147*c2e18aaaSAndroid Build Coastguard Worker    """Return set of TestInfos based on a given test.
148*c2e18aaaSAndroid Build Coastguard Worker
149*c2e18aaaSAndroid Build Coastguard Worker    Args:
150*c2e18aaaSAndroid Build Coastguard Worker        test: A string representing test references.
151*c2e18aaaSAndroid Build Coastguard Worker        tm_test_detail: The TestDetail of test configured in TEST_MAPPING files.
152*c2e18aaaSAndroid Build Coastguard Worker
153*c2e18aaaSAndroid Build Coastguard Worker    Returns:
154*c2e18aaaSAndroid Build Coastguard Worker        List of TestInfos based on the given test.
155*c2e18aaaSAndroid Build Coastguard Worker    """
156*c2e18aaaSAndroid Build Coastguard Worker    test_infos = []
157*c2e18aaaSAndroid Build Coastguard Worker    test_find_starts = time.time()
158*c2e18aaaSAndroid Build Coastguard Worker    test_found = False
159*c2e18aaaSAndroid Build Coastguard Worker    test_finders = []
160*c2e18aaaSAndroid Build Coastguard Worker    test_info_str = ''
161*c2e18aaaSAndroid Build Coastguard Worker    find_test_err_msg = None
162*c2e18aaaSAndroid Build Coastguard Worker    test_identifier = parse_test_identifier(test)
163*c2e18aaaSAndroid Build Coastguard Worker    test_name = test_identifier.test_name
164*c2e18aaaSAndroid Build Coastguard Worker    if not self._verified_mainline_modules(test_identifier):
165*c2e18aaaSAndroid Build Coastguard Worker      return test_infos
166*c2e18aaaSAndroid Build Coastguard Worker    find_methods = test_finder_handler.get_find_methods_for_test(
167*c2e18aaaSAndroid Build Coastguard Worker        self.mod_info, test
168*c2e18aaaSAndroid Build Coastguard Worker    )
169*c2e18aaaSAndroid Build Coastguard Worker    if self._bazel_mode:
170*c2e18aaaSAndroid Build Coastguard Worker      find_methods = [
171*c2e18aaaSAndroid Build Coastguard Worker          bazel_mode.create_new_finder(
172*c2e18aaaSAndroid Build Coastguard Worker              self.mod_info,
173*c2e18aaaSAndroid Build Coastguard Worker              f,
174*c2e18aaaSAndroid Build Coastguard Worker              host=self._host,
175*c2e18aaaSAndroid Build Coastguard Worker              enabled_features=self._bazel_mode_features,
176*c2e18aaaSAndroid Build Coastguard Worker          )
177*c2e18aaaSAndroid Build Coastguard Worker          for f in find_methods
178*c2e18aaaSAndroid Build Coastguard Worker      ]
179*c2e18aaaSAndroid Build Coastguard Worker
180*c2e18aaaSAndroid Build Coastguard Worker    for finder in find_methods:
181*c2e18aaaSAndroid Build Coastguard Worker      # Ideally whether a find method requires indexing should be defined within the
182*c2e18aaaSAndroid Build Coastguard Worker      # finder class itself. However the current finder class design prevent
183*c2e18aaaSAndroid Build Coastguard Worker      # us from defining property without a bigger change. Here we use a tuple
184*c2e18aaaSAndroid Build Coastguard Worker      # to specify the finders that doesn't require indexing and leave the
185*c2e18aaaSAndroid Build Coastguard Worker      # class redesign work for future work.
186*c2e18aaaSAndroid Build Coastguard Worker      if finder.finder_info not in (
187*c2e18aaaSAndroid Build Coastguard Worker          'EXAMPLE',
188*c2e18aaaSAndroid Build Coastguard Worker          'CACHE',
189*c2e18aaaSAndroid Build Coastguard Worker          'MODULE',
190*c2e18aaaSAndroid Build Coastguard Worker          'INTEGRATION',
191*c2e18aaaSAndroid Build Coastguard Worker          'CONFIG',
192*c2e18aaaSAndroid Build Coastguard Worker          'SUITE_PLAN',
193*c2e18aaaSAndroid Build Coastguard Worker      ):
194*c2e18aaaSAndroid Build Coastguard Worker        self._wait_for_index_if_needed()
195*c2e18aaaSAndroid Build Coastguard Worker
196*c2e18aaaSAndroid Build Coastguard Worker      # For tests in TEST_MAPPING, find method is only related to
197*c2e18aaaSAndroid Build Coastguard Worker      # test name, so the details can be set after test_info object
198*c2e18aaaSAndroid Build Coastguard Worker      # is created.
199*c2e18aaaSAndroid Build Coastguard Worker      try:
200*c2e18aaaSAndroid Build Coastguard Worker        found_test_infos = finder.find_method(
201*c2e18aaaSAndroid Build Coastguard Worker            finder.test_finder_instance, test_name
202*c2e18aaaSAndroid Build Coastguard Worker        )
203*c2e18aaaSAndroid Build Coastguard Worker      except atest_error.TestDiscoveryException as e:
204*c2e18aaaSAndroid Build Coastguard Worker        find_test_err_msg = e
205*c2e18aaaSAndroid Build Coastguard Worker      if found_test_infos:
206*c2e18aaaSAndroid Build Coastguard Worker        finder_info = finder.finder_info
207*c2e18aaaSAndroid Build Coastguard Worker        for t_info in found_test_infos:
208*c2e18aaaSAndroid Build Coastguard Worker          test_deps = set()
209*c2e18aaaSAndroid Build Coastguard Worker          if self.mod_info:
210*c2e18aaaSAndroid Build Coastguard Worker            test_deps = self.mod_info.get_install_module_dependency(
211*c2e18aaaSAndroid Build Coastguard Worker                t_info.test_name
212*c2e18aaaSAndroid Build Coastguard Worker            )
213*c2e18aaaSAndroid Build Coastguard Worker            logging.debug(
214*c2e18aaaSAndroid Build Coastguard Worker                '(%s) Test dependencies: %s', t_info.test_name, test_deps
215*c2e18aaaSAndroid Build Coastguard Worker            )
216*c2e18aaaSAndroid Build Coastguard Worker          if tm_test_detail:
217*c2e18aaaSAndroid Build Coastguard Worker            t_info.data[constants.TI_MODULE_ARG] = tm_test_detail.options
218*c2e18aaaSAndroid Build Coastguard Worker            t_info.from_test_mapping = True
219*c2e18aaaSAndroid Build Coastguard Worker            t_info.host = tm_test_detail.host
220*c2e18aaaSAndroid Build Coastguard Worker          if finder_info != CACHE_FINDER:
221*c2e18aaaSAndroid Build Coastguard Worker            t_info.test_finder = finder_info
222*c2e18aaaSAndroid Build Coastguard Worker          mainline_modules = test_identifier.module_names
223*c2e18aaaSAndroid Build Coastguard Worker          if mainline_modules:
224*c2e18aaaSAndroid Build Coastguard Worker            t_info.test_name = test
225*c2e18aaaSAndroid Build Coastguard Worker            # TODO(b/261607500): Replace usages of raw_test_name
226*c2e18aaaSAndroid Build Coastguard Worker            # with test_name once we can ensure that it doesn't
227*c2e18aaaSAndroid Build Coastguard Worker            # break any code that expects Mainline modules in the
228*c2e18aaaSAndroid Build Coastguard Worker            # string.
229*c2e18aaaSAndroid Build Coastguard Worker            t_info.raw_test_name = test_name
230*c2e18aaaSAndroid Build Coastguard Worker            # TODO: remove below statement when soong can also
231*c2e18aaaSAndroid Build Coastguard Worker            # parse TestConfig and inject mainline modules information
232*c2e18aaaSAndroid Build Coastguard Worker            # to module-info.
233*c2e18aaaSAndroid Build Coastguard Worker            for mod in mainline_modules:
234*c2e18aaaSAndroid Build Coastguard Worker              t_info.add_mainline_module(mod)
235*c2e18aaaSAndroid Build Coastguard Worker
236*c2e18aaaSAndroid Build Coastguard Worker          # Only add dependencies to build_targets when they are in
237*c2e18aaaSAndroid Build Coastguard Worker          # module info
238*c2e18aaaSAndroid Build Coastguard Worker          test_deps_in_mod_info = [
239*c2e18aaaSAndroid Build Coastguard Worker              test_dep
240*c2e18aaaSAndroid Build Coastguard Worker              for test_dep in test_deps
241*c2e18aaaSAndroid Build Coastguard Worker              if self.mod_info.is_module(test_dep)
242*c2e18aaaSAndroid Build Coastguard Worker          ]
243*c2e18aaaSAndroid Build Coastguard Worker          for dep in test_deps_in_mod_info:
244*c2e18aaaSAndroid Build Coastguard Worker            t_info.add_build_target(dep)
245*c2e18aaaSAndroid Build Coastguard Worker          test_infos.append(t_info)
246*c2e18aaaSAndroid Build Coastguard Worker        test_found = True
247*c2e18aaaSAndroid Build Coastguard Worker        print("Found '%s' as %s" % (atest_utils.mark_green(test), finder_info))
248*c2e18aaaSAndroid Build Coastguard Worker        if finder_info == CACHE_FINDER and test_infos:
249*c2e18aaaSAndroid Build Coastguard Worker          test_finders.append(list(test_infos)[0].test_finder)
250*c2e18aaaSAndroid Build Coastguard Worker        test_finders.append(finder_info)
251*c2e18aaaSAndroid Build Coastguard Worker        test_info_str = ','.join([str(x) for x in found_test_infos])
252*c2e18aaaSAndroid Build Coastguard Worker        break
253*c2e18aaaSAndroid Build Coastguard Worker    if not test_found:
254*c2e18aaaSAndroid Build Coastguard Worker      print('No test found for: {}'.format(atest_utils.mark_red(test)))
255*c2e18aaaSAndroid Build Coastguard Worker      if self.fuzzy_search:
256*c2e18aaaSAndroid Build Coastguard Worker        f_results = self._fuzzy_search_and_msg(test, find_test_err_msg)
257*c2e18aaaSAndroid Build Coastguard Worker        if f_results:
258*c2e18aaaSAndroid Build Coastguard Worker          test_infos.extend(f_results)
259*c2e18aaaSAndroid Build Coastguard Worker          test_found = True
260*c2e18aaaSAndroid Build Coastguard Worker          test_finders.append(FUZZY_FINDER)
261*c2e18aaaSAndroid Build Coastguard Worker    metrics.FindTestFinishEvent(
262*c2e18aaaSAndroid Build Coastguard Worker        duration=metrics_utils.convert_duration(time.time() - test_find_starts),
263*c2e18aaaSAndroid Build Coastguard Worker        success=test_found,
264*c2e18aaaSAndroid Build Coastguard Worker        test_reference=test,
265*c2e18aaaSAndroid Build Coastguard Worker        test_finders=test_finders,
266*c2e18aaaSAndroid Build Coastguard Worker        test_info=test_info_str,
267*c2e18aaaSAndroid Build Coastguard Worker    )
268*c2e18aaaSAndroid Build Coastguard Worker    # Cache test_infos by default except running with TEST_MAPPING which may
269*c2e18aaaSAndroid Build Coastguard Worker    # include customized flags and they are likely to mess up other
270*c2e18aaaSAndroid Build Coastguard Worker    # non-test_mapping tests.
271*c2e18aaaSAndroid Build Coastguard Worker    if test_infos and not tm_test_detail:
272*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.update_test_info_cache(test, test_infos)
273*c2e18aaaSAndroid Build Coastguard Worker      if self.msg:
274*c2e18aaaSAndroid Build Coastguard Worker        print(self.msg)
275*c2e18aaaSAndroid Build Coastguard Worker    return test_infos
276*c2e18aaaSAndroid Build Coastguard Worker
277*c2e18aaaSAndroid Build Coastguard Worker  def _verified_mainline_modules(self, test_identifier: TestIdentifier) -> bool:
278*c2e18aaaSAndroid Build Coastguard Worker    """Verify the test with mainline modules is acceptable.
279*c2e18aaaSAndroid Build Coastguard Worker
280*c2e18aaaSAndroid Build Coastguard Worker    The test must be a module and mainline modules are in module-info.
281*c2e18aaaSAndroid Build Coastguard Worker    The syntax rule of mainline modules will check in build process.
282*c2e18aaaSAndroid Build Coastguard Worker    The rule includes mainline modules are sorted alphabetically, no space,
283*c2e18aaaSAndroid Build Coastguard Worker    and no duplication.
284*c2e18aaaSAndroid Build Coastguard Worker
285*c2e18aaaSAndroid Build Coastguard Worker    Args:
286*c2e18aaaSAndroid Build Coastguard Worker        test_identifier: a TestIdentifier object.
287*c2e18aaaSAndroid Build Coastguard Worker
288*c2e18aaaSAndroid Build Coastguard Worker    Returns:
289*c2e18aaaSAndroid Build Coastguard Worker        True if this test is acceptable. Otherwise, print the reason and
290*c2e18aaaSAndroid Build Coastguard Worker        return False.
291*c2e18aaaSAndroid Build Coastguard Worker    """
292*c2e18aaaSAndroid Build Coastguard Worker    mainline_binaries = test_identifier.binary_names
293*c2e18aaaSAndroid Build Coastguard Worker    if not mainline_binaries:
294*c2e18aaaSAndroid Build Coastguard Worker      return True
295*c2e18aaaSAndroid Build Coastguard Worker
296*c2e18aaaSAndroid Build Coastguard Worker    # Exit earlier when any test name or mainline modules are not valid.
297*c2e18aaaSAndroid Build Coastguard Worker    if not self._valid_modules(test_identifier):
298*c2e18aaaSAndroid Build Coastguard Worker      return False
299*c2e18aaaSAndroid Build Coastguard Worker
300*c2e18aaaSAndroid Build Coastguard Worker    # Exit earlier if Atest cannot find relationship between the test and
301*c2e18aaaSAndroid Build Coastguard Worker    # the mainline binaries.
302*c2e18aaaSAndroid Build Coastguard Worker    return self._declared_mainline_modules(test_identifier)
303*c2e18aaaSAndroid Build Coastguard Worker
304*c2e18aaaSAndroid Build Coastguard Worker  def _valid_modules(self, identifier: TestIdentifier) -> bool:
305*c2e18aaaSAndroid Build Coastguard Worker    """Determine the test_name and mainline modules are modules."""
306*c2e18aaaSAndroid Build Coastguard Worker    if not self.mod_info.is_module(identifier.test_name):
307*c2e18aaaSAndroid Build Coastguard Worker      print(
308*c2e18aaaSAndroid Build Coastguard Worker          'Error: "{}" is not a testable module.'.format(
309*c2e18aaaSAndroid Build Coastguard Worker              atest_utils.mark_red(identifier.test_name)
310*c2e18aaaSAndroid Build Coastguard Worker          )
311*c2e18aaaSAndroid Build Coastguard Worker      )
312*c2e18aaaSAndroid Build Coastguard Worker      return False
313*c2e18aaaSAndroid Build Coastguard Worker
314*c2e18aaaSAndroid Build Coastguard Worker    # Exit earlier if the given mainline modules are unavailable in the
315*c2e18aaaSAndroid Build Coastguard Worker    # branch.
316*c2e18aaaSAndroid Build Coastguard Worker    unknown_modules = [
317*c2e18aaaSAndroid Build Coastguard Worker        module
318*c2e18aaaSAndroid Build Coastguard Worker        for module in identifier.module_names
319*c2e18aaaSAndroid Build Coastguard Worker        if not self.mod_info.is_module(module)
320*c2e18aaaSAndroid Build Coastguard Worker    ]
321*c2e18aaaSAndroid Build Coastguard Worker    if unknown_modules:
322*c2e18aaaSAndroid Build Coastguard Worker      print(
323*c2e18aaaSAndroid Build Coastguard Worker          'Error: Cannot find {} in module info!'.format(
324*c2e18aaaSAndroid Build Coastguard Worker              atest_utils.mark_red(', '.join(unknown_modules))
325*c2e18aaaSAndroid Build Coastguard Worker          )
326*c2e18aaaSAndroid Build Coastguard Worker      )
327*c2e18aaaSAndroid Build Coastguard Worker      return False
328*c2e18aaaSAndroid Build Coastguard Worker
329*c2e18aaaSAndroid Build Coastguard Worker    # Exit earlier if found unsupported `capex` files.
330*c2e18aaaSAndroid Build Coastguard Worker    unsupported_binaries = []
331*c2e18aaaSAndroid Build Coastguard Worker    for name in identifier.module_names:
332*c2e18aaaSAndroid Build Coastguard Worker      info = self.mod_info.get_module_info(name)
333*c2e18aaaSAndroid Build Coastguard Worker      if info.get('installed'):
334*c2e18aaaSAndroid Build Coastguard Worker        for bin in info.get('installed'):
335*c2e18aaaSAndroid Build Coastguard Worker          if not re.search(atest_utils.MAINLINE_MODULES_EXT_RE, bin):
336*c2e18aaaSAndroid Build Coastguard Worker            unsupported_binaries.append(bin)
337*c2e18aaaSAndroid Build Coastguard Worker    if unsupported_binaries:
338*c2e18aaaSAndroid Build Coastguard Worker      print(
339*c2e18aaaSAndroid Build Coastguard Worker          'The output format {} are not in a supported format; '
340*c2e18aaaSAndroid Build Coastguard Worker          'did you run mainline local setup script? '
341*c2e18aaaSAndroid Build Coastguard Worker          'Please refer to {}.'.format(
342*c2e18aaaSAndroid Build Coastguard Worker              atest_utils.mark_red(', '.join(unsupported_binaries)),
343*c2e18aaaSAndroid Build Coastguard Worker              atest_utils.mark_yellow(MAINLINE_LOCAL_DOC),
344*c2e18aaaSAndroid Build Coastguard Worker          )
345*c2e18aaaSAndroid Build Coastguard Worker      )
346*c2e18aaaSAndroid Build Coastguard Worker      return False
347*c2e18aaaSAndroid Build Coastguard Worker
348*c2e18aaaSAndroid Build Coastguard Worker    return True
349*c2e18aaaSAndroid Build Coastguard Worker
350*c2e18aaaSAndroid Build Coastguard Worker  def _declared_mainline_modules(self, identifier: TestIdentifier) -> bool:
351*c2e18aaaSAndroid Build Coastguard Worker    """Determine if all mainline modules were associated to the test."""
352*c2e18aaaSAndroid Build Coastguard Worker    test = identifier.test_name
353*c2e18aaaSAndroid Build Coastguard Worker    mainline_binaries = identifier.binary_names
354*c2e18aaaSAndroid Build Coastguard Worker    if not self.mod_info.has_mainline_modules(test, mainline_binaries):
355*c2e18aaaSAndroid Build Coastguard Worker      print(
356*c2e18aaaSAndroid Build Coastguard Worker          'Error: Mainline modules "{}" were not defined for {} in '
357*c2e18aaaSAndroid Build Coastguard Worker          'neither build file nor test config.'.format(
358*c2e18aaaSAndroid Build Coastguard Worker              atest_utils.mark_red(', '.join(mainline_binaries)),
359*c2e18aaaSAndroid Build Coastguard Worker              atest_utils.mark_red(test),
360*c2e18aaaSAndroid Build Coastguard Worker          )
361*c2e18aaaSAndroid Build Coastguard Worker      )
362*c2e18aaaSAndroid Build Coastguard Worker      return False
363*c2e18aaaSAndroid Build Coastguard Worker
364*c2e18aaaSAndroid Build Coastguard Worker    return True
365*c2e18aaaSAndroid Build Coastguard Worker
366*c2e18aaaSAndroid Build Coastguard Worker  def _fuzzy_search_and_msg(self, test, find_test_err_msg):
367*c2e18aaaSAndroid Build Coastguard Worker    """Fuzzy search and print message.
368*c2e18aaaSAndroid Build Coastguard Worker
369*c2e18aaaSAndroid Build Coastguard Worker    Args:
370*c2e18aaaSAndroid Build Coastguard Worker        test: A string representing test references
371*c2e18aaaSAndroid Build Coastguard Worker        find_test_err_msg: A string of find test error message.
372*c2e18aaaSAndroid Build Coastguard Worker
373*c2e18aaaSAndroid Build Coastguard Worker    Returns:
374*c2e18aaaSAndroid Build Coastguard Worker        A list of TestInfos if found, otherwise None.
375*c2e18aaaSAndroid Build Coastguard Worker    """
376*c2e18aaaSAndroid Build Coastguard Worker    # Currently we focus on guessing module names. Append names on
377*c2e18aaaSAndroid Build Coastguard Worker    # results if more finders support fuzzy searching.
378*c2e18aaaSAndroid Build Coastguard Worker    if atest_utils.has_chars(test, TESTNAME_CHARS):
379*c2e18aaaSAndroid Build Coastguard Worker      return None
380*c2e18aaaSAndroid Build Coastguard Worker    mod_finder = module_finder.ModuleFinder(self.mod_info)
381*c2e18aaaSAndroid Build Coastguard Worker    results = mod_finder.get_fuzzy_searching_results(test)
382*c2e18aaaSAndroid Build Coastguard Worker    if len(results) == 1 and self._confirm_running(results):
383*c2e18aaaSAndroid Build Coastguard Worker      found_test_infos = mod_finder.find_test_by_module_name(results[0])
384*c2e18aaaSAndroid Build Coastguard Worker      # found_test_infos is a list with at most 1 element.
385*c2e18aaaSAndroid Build Coastguard Worker      if found_test_infos:
386*c2e18aaaSAndroid Build Coastguard Worker        return found_test_infos
387*c2e18aaaSAndroid Build Coastguard Worker    elif len(results) > 1:
388*c2e18aaaSAndroid Build Coastguard Worker      self._print_fuzzy_searching_results(results)
389*c2e18aaaSAndroid Build Coastguard Worker    else:
390*c2e18aaaSAndroid Build Coastguard Worker      print('No matching result for {0}.'.format(test))
391*c2e18aaaSAndroid Build Coastguard Worker    if find_test_err_msg:
392*c2e18aaaSAndroid Build Coastguard Worker      print(f'{atest_utils.mark_magenta(find_test_err_msg)}\n')
393*c2e18aaaSAndroid Build Coastguard Worker    return None
394*c2e18aaaSAndroid Build Coastguard Worker
395*c2e18aaaSAndroid Build Coastguard Worker  def _get_test_infos(self, tests, test_mapping_test_details=None):
396*c2e18aaaSAndroid Build Coastguard Worker    """Return set of TestInfos based on passed in tests.
397*c2e18aaaSAndroid Build Coastguard Worker
398*c2e18aaaSAndroid Build Coastguard Worker    Args:
399*c2e18aaaSAndroid Build Coastguard Worker        tests: List of strings representing test references.
400*c2e18aaaSAndroid Build Coastguard Worker        test_mapping_test_details: List of TestDetail for tests configured in
401*c2e18aaaSAndroid Build Coastguard Worker          TEST_MAPPING files.
402*c2e18aaaSAndroid Build Coastguard Worker
403*c2e18aaaSAndroid Build Coastguard Worker    Returns:
404*c2e18aaaSAndroid Build Coastguard Worker        Set of TestInfos based on the passed in tests.
405*c2e18aaaSAndroid Build Coastguard Worker    """
406*c2e18aaaSAndroid Build Coastguard Worker    test_infos = []
407*c2e18aaaSAndroid Build Coastguard Worker    if not test_mapping_test_details:
408*c2e18aaaSAndroid Build Coastguard Worker      test_mapping_test_details = [None] * len(tests)
409*c2e18aaaSAndroid Build Coastguard Worker    for test, tm_test_detail in zip(tests, test_mapping_test_details):
410*c2e18aaaSAndroid Build Coastguard Worker      found_test_infos = self._find_test_infos(test, tm_test_detail)
411*c2e18aaaSAndroid Build Coastguard Worker      test_infos.extend(found_test_infos)
412*c2e18aaaSAndroid Build Coastguard Worker    return test_infos
413*c2e18aaaSAndroid Build Coastguard Worker
414*c2e18aaaSAndroid Build Coastguard Worker  def _confirm_running(self, results):
415*c2e18aaaSAndroid Build Coastguard Worker    """Listen to an answer from raw input.
416*c2e18aaaSAndroid Build Coastguard Worker
417*c2e18aaaSAndroid Build Coastguard Worker    Args:
418*c2e18aaaSAndroid Build Coastguard Worker        results: A list of results.
419*c2e18aaaSAndroid Build Coastguard Worker
420*c2e18aaaSAndroid Build Coastguard Worker    Returns:
421*c2e18aaaSAndroid Build Coastguard Worker        True is the answer is affirmative.
422*c2e18aaaSAndroid Build Coastguard Worker    """
423*c2e18aaaSAndroid Build Coastguard Worker    return atest_utils.prompt_with_yn_result(
424*c2e18aaaSAndroid Build Coastguard Worker        'Did you mean {0}?'.format(atest_utils.mark_green(results[0])), True
425*c2e18aaaSAndroid Build Coastguard Worker    )
426*c2e18aaaSAndroid Build Coastguard Worker
427*c2e18aaaSAndroid Build Coastguard Worker  def _print_fuzzy_searching_results(self, results):
428*c2e18aaaSAndroid Build Coastguard Worker    """Print modules when fuzzy searching gives multiple results.
429*c2e18aaaSAndroid Build Coastguard Worker
430*c2e18aaaSAndroid Build Coastguard Worker    If the result is lengthy, just print the first 10 items only since we
431*c2e18aaaSAndroid Build Coastguard Worker    have already given enough-accurate result.
432*c2e18aaaSAndroid Build Coastguard Worker
433*c2e18aaaSAndroid Build Coastguard Worker    Args:
434*c2e18aaaSAndroid Build Coastguard Worker        results: A list of guessed testable module names.
435*c2e18aaaSAndroid Build Coastguard Worker    """
436*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
437*c2e18aaaSAndroid Build Coastguard Worker        'Did you mean the following modules?', constants.WHITE
438*c2e18aaaSAndroid Build Coastguard Worker    )
439*c2e18aaaSAndroid Build Coastguard Worker    for mod in results[:10]:
440*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.colorful_print(mod, constants.GREEN)
441*c2e18aaaSAndroid Build Coastguard Worker
442*c2e18aaaSAndroid Build Coastguard Worker  def filter_comments(self, test_mapping_file):
443*c2e18aaaSAndroid Build Coastguard Worker    """Remove comments in TEST_MAPPING file to valid format.
444*c2e18aaaSAndroid Build Coastguard Worker
445*c2e18aaaSAndroid Build Coastguard Worker    Only '//' and '#' are regarded as comments.
446*c2e18aaaSAndroid Build Coastguard Worker
447*c2e18aaaSAndroid Build Coastguard Worker    Args:
448*c2e18aaaSAndroid Build Coastguard Worker        test_mapping_file: Path to a TEST_MAPPING file.
449*c2e18aaaSAndroid Build Coastguard Worker
450*c2e18aaaSAndroid Build Coastguard Worker    Returns:
451*c2e18aaaSAndroid Build Coastguard Worker        Valid json string without comments.
452*c2e18aaaSAndroid Build Coastguard Worker    """
453*c2e18aaaSAndroid Build Coastguard Worker
454*c2e18aaaSAndroid Build Coastguard Worker    def _replace(match):
455*c2e18aaaSAndroid Build Coastguard Worker      """Replace comments if found matching the defined regular
456*c2e18aaaSAndroid Build Coastguard Worker
457*c2e18aaaSAndroid Build Coastguard Worker      expression.
458*c2e18aaaSAndroid Build Coastguard Worker
459*c2e18aaaSAndroid Build Coastguard Worker      Args:
460*c2e18aaaSAndroid Build Coastguard Worker          match: The matched regex pattern
461*c2e18aaaSAndroid Build Coastguard Worker
462*c2e18aaaSAndroid Build Coastguard Worker      Returns:
463*c2e18aaaSAndroid Build Coastguard Worker          "" if it matches _COMMENTS, otherwise original string.
464*c2e18aaaSAndroid Build Coastguard Worker      """
465*c2e18aaaSAndroid Build Coastguard Worker      line = match.group(0).strip()
466*c2e18aaaSAndroid Build Coastguard Worker      return '' if any(map(line.startswith, _COMMENTS)) else line
467*c2e18aaaSAndroid Build Coastguard Worker
468*c2e18aaaSAndroid Build Coastguard Worker    with open(test_mapping_file, encoding='utf-8') as json_file:
469*c2e18aaaSAndroid Build Coastguard Worker      return re.sub(_COMMENTS_RE, _replace, json_file.read())
470*c2e18aaaSAndroid Build Coastguard Worker
471*c2e18aaaSAndroid Build Coastguard Worker  def _read_tests_in_test_mapping(self, test_mapping_file):
472*c2e18aaaSAndroid Build Coastguard Worker    """Read tests from a TEST_MAPPING file.
473*c2e18aaaSAndroid Build Coastguard Worker
474*c2e18aaaSAndroid Build Coastguard Worker    Args:
475*c2e18aaaSAndroid Build Coastguard Worker        test_mapping_file: Path to a TEST_MAPPING file.
476*c2e18aaaSAndroid Build Coastguard Worker
477*c2e18aaaSAndroid Build Coastguard Worker    Returns:
478*c2e18aaaSAndroid Build Coastguard Worker        A tuple of (all_tests, imports), where
479*c2e18aaaSAndroid Build Coastguard Worker        all_tests is a dictionary of all tests in the TEST_MAPPING file,
480*c2e18aaaSAndroid Build Coastguard Worker            grouped by test group.
481*c2e18aaaSAndroid Build Coastguard Worker        imports is a list of test_mapping.Import to include other test
482*c2e18aaaSAndroid Build Coastguard Worker            mapping files.
483*c2e18aaaSAndroid Build Coastguard Worker    """
484*c2e18aaaSAndroid Build Coastguard Worker    all_tests = {}
485*c2e18aaaSAndroid Build Coastguard Worker    imports = []
486*c2e18aaaSAndroid Build Coastguard Worker    test_mapping_dict = {}
487*c2e18aaaSAndroid Build Coastguard Worker    try:
488*c2e18aaaSAndroid Build Coastguard Worker      test_mapping_dict = json.loads(self.filter_comments(test_mapping_file))
489*c2e18aaaSAndroid Build Coastguard Worker    except json.JSONDecodeError as e:
490*c2e18aaaSAndroid Build Coastguard Worker      msg = 'Test Mapping file has invalid format: %s.' % e
491*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(msg)
492*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.colorful_print(msg, constants.RED)
493*c2e18aaaSAndroid Build Coastguard Worker      sys.exit(ExitCode.INVALID_TM_FORMAT)
494*c2e18aaaSAndroid Build Coastguard Worker    for test_group_name, test_list in test_mapping_dict.items():
495*c2e18aaaSAndroid Build Coastguard Worker      if test_group_name == constants.TEST_MAPPING_IMPORTS:
496*c2e18aaaSAndroid Build Coastguard Worker        for import_detail in test_list:
497*c2e18aaaSAndroid Build Coastguard Worker          imports.append(test_mapping.Import(test_mapping_file, import_detail))
498*c2e18aaaSAndroid Build Coastguard Worker      else:
499*c2e18aaaSAndroid Build Coastguard Worker        grouped_tests = all_tests.setdefault(test_group_name, set())
500*c2e18aaaSAndroid Build Coastguard Worker        tests = []
501*c2e18aaaSAndroid Build Coastguard Worker        for test in test_list:
502*c2e18aaaSAndroid Build Coastguard Worker          if (
503*c2e18aaaSAndroid Build Coastguard Worker              self.enable_file_patterns
504*c2e18aaaSAndroid Build Coastguard Worker              and not test_mapping.is_match_file_patterns(
505*c2e18aaaSAndroid Build Coastguard Worker                  test_mapping_file, test
506*c2e18aaaSAndroid Build Coastguard Worker              )
507*c2e18aaaSAndroid Build Coastguard Worker          ):
508*c2e18aaaSAndroid Build Coastguard Worker            continue
509*c2e18aaaSAndroid Build Coastguard Worker          test_name = parse_test_identifier(test['name']).test_name
510*c2e18aaaSAndroid Build Coastguard Worker          test_mod_info = self.mod_info.name_to_module_info.get(test_name)
511*c2e18aaaSAndroid Build Coastguard Worker          if not test_mod_info:
512*c2e18aaaSAndroid Build Coastguard Worker            print(
513*c2e18aaaSAndroid Build Coastguard Worker                'WARNING: %s is not a valid build target and '
514*c2e18aaaSAndroid Build Coastguard Worker                'may not be discoverable by TreeHugger. If you '
515*c2e18aaaSAndroid Build Coastguard Worker                'want to specify a class or test-package, '
516*c2e18aaaSAndroid Build Coastguard Worker                "please set 'name' to the test module and use "
517*c2e18aaaSAndroid Build Coastguard Worker                "'options' to specify the right tests via "
518*c2e18aaaSAndroid Build Coastguard Worker                "'include-filter'.\nNote: this can also occur "
519*c2e18aaaSAndroid Build Coastguard Worker                'if the test module is not built for your '
520*c2e18aaaSAndroid Build Coastguard Worker                'current lunch target.\n'
521*c2e18aaaSAndroid Build Coastguard Worker                % atest_utils.mark_red(test['name'])
522*c2e18aaaSAndroid Build Coastguard Worker            )
523*c2e18aaaSAndroid Build Coastguard Worker          elif not any(
524*c2e18aaaSAndroid Build Coastguard Worker              x in test_mod_info.get('compatibility_suites', [])
525*c2e18aaaSAndroid Build Coastguard Worker              for x in constants.TEST_MAPPING_SUITES
526*c2e18aaaSAndroid Build Coastguard Worker          ):
527*c2e18aaaSAndroid Build Coastguard Worker            print(
528*c2e18aaaSAndroid Build Coastguard Worker                'WARNING: Please add %s to either suite: %s for '
529*c2e18aaaSAndroid Build Coastguard Worker                'this TEST_MAPPING file to work with TreeHugger.'
530*c2e18aaaSAndroid Build Coastguard Worker                % (
531*c2e18aaaSAndroid Build Coastguard Worker                    atest_utils.mark_red(test['name']),
532*c2e18aaaSAndroid Build Coastguard Worker                    atest_utils.mark_green(constants.TEST_MAPPING_SUITES),
533*c2e18aaaSAndroid Build Coastguard Worker                )
534*c2e18aaaSAndroid Build Coastguard Worker            )
535*c2e18aaaSAndroid Build Coastguard Worker          tests.append(test_mapping.TestDetail(test))
536*c2e18aaaSAndroid Build Coastguard Worker        grouped_tests.update(tests)
537*c2e18aaaSAndroid Build Coastguard Worker    return all_tests, imports
538*c2e18aaaSAndroid Build Coastguard Worker
539*c2e18aaaSAndroid Build Coastguard Worker  def _get_tests_from_test_mapping_files(self, test_groups, test_mapping_files):
540*c2e18aaaSAndroid Build Coastguard Worker    """Get tests in the given test mapping files with the match group.
541*c2e18aaaSAndroid Build Coastguard Worker
542*c2e18aaaSAndroid Build Coastguard Worker    Args:
543*c2e18aaaSAndroid Build Coastguard Worker        test_groups: Groups of tests to run. Default is set to `presubmit` and
544*c2e18aaaSAndroid Build Coastguard Worker          `presubmit-large`.
545*c2e18aaaSAndroid Build Coastguard Worker        test_mapping_files: A list of path of TEST_MAPPING files.
546*c2e18aaaSAndroid Build Coastguard Worker
547*c2e18aaaSAndroid Build Coastguard Worker    Returns:
548*c2e18aaaSAndroid Build Coastguard Worker        A tuple of (tests, all_tests, imports), where,
549*c2e18aaaSAndroid Build Coastguard Worker        tests is a set of tests (test_mapping.TestDetail) defined in
550*c2e18aaaSAndroid Build Coastguard Worker        TEST_MAPPING file of the given path, and its parent directories,
551*c2e18aaaSAndroid Build Coastguard Worker        with matching test_group.
552*c2e18aaaSAndroid Build Coastguard Worker        all_tests is a dictionary of all tests in TEST_MAPPING files,
553*c2e18aaaSAndroid Build Coastguard Worker        grouped by test group.
554*c2e18aaaSAndroid Build Coastguard Worker        imports is a list of test_mapping.Import objects that contains the
555*c2e18aaaSAndroid Build Coastguard Worker        details of where to import a TEST_MAPPING file.
556*c2e18aaaSAndroid Build Coastguard Worker    """
557*c2e18aaaSAndroid Build Coastguard Worker    all_imports = []
558*c2e18aaaSAndroid Build Coastguard Worker    # Read and merge the tests in all TEST_MAPPING files.
559*c2e18aaaSAndroid Build Coastguard Worker    merged_all_tests = {}
560*c2e18aaaSAndroid Build Coastguard Worker    for test_mapping_file in test_mapping_files:
561*c2e18aaaSAndroid Build Coastguard Worker      all_tests, imports = self._read_tests_in_test_mapping(test_mapping_file)
562*c2e18aaaSAndroid Build Coastguard Worker      all_imports.extend(imports)
563*c2e18aaaSAndroid Build Coastguard Worker      for test_group_name, test_list in all_tests.items():
564*c2e18aaaSAndroid Build Coastguard Worker        grouped_tests = merged_all_tests.setdefault(test_group_name, set())
565*c2e18aaaSAndroid Build Coastguard Worker        grouped_tests.update(test_list)
566*c2e18aaaSAndroid Build Coastguard Worker    tests = set()
567*c2e18aaaSAndroid Build Coastguard Worker    for test_group in test_groups:
568*c2e18aaaSAndroid Build Coastguard Worker      temp_tests = set(merged_all_tests.get(test_group, []))
569*c2e18aaaSAndroid Build Coastguard Worker      tests.update(temp_tests)
570*c2e18aaaSAndroid Build Coastguard Worker      if test_group == constants.TEST_GROUP_ALL:
571*c2e18aaaSAndroid Build Coastguard Worker        for grouped_tests in merged_all_tests.values():
572*c2e18aaaSAndroid Build Coastguard Worker          tests.update(grouped_tests)
573*c2e18aaaSAndroid Build Coastguard Worker    return tests, merged_all_tests, all_imports
574*c2e18aaaSAndroid Build Coastguard Worker
575*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-arguments
576*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-locals
577*c2e18aaaSAndroid Build Coastguard Worker  def _find_tests_by_test_mapping(
578*c2e18aaaSAndroid Build Coastguard Worker      self,
579*c2e18aaaSAndroid Build Coastguard Worker      path='',
580*c2e18aaaSAndroid Build Coastguard Worker      test_groups=None,
581*c2e18aaaSAndroid Build Coastguard Worker      file_name=constants.TEST_MAPPING,
582*c2e18aaaSAndroid Build Coastguard Worker      include_subdirs=False,
583*c2e18aaaSAndroid Build Coastguard Worker      checked_files=None,
584*c2e18aaaSAndroid Build Coastguard Worker  ):
585*c2e18aaaSAndroid Build Coastguard Worker    """Find tests defined in TEST_MAPPING in the given path.
586*c2e18aaaSAndroid Build Coastguard Worker
587*c2e18aaaSAndroid Build Coastguard Worker    Args:
588*c2e18aaaSAndroid Build Coastguard Worker        path: A string of path in source. Default is set to '', i.e., CWD.
589*c2e18aaaSAndroid Build Coastguard Worker        test_groups: A List of test groups to run.
590*c2e18aaaSAndroid Build Coastguard Worker        file_name: Name of TEST_MAPPING file. Default is set to `TEST_MAPPING`.
591*c2e18aaaSAndroid Build Coastguard Worker          The argument is added for testing purpose.
592*c2e18aaaSAndroid Build Coastguard Worker        include_subdirs: True to include tests in TEST_MAPPING files in sub
593*c2e18aaaSAndroid Build Coastguard Worker          directories.
594*c2e18aaaSAndroid Build Coastguard Worker        checked_files: Paths of TEST_MAPPING files that have been checked.
595*c2e18aaaSAndroid Build Coastguard Worker
596*c2e18aaaSAndroid Build Coastguard Worker    Returns:
597*c2e18aaaSAndroid Build Coastguard Worker        A tuple of (tests, all_tests), where,
598*c2e18aaaSAndroid Build Coastguard Worker        tests is a set of tests (test_mapping.TestDetail) defined in
599*c2e18aaaSAndroid Build Coastguard Worker        TEST_MAPPING file of the given path, and its parent directories,
600*c2e18aaaSAndroid Build Coastguard Worker        with matching test_group.
601*c2e18aaaSAndroid Build Coastguard Worker        all_tests is a dictionary of all tests in TEST_MAPPING files,
602*c2e18aaaSAndroid Build Coastguard Worker        grouped by test group.
603*c2e18aaaSAndroid Build Coastguard Worker    """
604*c2e18aaaSAndroid Build Coastguard Worker    path = os.path.realpath(path)
605*c2e18aaaSAndroid Build Coastguard Worker    # Default test_groups is set to [`presubmit`, `presubmit-large`].
606*c2e18aaaSAndroid Build Coastguard Worker    if not test_groups:
607*c2e18aaaSAndroid Build Coastguard Worker      test_groups = constants.DEFAULT_TEST_GROUPS
608*c2e18aaaSAndroid Build Coastguard Worker    test_mapping_files = set()
609*c2e18aaaSAndroid Build Coastguard Worker    all_tests = {}
610*c2e18aaaSAndroid Build Coastguard Worker    test_mapping_file = os.path.join(path, file_name)
611*c2e18aaaSAndroid Build Coastguard Worker    if os.path.exists(test_mapping_file):
612*c2e18aaaSAndroid Build Coastguard Worker      test_mapping_files.add(test_mapping_file)
613*c2e18aaaSAndroid Build Coastguard Worker    # Include all TEST_MAPPING files in sub-directories if `include_subdirs`
614*c2e18aaaSAndroid Build Coastguard Worker    # is set to True.
615*c2e18aaaSAndroid Build Coastguard Worker    if include_subdirs:
616*c2e18aaaSAndroid Build Coastguard Worker      test_mapping_files.update(atest_utils.find_files(path, file_name))
617*c2e18aaaSAndroid Build Coastguard Worker    # Include all possible TEST_MAPPING files in parent directories.
618*c2e18aaaSAndroid Build Coastguard Worker    while path not in (self.root_dir, os.sep):
619*c2e18aaaSAndroid Build Coastguard Worker      path = os.path.dirname(path)
620*c2e18aaaSAndroid Build Coastguard Worker      test_mapping_file = os.path.join(path, file_name)
621*c2e18aaaSAndroid Build Coastguard Worker      if os.path.exists(test_mapping_file):
622*c2e18aaaSAndroid Build Coastguard Worker        test_mapping_files.add(test_mapping_file)
623*c2e18aaaSAndroid Build Coastguard Worker
624*c2e18aaaSAndroid Build Coastguard Worker    if checked_files is None:
625*c2e18aaaSAndroid Build Coastguard Worker      checked_files = set()
626*c2e18aaaSAndroid Build Coastguard Worker    test_mapping_files.difference_update(checked_files)
627*c2e18aaaSAndroid Build Coastguard Worker    checked_files.update(test_mapping_files)
628*c2e18aaaSAndroid Build Coastguard Worker    if not test_mapping_files:
629*c2e18aaaSAndroid Build Coastguard Worker      return test_mapping_files, all_tests
630*c2e18aaaSAndroid Build Coastguard Worker
631*c2e18aaaSAndroid Build Coastguard Worker    tests, all_tests, imports = self._get_tests_from_test_mapping_files(
632*c2e18aaaSAndroid Build Coastguard Worker        test_groups, test_mapping_files
633*c2e18aaaSAndroid Build Coastguard Worker    )
634*c2e18aaaSAndroid Build Coastguard Worker
635*c2e18aaaSAndroid Build Coastguard Worker    # Load TEST_MAPPING files from imports recursively.
636*c2e18aaaSAndroid Build Coastguard Worker    if imports:
637*c2e18aaaSAndroid Build Coastguard Worker      for import_detail in imports:
638*c2e18aaaSAndroid Build Coastguard Worker        path = import_detail.get_path()
639*c2e18aaaSAndroid Build Coastguard Worker        # (b/110166535 #19) Import path might not exist if a project is
640*c2e18aaaSAndroid Build Coastguard Worker        # located in different directory in different branches.
641*c2e18aaaSAndroid Build Coastguard Worker        if path is None:
642*c2e18aaaSAndroid Build Coastguard Worker          atest_utils.print_and_log_warning(
643*c2e18aaaSAndroid Build Coastguard Worker              'Failed to import TEST_MAPPING at %s', import_detail
644*c2e18aaaSAndroid Build Coastguard Worker          )
645*c2e18aaaSAndroid Build Coastguard Worker          continue
646*c2e18aaaSAndroid Build Coastguard Worker        # Search for tests based on the imported search path.
647*c2e18aaaSAndroid Build Coastguard Worker        import_tests, import_all_tests = self._find_tests_by_test_mapping(
648*c2e18aaaSAndroid Build Coastguard Worker            path, test_groups, file_name, include_subdirs, checked_files
649*c2e18aaaSAndroid Build Coastguard Worker        )
650*c2e18aaaSAndroid Build Coastguard Worker        # Merge the collections
651*c2e18aaaSAndroid Build Coastguard Worker        tests.update(import_tests)
652*c2e18aaaSAndroid Build Coastguard Worker        for group, grouped_tests in import_all_tests.items():
653*c2e18aaaSAndroid Build Coastguard Worker          all_tests.setdefault(group, set()).update(grouped_tests)
654*c2e18aaaSAndroid Build Coastguard Worker
655*c2e18aaaSAndroid Build Coastguard Worker    return tests, all_tests
656*c2e18aaaSAndroid Build Coastguard Worker
657*c2e18aaaSAndroid Build Coastguard Worker  def _get_test_mapping_tests(self, args, exit_if_no_test_found=True):
658*c2e18aaaSAndroid Build Coastguard Worker    """Find the tests in TEST_MAPPING files.
659*c2e18aaaSAndroid Build Coastguard Worker
660*c2e18aaaSAndroid Build Coastguard Worker    Args:
661*c2e18aaaSAndroid Build Coastguard Worker        args: arg parsed object. exit_if_no_test(s)_found: A flag to exit atest
662*c2e18aaaSAndroid Build Coastguard Worker          if no test mapping tests found.
663*c2e18aaaSAndroid Build Coastguard Worker
664*c2e18aaaSAndroid Build Coastguard Worker    Returns:
665*c2e18aaaSAndroid Build Coastguard Worker        A tuple of (test_names, test_details_list), where
666*c2e18aaaSAndroid Build Coastguard Worker        test_names: a list of test name
667*c2e18aaaSAndroid Build Coastguard Worker        test_details_list: a list of test_mapping.TestDetail objects for
668*c2e18aaaSAndroid Build Coastguard Worker            the tests in TEST_MAPPING files with matching test group.
669*c2e18aaaSAndroid Build Coastguard Worker    """
670*c2e18aaaSAndroid Build Coastguard Worker    # Pull out tests from test mapping
671*c2e18aaaSAndroid Build Coastguard Worker    src_path = ''
672*c2e18aaaSAndroid Build Coastguard Worker    test_groups = constants.DEFAULT_TEST_GROUPS
673*c2e18aaaSAndroid Build Coastguard Worker    if args.tests:
674*c2e18aaaSAndroid Build Coastguard Worker      if ':' in args.tests[0]:
675*c2e18aaaSAndroid Build Coastguard Worker        src_path, test_group = args.tests[0].split(':')
676*c2e18aaaSAndroid Build Coastguard Worker        test_groups = [test_group]
677*c2e18aaaSAndroid Build Coastguard Worker      else:
678*c2e18aaaSAndroid Build Coastguard Worker        src_path = args.tests[0]
679*c2e18aaaSAndroid Build Coastguard Worker
680*c2e18aaaSAndroid Build Coastguard Worker    test_details, all_test_details = self._find_tests_by_test_mapping(
681*c2e18aaaSAndroid Build Coastguard Worker        path=src_path,
682*c2e18aaaSAndroid Build Coastguard Worker        test_groups=test_groups,
683*c2e18aaaSAndroid Build Coastguard Worker        include_subdirs=args.include_subdirs,
684*c2e18aaaSAndroid Build Coastguard Worker        checked_files=set(),
685*c2e18aaaSAndroid Build Coastguard Worker    )
686*c2e18aaaSAndroid Build Coastguard Worker    test_details_list = list(test_details)
687*c2e18aaaSAndroid Build Coastguard Worker    if not test_details_list and exit_if_no_test_found:
688*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.print_and_log_warning(
689*c2e18aaaSAndroid Build Coastguard Worker          'No tests of group `%s` found in %s or its '
690*c2e18aaaSAndroid Build Coastguard Worker          'parent directories. (Available groups: %s)\n'
691*c2e18aaaSAndroid Build Coastguard Worker          'You might be missing atest arguments,'
692*c2e18aaaSAndroid Build Coastguard Worker          ' try `atest --help` for more information.',
693*c2e18aaaSAndroid Build Coastguard Worker          test_groups,
694*c2e18aaaSAndroid Build Coastguard Worker          os.path.join(src_path, constants.TEST_MAPPING),
695*c2e18aaaSAndroid Build Coastguard Worker          ', '.join(all_test_details.keys()),
696*c2e18aaaSAndroid Build Coastguard Worker      )
697*c2e18aaaSAndroid Build Coastguard Worker      if all_test_details:
698*c2e18aaaSAndroid Build Coastguard Worker        tests = ''
699*c2e18aaaSAndroid Build Coastguard Worker        for test_group, test_list in all_test_details.items():
700*c2e18aaaSAndroid Build Coastguard Worker          tests += '%s:\n' % test_group
701*c2e18aaaSAndroid Build Coastguard Worker          for test_detail in sorted(test_list, key=str):
702*c2e18aaaSAndroid Build Coastguard Worker            tests += '\t%s\n' % test_detail
703*c2e18aaaSAndroid Build Coastguard Worker        atest_utils.print_and_log_warning(
704*c2e18aaaSAndroid Build Coastguard Worker            'All available tests in TEST_MAPPING files are:\n%s', tests
705*c2e18aaaSAndroid Build Coastguard Worker        )
706*c2e18aaaSAndroid Build Coastguard Worker      metrics_utils.send_exit_event(ExitCode.TEST_NOT_FOUND)
707*c2e18aaaSAndroid Build Coastguard Worker      sys.exit(ExitCode.TEST_NOT_FOUND)
708*c2e18aaaSAndroid Build Coastguard Worker
709*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
710*c2e18aaaSAndroid Build Coastguard Worker        'Test details:\n%s',
711*c2e18aaaSAndroid Build Coastguard Worker        '\n'.join([str(detail) for detail in test_details_list]),
712*c2e18aaaSAndroid Build Coastguard Worker    )
713*c2e18aaaSAndroid Build Coastguard Worker    test_names = [detail.name for detail in test_details_list]
714*c2e18aaaSAndroid Build Coastguard Worker    return test_names, test_details_list
715*c2e18aaaSAndroid Build Coastguard Worker
716*c2e18aaaSAndroid Build Coastguard Worker  def _extract_testable_modules_by_wildcard(self, user_input):
717*c2e18aaaSAndroid Build Coastguard Worker    """Extract the given string with wildcard symbols to testable
718*c2e18aaaSAndroid Build Coastguard Worker
719*c2e18aaaSAndroid Build Coastguard Worker    module names.
720*c2e18aaaSAndroid Build Coastguard Worker
721*c2e18aaaSAndroid Build Coastguard Worker    Assume the available testable modules is:
722*c2e18aaaSAndroid Build Coastguard Worker        ['Google', 'google', 'G00gle', 'g00gle']
723*c2e18aaaSAndroid Build Coastguard Worker    and the user_input is:
724*c2e18aaaSAndroid Build Coastguard Worker        ['*oo*', 'g00gle']
725*c2e18aaaSAndroid Build Coastguard Worker    This method will return:
726*c2e18aaaSAndroid Build Coastguard Worker        ['Google', 'google', 'g00gle']
727*c2e18aaaSAndroid Build Coastguard Worker
728*c2e18aaaSAndroid Build Coastguard Worker    Args:
729*c2e18aaaSAndroid Build Coastguard Worker        user_input: A list of input.
730*c2e18aaaSAndroid Build Coastguard Worker
731*c2e18aaaSAndroid Build Coastguard Worker    Returns:
732*c2e18aaaSAndroid Build Coastguard Worker        A list of testable modules.
733*c2e18aaaSAndroid Build Coastguard Worker    """
734*c2e18aaaSAndroid Build Coastguard Worker    testable_mods = self.mod_info.get_testable_modules()
735*c2e18aaaSAndroid Build Coastguard Worker    extracted_tests = []
736*c2e18aaaSAndroid Build Coastguard Worker    for test in user_input:
737*c2e18aaaSAndroid Build Coastguard Worker      if atest_utils.has_wildcard(test):
738*c2e18aaaSAndroid Build Coastguard Worker        extracted_tests.extend(fnmatch.filter(testable_mods, test))
739*c2e18aaaSAndroid Build Coastguard Worker      else:
740*c2e18aaaSAndroid Build Coastguard Worker        extracted_tests.append(test)
741*c2e18aaaSAndroid Build Coastguard Worker    return extracted_tests
742*c2e18aaaSAndroid Build Coastguard Worker
743*c2e18aaaSAndroid Build Coastguard Worker  def translate(self, args):
744*c2e18aaaSAndroid Build Coastguard Worker    """Translate atest command line into build targets and run commands.
745*c2e18aaaSAndroid Build Coastguard Worker
746*c2e18aaaSAndroid Build Coastguard Worker    Args:
747*c2e18aaaSAndroid Build Coastguard Worker        args: arg parsed object.
748*c2e18aaaSAndroid Build Coastguard Worker
749*c2e18aaaSAndroid Build Coastguard Worker    Returns:
750*c2e18aaaSAndroid Build Coastguard Worker        A tuple with set of build_target strings and list of TestInfos.
751*c2e18aaaSAndroid Build Coastguard Worker    """
752*c2e18aaaSAndroid Build Coastguard Worker    tests = args.tests
753*c2e18aaaSAndroid Build Coastguard Worker    detect_type = DetectType.TEST_WITH_ARGS
754*c2e18aaaSAndroid Build Coastguard Worker    # Disable fuzzy searching when running with test mapping related args.
755*c2e18aaaSAndroid Build Coastguard Worker    if not args.tests or atest_utils.is_test_mapping(args):
756*c2e18aaaSAndroid Build Coastguard Worker      self.fuzzy_search = False
757*c2e18aaaSAndroid Build Coastguard Worker      detect_type = DetectType.TEST_NULL_ARGS
758*c2e18aaaSAndroid Build Coastguard Worker    start = time.time()
759*c2e18aaaSAndroid Build Coastguard Worker    # Not including host unit tests if user specify --test-mapping.
760*c2e18aaaSAndroid Build Coastguard Worker    host_unit_tests = []
761*c2e18aaaSAndroid Build Coastguard Worker    if not any((args.tests, args.test_mapping)):
762*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Finding Host Unit Tests...')
763*c2e18aaaSAndroid Build Coastguard Worker      host_unit_tests = test_finder_utils.find_host_unit_tests(
764*c2e18aaaSAndroid Build Coastguard Worker          self.mod_info, str(Path(os.getcwd()).relative_to(self.root_dir))
765*c2e18aaaSAndroid Build Coastguard Worker      )
766*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Found host_unit_tests: %s', host_unit_tests)
767*c2e18aaaSAndroid Build Coastguard Worker    # Test details from TEST_MAPPING files
768*c2e18aaaSAndroid Build Coastguard Worker    test_details_list = None
769*c2e18aaaSAndroid Build Coastguard Worker    if atest_utils.is_test_mapping(args):
770*c2e18aaaSAndroid Build Coastguard Worker      if args.enable_file_patterns:
771*c2e18aaaSAndroid Build Coastguard Worker        self.enable_file_patterns = True
772*c2e18aaaSAndroid Build Coastguard Worker      tests, test_details_list = self._get_test_mapping_tests(
773*c2e18aaaSAndroid Build Coastguard Worker          args, not bool(host_unit_tests)
774*c2e18aaaSAndroid Build Coastguard Worker      )
775*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print('\nFinding Tests...', constants.CYAN)
776*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Finding Tests: %s', tests)
777*c2e18aaaSAndroid Build Coastguard Worker    # Clear cache if user pass -c option
778*c2e18aaaSAndroid Build Coastguard Worker    if args.clear_cache:
779*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.clean_test_info_caches(tests + host_unit_tests)
780*c2e18aaaSAndroid Build Coastguard Worker    # Process tests which might contain wildcard symbols in advance.
781*c2e18aaaSAndroid Build Coastguard Worker    if atest_utils.has_wildcard(tests):
782*c2e18aaaSAndroid Build Coastguard Worker      tests = self._extract_testable_modules_by_wildcard(tests)
783*c2e18aaaSAndroid Build Coastguard Worker    test_infos = self._get_test_infos(tests, test_details_list)
784*c2e18aaaSAndroid Build Coastguard Worker    if host_unit_tests:
785*c2e18aaaSAndroid Build Coastguard Worker      host_unit_test_details = [
786*c2e18aaaSAndroid Build Coastguard Worker          test_mapping.TestDetail({'name': test, 'host': True})
787*c2e18aaaSAndroid Build Coastguard Worker          for test in host_unit_tests
788*c2e18aaaSAndroid Build Coastguard Worker      ]
789*c2e18aaaSAndroid Build Coastguard Worker      host_unit_test_infos = self._get_test_infos(
790*c2e18aaaSAndroid Build Coastguard Worker          host_unit_tests, host_unit_test_details
791*c2e18aaaSAndroid Build Coastguard Worker      )
792*c2e18aaaSAndroid Build Coastguard Worker      test_infos.extend(host_unit_test_infos)
793*c2e18aaaSAndroid Build Coastguard Worker    if atest_utils.has_mixed_type_filters(test_infos):
794*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.colorful_print(
795*c2e18aaaSAndroid Build Coastguard Worker          'Mixed type filters found. '
796*c2e18aaaSAndroid Build Coastguard Worker          'Please separate tests into different runs.',
797*c2e18aaaSAndroid Build Coastguard Worker          constants.YELLOW,
798*c2e18aaaSAndroid Build Coastguard Worker      )
799*c2e18aaaSAndroid Build Coastguard Worker      sys.exit(ExitCode.MIXED_TYPE_FILTER)
800*c2e18aaaSAndroid Build Coastguard Worker    finished_time = time.time() - start
801*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Finding tests finished in %ss', finished_time)
802*c2e18aaaSAndroid Build Coastguard Worker    metrics.LocalDetectEvent(detect_type=detect_type, result=int(finished_time))
803*c2e18aaaSAndroid Build Coastguard Worker    for t_info in test_infos:
804*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('%s\n', t_info)
805*c2e18aaaSAndroid Build Coastguard Worker    return test_infos
806*c2e18aaaSAndroid Build Coastguard Worker
807*c2e18aaaSAndroid Build Coastguard Worker
808*c2e18aaaSAndroid Build Coastguard Worker# TODO: (b/265359291) Raise Exception when the brackets are not in pair.
809*c2e18aaaSAndroid Build Coastguard Workerdef parse_test_identifier(test: str) -> TestIdentifier:
810*c2e18aaaSAndroid Build Coastguard Worker  """Get mainline module names and binaries information."""
811*c2e18aaaSAndroid Build Coastguard Worker  result = atest_utils.get_test_and_mainline_modules(test)
812*c2e18aaaSAndroid Build Coastguard Worker  if not result:
813*c2e18aaaSAndroid Build Coastguard Worker    return TestIdentifier(test, [], [])
814*c2e18aaaSAndroid Build Coastguard Worker  test_name = result.group('test')
815*c2e18aaaSAndroid Build Coastguard Worker  mainline_binaries = result.group('mainline_modules').split('+')
816*c2e18aaaSAndroid Build Coastguard Worker  mainline_modules = [Path(m).stem for m in mainline_binaries]
817*c2e18aaaSAndroid Build Coastguard Worker  logging.debug('mainline_modules: %s', mainline_modules)
818*c2e18aaaSAndroid Build Coastguard Worker  return TestIdentifier(test_name, mainline_modules, mainline_binaries)
819