1# Copyright 2018, 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.
14
15"""Test Finder Handler module."""
16
17# pylint: disable=import-outside-toplevel
18# pylint: disable=protected-access
19
20from enum import Enum, unique
21import inspect
22import logging
23import re
24import sys
25
26from atest import atest_utils, constants
27from atest.atest_enum import ExitCode
28from atest.test_finders import cache_finder
29from atest.test_finders import module_finder
30from atest.test_finders import suite_plan_finder
31from atest.test_finders import test_finder_base
32from atest.test_finders import test_finder_utils
33from atest.test_finders import tf_integration_finder
34
35# List of default test finder classes.
36_TEST_FINDERS = {
37    suite_plan_finder.SuitePlanFinder,
38    tf_integration_finder.TFIntegrationFinder,
39    module_finder.ModuleFinder,
40    cache_finder.CacheFinder,
41}
42
43
44@unique
45class FinderMethod(Enum):
46  """An enum object for test finders.
47
48  Explanation of FinderMethod:
49  0. MODULE: LOCAL_MODULE or LOCAL_PACKAGE_NAME value in Android.mk/Android.bp.
50  1. MAINLINE_MODULE: module[mod1.apk+mod2.apex] pattern in TEST_MAPPING files.
51  2. CLASS: Names which the same with a ClassName.java/kt file.
52  3. QUALIFIED_CLASS: String like "a.b.c.ClassName".
53  4. MODULE_CLASS: Combo of MODULE and CLASS as "module:class".
54  5. PACKAGE: Package in java file. Same as file path to java file.
55  6. MODULE_PACKAGE: Combo of MODULE and PACKAGE as "module:package".
56  7. MODULE_FILE_PATH: File path to dir of tests or test itself.
57  8. INTEGRATION_FILE_PATH: File path to config xml in one of the 4 integration
58                            config directories.
59  9. INTEGRATION: xml file name in one of the 4 integration config directories.
60  10. SUITE: Value of the "run-suite-tag" in xml config file in 4 config dirs.
61             Same as value of "test-suite-tag" in AndroidTest.xml files.
62  11. CC_CLASS: Test case in cc file.
63  12. SUITE_PLAN: Suite name such as cts.
64  13. SUITE_PLAN_FILE_PATH: File path to config xml in the suite config
65                            directories.
66  14. CACHE: A pseudo type that runs cache_finder without finding test in real.
67  15: CONFIG: Find tests by the given AndroidTest.xml file path.
68  """
69
70  MODULE = ('MODULE', module_finder.ModuleFinder.find_test_by_module_name)
71  MAINLINE_MODULE = (
72      'MAINLINE_MODULE',
73      module_finder.MainlineModuleFinder.find_test_by_module_name,
74  )
75  CLASS = ('CLASS', module_finder.ModuleFinder.find_test_by_class_name)
76  MODULE_CLASS = (
77      'MODULE_CLASS',
78      module_finder.ModuleFinder.find_test_by_module_and_class,
79  )
80  QUALIFIED_CLASS = (
81      'QUALIFIED_CLASS',
82      module_finder.ModuleFinder.find_test_by_class_name,
83  )
84  PACKAGE = ('PACKAGE', module_finder.ModuleFinder.find_test_by_package_name)
85  MODULE_PACKAGE = (
86      'MODULE_PACKAGE',
87      module_finder.ModuleFinder.find_test_by_module_and_package,
88  )
89  MODULE_FILE_PATH = (
90      'MODULE_FILE_PATH',
91      module_finder.ModuleFinder.find_test_by_path,
92  )
93  INTEGRATION_FILE_PATH = (
94      'INTEGRATION_FILE_PATH',
95      tf_integration_finder.TFIntegrationFinder.find_int_test_by_path,
96  )
97  INTEGRATION = (
98      'INTEGRATION',
99      tf_integration_finder.TFIntegrationFinder.find_test_by_integration_name,
100  )
101  CC_CLASS = ('CC_CLASS', module_finder.ModuleFinder.find_test_by_cc_class_name)
102  SUITE_PLAN = (
103      'SUITE_PLAN',
104      suite_plan_finder.SuitePlanFinder.find_test_by_suite_name,
105  )
106  SUITE_PLAN_FILE_PATH = (
107      'SUITE_PLAN_FILE_PATH',
108      suite_plan_finder.SuitePlanFinder.find_test_by_suite_path,
109  )
110  CACHE = ('CACHE', cache_finder.CacheFinder.find_test_by_cache)
111  CONFIG = ('CONFIG', module_finder.ModuleFinder.find_test_by_config_name)
112
113  def __init__(self, name, method):
114    self._name = name
115    self._method = method
116
117  def get_name(self):
118    """Return finder's name."""
119    return self._name
120
121  def get_method(self):
122    """Return finder's method."""
123    return self._method
124
125
126def _get_finder_instance_dict(module_info):
127  """Return dict of finder instances.
128
129  Args:
130      module_info: ModuleInfo for finder classes to use.
131
132  Returns:
133      Dict of finder instances keyed by their name.
134  """
135  instance_dict = {}
136  for finder in _get_test_finders():
137    instance_dict[finder.NAME] = finder(module_info=module_info)
138  return instance_dict
139
140
141def _get_test_finders():
142  """Returns the test finders.
143
144  If external test types are defined outside atest, they can be try-except
145  imported into here.
146
147  Returns:
148      Set of test finder classes.
149  """
150  test_finders_list = _TEST_FINDERS
151  # Example import of external test finder:
152  try:
153    from test_finders import example_finder
154
155    test_finders_list.add(example_finder.ExampleFinder)
156  except ImportError:
157    pass
158  return test_finders_list
159
160
161def _validate_ref(ref: str):
162  # Filter out trailing dot but keeping `.` and `..` in ref.
163  if '..' not in ref:
164    if re.match(r'(?:[\w\.\d-]+)\.$', ref):
165      atest_utils.colorful_print(
166          f'Found trailing dot({ref}). Please correct it and try again.',
167          constants.RED,
168      )
169      sys.exit(ExitCode.INPUT_TEST_REFERENCE_ERROR)
170
171
172# pylint: disable=too-many-branches
173# pylint: disable=too-many-return-statements
174def _get_test_reference_types(ref):
175  """Determine type of test reference based on the content of string.
176
177  Examples:
178      The string 'SequentialRWTest' could be a reference to
179      a Module or a Class name.
180
181      The string 'cts/tests/filesystem' could be a Path, Integration
182      or Suite reference.
183
184  Args:
185      ref: A string referencing a test.
186
187  Returns:
188      A list of possible REFERENCE_TYPEs (ints) for reference string.
189  """
190  _validate_ref(ref)
191  if ref.startswith('.') or '..' in ref:
192    return [
193        FinderMethod.CACHE,
194        FinderMethod.MODULE_FILE_PATH,
195        FinderMethod.INTEGRATION_FILE_PATH,
196        FinderMethod.SUITE_PLAN_FILE_PATH,
197    ]
198  if '/' in ref:
199    if ref.startswith('/'):
200      return [
201          FinderMethod.CACHE,
202          FinderMethod.MODULE_FILE_PATH,
203          FinderMethod.INTEGRATION_FILE_PATH,
204          FinderMethod.SUITE_PLAN_FILE_PATH,
205      ]
206    if ':' in ref:
207      return [
208          FinderMethod.CACHE,
209          FinderMethod.MODULE_FILE_PATH,
210          FinderMethod.INTEGRATION_FILE_PATH,
211          FinderMethod.INTEGRATION,
212          FinderMethod.SUITE_PLAN_FILE_PATH,
213          FinderMethod.MODULE_CLASS,
214      ]
215    return [
216        FinderMethod.CACHE,
217        FinderMethod.MODULE_FILE_PATH,
218        FinderMethod.INTEGRATION_FILE_PATH,
219        FinderMethod.INTEGRATION,
220        FinderMethod.SUITE_PLAN_FILE_PATH,
221        FinderMethod.CC_CLASS,
222        # TODO: Uncomment in SUITE when it's supported
223        # FinderMethod.SUITE
224    ]
225  if atest_utils.get_test_and_mainline_modules(ref):
226    return [FinderMethod.CACHE, FinderMethod.MAINLINE_MODULE]
227  if '.' in ref:
228    ref_end = ref.rsplit('.', 1)[-1]
229    ref_end_is_upper = ref_end[0].isupper()
230  # parse_test_reference() will return none empty dictionary if input test
231  # reference match $module:$package_class.
232  if test_finder_utils.parse_test_reference(ref):
233    if '.' in ref:
234      if ref_end_is_upper:
235        # Possible types:
236        # Module:fully.qualified.Class
237        # Module:filly.qualifiled.(P|p)ackage (b/289515000)
238        # Integration:fully.q.Class
239        return [
240            FinderMethod.CACHE,
241            FinderMethod.MODULE_CLASS,
242            FinderMethod.MODULE_PACKAGE,
243            FinderMethod.INTEGRATION,
244        ]
245      # Module:some.package
246      return [
247          FinderMethod.CACHE,
248          FinderMethod.MODULE_PACKAGE,
249          FinderMethod.MODULE_CLASS,
250      ]
251    # Module:Class or IntegrationName:Class
252    return [
253        FinderMethod.CACHE,
254        FinderMethod.MODULE_CLASS,
255        FinderMethod.INTEGRATION,
256    ]
257  if '.' in ref:
258    # The string of ref_end possibly includes specific mathods, e.g.
259    # foo.java#method, so let ref_end be the first part of splitting '#'.
260    if '#' in ref_end:
261      ref_end = ref_end.split('#')[0]
262    if ref_end in ('java', 'kt', 'bp', 'mk', 'cc', 'cpp'):
263      return [FinderMethod.CACHE, FinderMethod.MODULE_FILE_PATH]
264    if ref_end == 'xml':
265      return [
266          FinderMethod.CACHE,
267          FinderMethod.INTEGRATION_FILE_PATH,
268          FinderMethod.SUITE_PLAN_FILE_PATH,
269      ]
270    # (b/207327349) ref_end_is_upper does not guarantee a classname anymore.
271    return [
272        FinderMethod.CACHE,
273        FinderMethod.MODULE,
274        FinderMethod.QUALIFIED_CLASS,
275        FinderMethod.PACKAGE,
276    ]
277  # Note: We assume that if you're referencing a file in your cwd,
278  # that file must have a '.' in its name, i.e. foo.java, foo.xml.
279  # If this ever becomes not the case, then we need to include path below.
280  return [
281      FinderMethod.CACHE,
282      FinderMethod.MODULE,
283      FinderMethod.INTEGRATION,
284      # TODO: Uncomment in SUITE when it's supported
285      # FinderMethod.SUITE,
286      FinderMethod.CONFIG,
287      FinderMethod.SUITE_PLAN,
288      FinderMethod.CLASS,
289      FinderMethod.CC_CLASS,
290  ]
291
292
293def _get_registered_find_methods(module_info):
294  """Return list of registered find methods.
295
296  This is used to return find methods that were not listed in the
297  default find methods but just registered in the finder classes. These
298  find methods will run before the default find methods.
299
300  Args:
301      module_info: ModuleInfo for finder classes to instantiate with.
302
303  Returns:
304      List of registered find methods.
305  """
306  find_methods = []
307  finder_instance_dict = _get_finder_instance_dict(module_info)
308  for finder in _get_test_finders():
309    finder_instance = finder_instance_dict[finder.NAME]
310    for find_method_info in finder_instance.get_all_find_methods():
311      find_methods.append(
312          test_finder_base.Finder(
313              finder_instance, find_method_info.find_method, finder.NAME
314          )
315      )
316  return find_methods
317
318
319def _get_default_find_methods(module_info, test):
320  """Default find methods to be used based on the given test name.
321
322  Args:
323      module_info: ModuleInfo for finder instances to use.
324      test: String of test name to help determine which find methods to utilize.
325
326  Returns:
327      List of find methods to use.
328  """
329  find_methods = []
330  finder_instance_dict = _get_finder_instance_dict(module_info)
331  test_ref_types = _get_test_reference_types(test)
332  logging.debug(
333      'Resolved input to possible references: %s',
334      ', '.join([t.get_name() for t in test_ref_types]),
335  )
336  for test_ref_type in test_ref_types:
337    find_method = test_ref_type.get_method()
338    finder_instance = finder_instance_dict[inspect._findclass(find_method).NAME]
339    finder_info = test_ref_type.get_name()
340    find_methods.append(
341        test_finder_base.Finder(finder_instance, find_method, finder_info)
342    )
343  return find_methods
344
345
346def get_find_methods_for_test(module_info, test):
347  """Return a list of ordered find methods.
348
349  Args:
350    test: String of test name to get find methods for.
351
352  Returns:
353      List of ordered find methods.
354  """
355  registered_find_methods = _get_registered_find_methods(module_info)
356  default_find_methods = _get_default_find_methods(module_info, test)
357  return registered_find_methods + default_find_methods
358