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