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""" 16Module Finder class. 17""" 18 19# pylint: disable=line-too-long 20 21import logging 22import os 23 24import atest_configs 25import atest_error 26import atest_utils 27import constants 28 29from test_finders import test_info 30from test_finders import test_finder_base 31from test_finders import test_finder_utils 32from test_runners import atest_tf_test_runner 33from test_runners import robolectric_test_runner 34from test_runners import vts_tf_test_runner 35 36_ANDROID_MK = 'Android.mk' 37 38# These are suites in LOCAL_COMPATIBILITY_SUITE that aren't really suites so 39# we can ignore them. 40_SUITES_TO_IGNORE = frozenset({'general-tests', 'device-tests', 'tests'}) 41 42class ModuleFinder(test_finder_base.TestFinderBase): 43 """Module finder class.""" 44 NAME = 'MODULE' 45 _TEST_RUNNER = atest_tf_test_runner.AtestTradefedTestRunner.NAME 46 _ROBOLECTRIC_RUNNER = robolectric_test_runner.RobolectricTestRunner.NAME 47 _VTS_TEST_RUNNER = vts_tf_test_runner.VtsTradefedTestRunner.NAME 48 49 def __init__(self, module_info=None): 50 super().__init__() 51 self.root_dir = os.environ.get(constants.ANDROID_BUILD_TOP) 52 self.module_info = module_info 53 54 def _determine_testable_module(self, path, file_path=None): 55 """Determine which module the user is trying to test. 56 57 Returns the module to test. If there are multiple possibilities, will 58 ask the user. Otherwise will return the only module found. 59 60 Args: 61 path: String path of module to look for. 62 file_path: String path of input file. 63 64 Returns: 65 A list of the module names. 66 """ 67 testable_modules = [] 68 # A list to save those testable modules but srcs information is empty. 69 testable_modules_no_srcs = [] 70 for mod in self.module_info.get_module_names(path): 71 mod_info = self.module_info.get_module_info(mod) 72 # Robolectric tests always exist in pairs of 2, one module to build 73 # the test and another to run it. For now, we are assuming they are 74 # isolated in their own folders and will return if we find one. 75 if self.module_info.is_robolectric_test(mod): 76 # return a list with one module name if it is robolectric. 77 return [mod] 78 if self.module_info.is_testable_module(mod_info): 79 # If test module defined srcs, input file_path should be defined 80 # in the src list of module. 81 module_srcs = mod_info.get(constants.MODULE_SRCS, []) 82 if file_path and os.path.relpath( 83 file_path, self.root_dir) not in module_srcs: 84 logging.debug('Skip module: %s for %s', mod, file_path) 85 # Collect those modules if they don't have srcs information 86 # in module-info, use this list if there's no other matched 87 # module with src information. 88 if not module_srcs: 89 testable_modules_no_srcs.append( 90 mod_info.get(constants.MODULE_NAME)) 91 continue 92 testable_modules.append(mod_info.get(constants.MODULE_NAME)) 93 if not testable_modules: 94 testable_modules.extend(testable_modules_no_srcs) 95 return test_finder_utils.extract_test_from_tests(testable_modules) 96 97 def _is_vts_module(self, module_name): 98 """Returns True if the module is a vts10 module, else False.""" 99 mod_info = self.module_info.get_module_info(module_name) 100 suites = [] 101 if mod_info: 102 suites = mod_info.get('compatibility_suites', []) 103 # Pull out all *ts (cts, tvts, etc) suites. 104 suites = [suite for suite in suites if suite not in _SUITES_TO_IGNORE] 105 return len(suites) == 1 and 'vts10' in suites 106 107 def _update_to_vts_test_info(self, test): 108 """Fill in the fields with vts10 specific info. 109 110 We need to update the runner to use the vts10 runner and also find the 111 test specific dependencies. 112 113 Args: 114 test: TestInfo to update with vts10 specific details. 115 116 Return: 117 TestInfo that is ready for the vts10 test runner. 118 """ 119 test.test_runner = self._VTS_TEST_RUNNER 120 config_file = os.path.join(self.root_dir, 121 test.data[constants.TI_REL_CONFIG]) 122 # Need to get out dir (special logic is to account for custom out dirs). 123 # The out dir is used to construct the build targets for the test deps. 124 out_dir = os.environ.get(constants.ANDROID_HOST_OUT) 125 custom_out_dir = os.environ.get(constants.ANDROID_OUT_DIR) 126 # If we're not an absolute custom out dir, get no-absolute out dir path. 127 if custom_out_dir is None or not os.path.isabs(custom_out_dir): 128 out_dir = os.path.relpath(out_dir, self.root_dir) 129 vts_out_dir = os.path.join(out_dir, 'vts10', 'android-vts10', 'testcases') 130 # Parse dependency of default staging plans. 131 xml_paths = test_finder_utils.search_integration_dirs( 132 constants.VTS_STAGING_PLAN, 133 self.module_info.get_paths(constants.VTS_TF_MODULE)) 134 vts_xmls = set() 135 vts_xmls.add(config_file) 136 for xml_path in xml_paths: 137 vts_xmls |= test_finder_utils.get_plans_from_vts_xml(xml_path) 138 for config_file in vts_xmls: 139 # Add in vts10 test build targets. 140 test.build_targets |= test_finder_utils.get_targets_from_vts_xml( 141 config_file, vts_out_dir, self.module_info) 142 test.build_targets.add('vts-test-core') 143 test.build_targets.add(test.test_name) 144 return test 145 146 def _update_to_robolectric_test_info(self, test): 147 """Update the fields for a robolectric test. 148 149 Args: 150 test: TestInfo to be updated with robolectric fields. 151 152 Returns: 153 TestInfo with robolectric fields. 154 """ 155 test.test_runner = self._ROBOLECTRIC_RUNNER 156 test.test_name = self.module_info.get_robolectric_test_name(test.test_name) 157 return test 158 159 def _process_test_info(self, test): 160 """Process the test info and return some fields updated/changed. 161 162 We need to check if the test found is a special module (like vts10) and 163 update the test_info fields (like test_runner) appropriately. 164 165 Args: 166 test: TestInfo that has been filled out by a find method. 167 168 Return: 169 TestInfo that has been modified as needed and return None if 170 this module can't be found in the module_info. 171 """ 172 module_name = test.test_name 173 mod_info = self.module_info.get_module_info(module_name) 174 if not mod_info: 175 return None 176 test.module_class = mod_info['class'] 177 test.install_locations = test_finder_utils.get_install_locations( 178 mod_info['installed']) 179 # Check if this is only a vts10 module. 180 if self._is_vts_module(test.test_name): 181 return self._update_to_vts_test_info(test) 182 if self.module_info.is_robolectric_test(test.test_name): 183 return self._update_to_robolectric_test_info(test) 184 rel_config = test.data[constants.TI_REL_CONFIG] 185 test.build_targets = self._get_build_targets(module_name, rel_config) 186 # For device side java test, it will use 187 # com.android.compatibility.testtype.DalvikTest as test runner in 188 # cts-dalvik-device-test-runner.jar 189 if self.module_info.is_auto_gen_test_config(module_name): 190 if constants.MODULE_CLASS_JAVA_LIBRARIES in test.module_class: 191 test.build_targets.update(test_finder_utils.DALVIK_TEST_DEPS) 192 # Update test name if the test belong to extra config which means it's 193 # test config name is not the same as module name. For extra config, it 194 # index will be greater or equal to 1. 195 try: 196 if (mod_info.get(constants.MODULE_TEST_CONFIG, []).index(rel_config) 197 > 0): 198 config_test_name = os.path.splitext(os.path.basename( 199 rel_config))[0] 200 logging.debug('Replace test_info.name(%s) to %s', 201 test.test_name, config_test_name) 202 test.test_name = config_test_name 203 except ValueError: 204 pass 205 return test 206 207 def _get_build_targets(self, module_name, rel_config): 208 """Get the test deps. 209 210 Args: 211 module_name: name of the test. 212 rel_config: XML for the given test. 213 214 Returns: 215 Set of build targets. 216 """ 217 targets = set() 218 if not self.module_info.is_auto_gen_test_config(module_name): 219 config_file = os.path.join(self.root_dir, rel_config) 220 targets = test_finder_utils.get_targets_from_xml(config_file, 221 self.module_info) 222 if constants.VTS_CORE_SUITE in self.module_info.get_module_info( 223 module_name).get(constants.MODULE_COMPATIBILITY_SUITES, []): 224 targets.add(constants.VTS_CORE_TF_MODULE) 225 for suite in self.module_info.get_module_info( 226 module_name).get(constants.MODULE_COMPATIBILITY_SUITES, []): 227 targets.update(constants.SUITE_DEPS.get(suite, [])) 228 for module_path in self.module_info.get_paths(module_name): 229 mod_dir = module_path.replace('/', '-') 230 targets.add(constants.MODULES_IN + mod_dir) 231 # (b/156457698) Force add vts_kernel_tests as build target if our test 232 # belong to REQUIRED_KERNEL_TEST_MODULES due to required_module option 233 # not working for sh_test in soong. 234 if module_name in constants.REQUIRED_KERNEL_TEST_MODULES: 235 targets.add('vts_kernel_tests') 236 # (b/184567849) Force adding module_name as a build_target. This will 237 # allow excluding MODULES-IN-* and prevent from missing build targets. 238 if module_name and self.module_info.is_module(module_name): 239 targets.add(module_name) 240 return targets 241 242 def _get_module_test_config(self, module_name, rel_config=None): 243 """Get the value of test_config in module_info. 244 245 Get the value of 'test_config' in module_info if its 246 auto_test_config is not true. 247 In this case, the test_config is specified by user. 248 If not, return rel_config. 249 250 Args: 251 module_name: A string of the test's module name. 252 rel_config: XML for the given test. 253 254 Returns: 255 A list of string of test_config path if found, else return rel_config. 256 """ 257 default_all_config = not (atest_configs.GLOBAL_ARGS and 258 atest_configs.GLOBAL_ARGS.test_config_select) 259 mod_info = self.module_info.get_module_info(module_name) 260 if mod_info: 261 test_configs = [] 262 test_config_list = mod_info.get(constants.MODULE_TEST_CONFIG, []) 263 if test_config_list: 264 # multiple test configs 265 if len(test_config_list) > 1: 266 test_configs = test_finder_utils.extract_test_from_tests( 267 test_config_list, default_all=default_all_config) 268 else: 269 test_configs = test_config_list 270 if test_configs: 271 return test_configs 272 # Double check if below section is needed. 273 if (not self.module_info.is_auto_gen_test_config(module_name) 274 and len(test_configs) > 0): 275 return test_configs 276 return [rel_config] if rel_config else [] 277 278 # pylint: disable=too-many-branches 279 # pylint: disable=too-many-locals 280 def _get_test_info_filter(self, path, methods, **kwargs): 281 """Get test info filter. 282 283 Args: 284 path: A string of the test's path. 285 methods: A set of method name strings. 286 rel_module_dir: Optional. A string of the module dir no-absolute to 287 root. 288 class_name: Optional. A string of the class name. 289 is_native_test: Optional. A boolean variable of whether to search 290 for a native test or not. 291 292 Returns: 293 A set of test info filter. 294 """ 295 _, file_name = test_finder_utils.get_dir_path_and_filename(path) 296 ti_filter = frozenset() 297 if kwargs.get('is_native_test', None): 298 ti_filter = frozenset([test_info.TestFilter( 299 test_finder_utils.get_cc_filter( 300 kwargs.get('class_name', '*'), methods), frozenset())]) 301 # Path to java file. 302 elif file_name and constants.JAVA_EXT_RE.match(file_name): 303 full_class_name = test_finder_utils.get_fully_qualified_class_name( 304 path) 305 # If input class is parameterized java class, adding * to the end of 306 # method filter string to make sure the generated method name could 307 # be run. 308 if test_finder_utils.is_parameterized_java_class(path): 309 update_methods = [] 310 for method in methods: 311 update_methods.append(method + '*') 312 methods = frozenset(update_methods) 313 ti_filter = frozenset( 314 [test_info.TestFilter(full_class_name, methods)]) 315 # Path to cc file. 316 elif file_name and constants.CC_EXT_RE.match(file_name): 317 # TODO (b/173019813) Should setup correct filter for an input file. 318 if not test_finder_utils.has_cc_class(path): 319 raise atest_error.MissingCCTestCaseError( 320 "Can't find CC class in %s" % path) 321 # Extract class_name, method_name and parameterized_class from 322 # the given cc path. 323 file_classes, _, file_para_classes = ( 324 test_finder_utils.get_cc_test_classes_methods(path)) 325 cc_filters = [] 326 # When instantiate tests found, recompose the class name in 327 # $(InstantiationName)/$(ClassName) 328 for file_class in file_classes: 329 if file_class in file_para_classes: 330 file_class = '*/%s' % file_class 331 cc_filters.append( 332 test_info.TestFilter( 333 test_finder_utils.get_cc_filter(file_class, methods), 334 frozenset())) 335 ti_filter = frozenset(cc_filters) 336 # If input path is a folder and have class_name information. 337 elif (not file_name and kwargs.get('class_name', None)): 338 ti_filter = frozenset( 339 [test_info.TestFilter(kwargs.get('class_name', None), methods)]) 340 # Path to non-module dir, treat as package. 341 elif (not file_name 342 and kwargs.get('rel_module_dir', None) != 343 os.path.relpath(path, self.root_dir)): 344 dir_items = [os.path.join(path, f) for f in os.listdir(path)] 345 for dir_item in dir_items: 346 if constants.JAVA_EXT_RE.match(dir_item): 347 package_name = test_finder_utils.get_package_name(dir_item) 348 if package_name: 349 # methods should be empty frozenset for package. 350 if methods: 351 raise atest_error.MethodWithoutClassError( 352 '%s: Method filtering requires class' 353 % str(methods)) 354 ti_filter = frozenset( 355 [test_info.TestFilter(package_name, methods)]) 356 break 357 logging.debug('_get_test_info_filter() ti_filter: %s', ti_filter) 358 return ti_filter 359 360 def _get_rel_config(self, test_path): 361 """Get config file's no-absolute path. 362 363 Args: 364 test_path: A string of the test absolute path. 365 366 Returns: 367 A string of config's no-absolute path, else None. 368 """ 369 test_dir = os.path.dirname(test_path) 370 rel_module_dir = test_finder_utils.find_parent_module_dir( 371 self.root_dir, test_dir, self.module_info) 372 if rel_module_dir: 373 return os.path.join(rel_module_dir, constants.MODULE_CONFIG) 374 return None 375 376 def _get_test_infos(self, test_path, rel_config, module_name, test_filter): 377 """Get test_info for test_path. 378 379 Args: 380 test_path: A string of the test path. 381 rel_config: A string of rel path of config. 382 module_name: A string of the module name to use. 383 test_filter: A test info filter. 384 385 Returns: 386 A list of TestInfo namedtuple if found, else None. 387 """ 388 if not rel_config: 389 rel_config = self._get_rel_config(test_path) 390 if not rel_config: 391 return None 392 if module_name: 393 module_names = [module_name] 394 else: 395 module_names = self._determine_testable_module( 396 os.path.dirname(rel_config), 397 test_path if self._is_comparted_src(test_path) else None) 398 test_infos = [] 399 if module_names: 400 for mname in module_names: 401 # The real test config might be record in module-info. 402 rel_configs = self._get_module_test_config( 403 mname, rel_config=rel_config) 404 for rel_cfg in rel_configs: 405 mod_info = self.module_info.get_module_info(mname) 406 tinfo = self._process_test_info(test_info.TestInfo( 407 test_name=mname, 408 test_runner=self._TEST_RUNNER, 409 build_targets=set(), 410 data={constants.TI_FILTER: test_filter, 411 constants.TI_REL_CONFIG: rel_cfg}, 412 compatibility_suites=mod_info.get( 413 constants.MODULE_COMPATIBILITY_SUITES, []))) 414 if tinfo: 415 test_infos.append(tinfo) 416 return test_infos 417 418 def find_test_by_module_name(self, module_name): 419 """Find test for the given module name. 420 421 Args: 422 module_name: A string of the test's module name. 423 424 Returns: 425 A list that includes only 1 populated TestInfo namedtuple 426 if found, otherwise None. 427 """ 428 tinfos = [] 429 mod_info = self.module_info.get_module_info(module_name) 430 if self.module_info.is_testable_module(mod_info): 431 # path is a list with only 1 element. 432 rel_config = os.path.join(mod_info['path'][0], 433 constants.MODULE_CONFIG) 434 rel_configs = self._get_module_test_config(module_name, 435 rel_config=rel_config) 436 for rel_config in rel_configs: 437 tinfo = self._process_test_info(test_info.TestInfo( 438 test_name=module_name, 439 test_runner=self._TEST_RUNNER, 440 build_targets=set(), 441 data={constants.TI_REL_CONFIG: rel_config, 442 constants.TI_FILTER: frozenset()}, 443 compatibility_suites=mod_info.get( 444 constants.MODULE_COMPATIBILITY_SUITES, []))) 445 if tinfo: 446 tinfos.append(tinfo) 447 if tinfos: 448 return tinfos 449 return None 450 451 def find_test_by_kernel_class_name(self, module_name, class_name): 452 """Find kernel test for the given class name. 453 454 Args: 455 module_name: A string of the module name to use. 456 class_name: A string of the test's class name. 457 458 Returns: 459 A list of populated TestInfo namedtuple if test found, else None. 460 """ 461 462 class_name, methods = test_finder_utils.split_methods(class_name) 463 test_configs = self._get_module_test_config(module_name) 464 if not test_configs: 465 return None 466 tinfos = [] 467 for test_config in test_configs: 468 test_config_path = os.path.join(self.root_dir, test_config) 469 mod_info = self.module_info.get_module_info(module_name) 470 ti_filter = frozenset( 471 [test_info.TestFilter(class_name, methods)]) 472 if test_finder_utils.is_test_from_kernel_xml(test_config_path, class_name): 473 tinfo = self._process_test_info(test_info.TestInfo( 474 test_name=module_name, 475 test_runner=self._TEST_RUNNER, 476 build_targets=set(), 477 data={constants.TI_REL_CONFIG: test_config, 478 constants.TI_FILTER: ti_filter}, 479 compatibility_suites=mod_info.get( 480 constants.MODULE_COMPATIBILITY_SUITES, []))) 481 if tinfo: 482 tinfos.append(tinfo) 483 if tinfos: 484 return tinfos 485 return None 486 487 def find_test_by_class_name(self, class_name, module_name=None, 488 rel_config=None, is_native_test=False): 489 """Find test files given a class name. 490 491 If module_name and rel_config not given it will calculate it determine 492 it by looking up the tree from the class file. 493 494 Args: 495 class_name: A string of the test's class name. 496 module_name: Optional. A string of the module name to use. 497 rel_config: Optional. A string of module dir no-absolute to repo root. 498 is_native_test: A boolean variable of whether to search for a 499 native test or not. 500 501 Returns: 502 A list of populated TestInfo namedtuple if test found, else None. 503 """ 504 class_name, methods = test_finder_utils.split_methods(class_name) 505 search_class_name = class_name 506 # For parameterized gtest, test class will be automerged to 507 # $(class_prefix)/$(base_class) name. Using $(base_class) for searching 508 # matched TEST_P to make sure test class is matched. 509 if '/' in search_class_name: 510 search_class_name = str(search_class_name).split('/')[-1] 511 if rel_config: 512 search_dir = os.path.join(self.root_dir, 513 os.path.dirname(rel_config)) 514 else: 515 search_dir = self.root_dir 516 test_paths = test_finder_utils.find_class_file(search_dir, search_class_name, 517 is_native_test, methods) 518 if not test_paths and rel_config: 519 logging.info('Did not find class (%s) under module path (%s), ' 520 'researching from repo root.', class_name, rel_config) 521 test_paths = test_finder_utils.find_class_file(self.root_dir, 522 search_class_name, 523 is_native_test, 524 methods) 525 test_paths = test_paths if test_paths is not None else [] 526 # If we already have module name, use path in module-info as test_path. 527 if not test_paths: 528 if not module_name: 529 return None 530 # Use the module path as test_path. 531 module_paths = self.module_info.get_paths(module_name) 532 test_paths = [] 533 for rel_module_path in module_paths: 534 test_paths.append(os.path.join(self.root_dir, rel_module_path)) 535 tinfos = [] 536 for test_path in test_paths: 537 test_filter = self._get_test_info_filter( 538 test_path, methods, class_name=class_name, 539 is_native_test=is_native_test) 540 test_infos = self._get_test_infos( 541 test_path, rel_config, module_name, test_filter) 542 # If input include methods, check if tinfo match. 543 if test_infos and len(test_infos) > 1 and methods: 544 test_infos = self._get_matched_test_infos(test_infos, methods) 545 if test_infos: 546 tinfos.extend(test_infos) 547 return tinfos if tinfos else None 548 549 def _get_matched_test_infos(self, test_infos, methods): 550 """Get the test_infos matched the given methods. 551 552 Args: 553 test_infos: A list of TestInfo obj. 554 methods: A set of method name strings. 555 556 Returns: 557 A list of matched TestInfo namedtuple, else None. 558 """ 559 matched_test_infos = set() 560 for tinfo in test_infos: 561 test_config, test_srcs = test_finder_utils.get_test_config_and_srcs( 562 tinfo, self.module_info) 563 if test_config: 564 filter_dict = atest_utils.get_android_junit_config_filters( 565 test_config) 566 # Always treat the test_info is matched if no filters found. 567 if not filter_dict.keys(): 568 matched_test_infos.add(tinfo) 569 continue 570 for method in methods: 571 if self._is_srcs_match_method_annotation(method, test_srcs, 572 filter_dict): 573 logging.debug('For method:%s Test:%s matched ' 574 'filter_dict: %s', method, 575 tinfo.test_name, filter_dict) 576 matched_test_infos.add(tinfo) 577 return list(matched_test_infos) 578 579 def _is_srcs_match_method_annotation(self, method, srcs, annotation_dict): 580 """Check if input srcs matched annotation. 581 582 Args: 583 method: A string of test method name. 584 srcs: A list of source file of test. 585 annotation_dict: A dictionary record the include and exclude 586 annotations. 587 588 Returns: 589 True if input method matched the annotation of input srcs, else 590 None. 591 """ 592 include_annotations = annotation_dict.get( 593 constants.INCLUDE_ANNOTATION, []) 594 exclude_annotations = annotation_dict.get( 595 constants.EXCLUDE_ANNOTATION, []) 596 for src in srcs: 597 include_methods = set() 598 src_path = os.path.join(self.root_dir, src) 599 # Add methods matched include_annotations. 600 for annotation in include_annotations: 601 include_methods.update( 602 test_finder_utils.get_annotated_methods( 603 annotation, src_path)) 604 if exclude_annotations: 605 # For exclude annotation, get all the method in the input srcs, 606 # and filter out the matched annotation. 607 exclude_methods = set() 608 all_methods = test_finder_utils.get_java_methods(src_path) 609 for annotation in exclude_annotations: 610 exclude_methods.update( 611 test_finder_utils.get_annotated_methods( 612 annotation, src_path)) 613 include_methods = all_methods - exclude_methods 614 if method in include_methods: 615 return True 616 return False 617 618 def find_test_by_module_and_class(self, module_class): 619 """Find the test info given a MODULE:CLASS string. 620 621 Args: 622 module_class: A string of form MODULE:CLASS or MODULE:CLASS#METHOD. 623 624 Returns: 625 A list of populated TestInfo namedtuple if found, else None. 626 """ 627 if ':' not in module_class: 628 return None 629 module_name, class_name = module_class.split(':') 630 # module_infos is a list with at most 1 element. 631 module_infos = self.find_test_by_module_name(module_name) 632 module_info = module_infos[0] if module_infos else None 633 if not module_info: 634 return None 635 find_result = None 636 # If the target module is NATIVE_TEST, search CC classes only. 637 if not self.module_info.is_native_test(module_name): 638 # Find by java class. 639 find_result = self.find_test_by_class_name( 640 class_name, module_info.test_name, 641 module_info.data.get(constants.TI_REL_CONFIG)) 642 # kernel target test is also define as NATIVE_TEST in build system. 643 # TODO (b/157210083) Update find_test_by_kernel_class_name method to 644 # support gen_rule use case. 645 if not find_result: 646 find_result = self.find_test_by_kernel_class_name( 647 module_name, class_name) 648 # Find by cc class. 649 if not find_result: 650 find_result = self.find_test_by_cc_class_name( 651 class_name, module_info.test_name, 652 module_info.data.get(constants.TI_REL_CONFIG)) 653 return find_result 654 655 def find_test_by_package_name(self, package, module_name=None, 656 rel_config=None): 657 """Find the test info given a PACKAGE string. 658 659 Args: 660 package: A string of the package name. 661 module_name: Optional. A string of the module name. 662 ref_config: Optional. A string of rel path of config. 663 664 Returns: 665 A list of populated TestInfo namedtuple if found, else None. 666 """ 667 _, methods = test_finder_utils.split_methods(package) 668 if methods: 669 raise atest_error.MethodWithoutClassError('%s: Method filtering ' 670 'requires class' % ( 671 methods)) 672 # Confirm that packages exists and get user input for multiples. 673 if rel_config: 674 search_dir = os.path.join(self.root_dir, 675 os.path.dirname(rel_config)) 676 else: 677 search_dir = self.root_dir 678 package_paths = test_finder_utils.run_find_cmd( 679 test_finder_utils.FIND_REFERENCE_TYPE.PACKAGE, search_dir, package) 680 package_paths = package_paths if package_paths is not None else [] 681 # Package path will be the full path to the dir represented by package. 682 if not package_paths: 683 if not module_name: 684 return None 685 module_paths = self.module_info.get_paths(module_name) 686 for rel_module_path in module_paths: 687 package_paths.append(os.path.join(self.root_dir, rel_module_path)) 688 test_filter = frozenset([test_info.TestFilter(package, frozenset())]) 689 test_infos = [] 690 for package_path in package_paths: 691 tinfo = self._get_test_infos(package_path, rel_config, 692 module_name, test_filter) 693 if tinfo: 694 test_infos.extend(tinfo) 695 return test_infos if test_infos else None 696 697 def find_test_by_module_and_package(self, module_package): 698 """Find the test info given a MODULE:PACKAGE string. 699 700 Args: 701 module_package: A string of form MODULE:PACKAGE 702 703 Returns: 704 A list of populated TestInfo namedtuple if found, else None. 705 """ 706 module_name, package = module_package.split(':') 707 # module_infos is a list with at most 1 element. 708 module_infos = self.find_test_by_module_name(module_name) 709 module_info = module_infos[0] if module_infos else None 710 if not module_info: 711 return None 712 return self.find_test_by_package_name( 713 package, module_info.test_name, 714 module_info.data.get(constants.TI_REL_CONFIG)) 715 716 def find_test_by_path(self, rel_path): 717 """Find the first test info matching the given path. 718 719 Strategy: 720 path_to_java_file --> Resolve to CLASS 721 path_to_cc_file --> Resolve to CC CLASS 722 path_to_module_file -> Resolve to MODULE 723 path_to_module_dir -> Resolve to MODULE 724 path_to_dir_with_class_files--> Resolve to PACKAGE 725 path_to_any_other_dir --> Resolve as MODULE 726 727 Args: 728 rel_path: A string of the relative path to $BUILD_TOP. 729 730 Returns: 731 A list of populated TestInfo namedtuple if test found, else None 732 """ 733 logging.debug('Finding test by path: %s', rel_path) 734 path, methods = test_finder_utils.split_methods(rel_path) 735 # TODO: See if this can be generalized and shared with methods above 736 # create absolute path from cwd and remove symbolic links 737 path = os.path.realpath(path) 738 if not os.path.exists(path): 739 return None 740 if (methods and 741 not test_finder_utils.has_method_in_file(path, methods)): 742 return None 743 dir_path, _ = test_finder_utils.get_dir_path_and_filename(path) 744 # Module/Class 745 rel_module_dir = test_finder_utils.find_parent_module_dir( 746 self.root_dir, dir_path, self.module_info) 747 if not rel_module_dir: 748 # Try to find unit-test for input path. 749 path = os.path.relpath( 750 os.path.realpath(rel_path), 751 os.environ.get(constants.ANDROID_BUILD_TOP, '')) 752 unit_tests = test_finder_utils.find_host_unit_tests( 753 self.module_info, path) 754 if unit_tests: 755 tinfos = [] 756 for unit_test in unit_tests: 757 tinfo = self._get_test_infos(path, constants.MODULE_CONFIG, 758 unit_test, frozenset()) 759 if tinfo: 760 tinfos.extend(tinfo) 761 return tinfos 762 return None 763 rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG) 764 test_filter = self._get_test_info_filter(path, methods, 765 rel_module_dir=rel_module_dir) 766 return self._get_test_infos(path, rel_config, None, test_filter) 767 768 def find_test_by_cc_class_name(self, class_name, module_name=None, 769 rel_config=None): 770 """Find test files given a cc class name. 771 772 If module_name and rel_config not given, test will be determined 773 by looking up the tree for files which has input class. 774 775 Args: 776 class_name: A string of the test's class name. 777 module_name: Optional. A string of the module name to use. 778 rel_config: Optional. A string of module dir no-absolute to repo root. 779 780 Returns: 781 A list of populated TestInfo namedtuple if test found, else None. 782 """ 783 # Check if class_name is prepended with file name. If so, trim the 784 # prefix and keep only the class_name. 785 if '.' in class_name: 786 # Assume the class name has a format of file_name.class_name 787 class_name = class_name[class_name.rindex('.')+1:] 788 logging.info('Search with updated class name: %s', class_name) 789 return self.find_test_by_class_name( 790 class_name, module_name, rel_config, is_native_test=True) 791 792 def get_testable_modules_with_ld(self, user_input, ld_range=0): 793 """Calculate the edit distances of the input and testable modules. 794 795 The user input will be calculated across all testable modules and 796 results in integers generated by Levenshtein Distance algorithm. 797 To increase the speed of the calculation, a bound can be applied to 798 this method to prevent from calculating every testable modules. 799 800 Guessing from typos, e.g. atest atest_unitests, implies a tangible range 801 of length that Atest only needs to search within it, and the default of 802 the bound is 2. 803 804 Guessing from keywords however, e.g. atest --search Camera, means that 805 the uncertainty of the module name is way higher, and Atest should walk 806 through all testable modules and return the highest possibilities. 807 808 Args: 809 user_input: A string of the user input. 810 ld_range: An integer that range the searching scope. If the length 811 of user_input is 10, then Atest will calculate modules of 812 which length is between 8 and 12. 0 is equivalent to 813 unlimited. 814 815 Returns: 816 A List of LDs and possible module names. If the user_input is "fax", 817 the output will be like: 818 [[2, "fog"], [2, "Fix"], [4, "duck"], [7, "Duckies"]] 819 820 Which means the most lilely names of "fax" are fog and Fix(LD=2), 821 while Dickies is the most unlikely one(LD=7). 822 """ 823 atest_utils.colorful_print('\nSearching for similar module names using ' 824 'fuzzy search...', constants.CYAN) 825 testable_modules = sorted(self.module_info.get_testable_modules(), 826 key=len) 827 lower_bound = len(user_input) - ld_range 828 upper_bound = len(user_input) + ld_range 829 testable_modules_with_ld = [] 830 for module_name in testable_modules: 831 # Dispose those too short or too lengthy. 832 if ld_range != 0: 833 if len(module_name) < lower_bound: 834 continue 835 if len(module_name) > upper_bound: 836 break 837 testable_modules_with_ld.append( 838 [test_finder_utils.get_levenshtein_distance( 839 user_input, module_name), module_name]) 840 return testable_modules_with_ld 841 842 def get_fuzzy_searching_results(self, user_input): 843 """Give results which have no more than allowance of edit distances. 844 845 Args: 846 user_input: the target module name for fuzzy searching. 847 848 Return: 849 A list of guessed modules. 850 """ 851 modules_with_ld = self.get_testable_modules_with_ld( 852 user_input, ld_range=constants.LD_RANGE) 853 guessed_modules = [] 854 for _distance, _module in modules_with_ld: 855 if _distance <= abs(constants.LD_RANGE): 856 guessed_modules.append(_module) 857 return guessed_modules 858 859 def find_test_by_config_name(self, config_name): 860 """Find test for the given config name. 861 862 Args: 863 config_name: A string of the test's config name. 864 865 Returns: 866 A list that includes only 1 populated TestInfo namedtuple 867 if found, otherwise None. 868 """ 869 for module_name, mod_info in self.module_info.name_to_module_info.items(): 870 test_configs = mod_info.get(constants.MODULE_TEST_CONFIG, []) 871 for test_config in test_configs: 872 test_config_name = os.path.splitext( 873 os.path.basename(test_config))[0] 874 if test_config_name == config_name: 875 tinfo = test_info.TestInfo( 876 test_name=test_config_name, 877 test_runner=self._TEST_RUNNER, 878 build_targets=self._get_build_targets(module_name, 879 test_config), 880 data={constants.TI_REL_CONFIG: test_config, 881 constants.TI_FILTER: frozenset()}, 882 compatibility_suites=mod_info.get( 883 constants.MODULE_COMPATIBILITY_SUITES, [])) 884 if tinfo: 885 # There should have only one test_config with the same 886 # name in source tree. 887 return [tinfo] 888 return None 889 890 def _is_comparted_src(self, path): 891 """Check if the input path need to match srcs information in module. 892 893 If path is a folder or android build file, we don't need to compart 894 with module's srcs. 895 896 Args: 897 path: A string of the test's path. 898 899 Returns: 900 True if input path need to match with module's src info, else False. 901 """ 902 if os.path.isdir(path): 903 return False 904 if atest_utils.is_build_file(path): 905 return False 906 return True 907