• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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