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"""
16Test Finder Handler module.
17"""
18
19# pylint: disable=line-too-long
20# pylint: disable=import-outside-toplevel
21# pylint: disable=protected-access
22
23import inspect
24import logging
25
26import atest_enum
27
28from test_finders import cache_finder
29from test_finders import test_finder_base
30from test_finders import suite_plan_finder
31from test_finders import tf_integration_finder
32from test_finders import module_finder
33
34# List of default test finder classes.
35_TEST_FINDERS = {
36    suite_plan_finder.SuitePlanFinder,
37    tf_integration_finder.TFIntegrationFinder,
38    module_finder.ModuleFinder,
39    cache_finder.CacheFinder,
40}
41
42# Explanation of REFERENCE_TYPEs:
43# ----------------------------------
44# 0. MODULE: LOCAL_MODULE or LOCAL_PACKAGE_NAME value in Android.mk/Android.bp.
45# 1. CLASS: Names which the same with a ClassName.java/kt file.
46# 2. QUALIFIED_CLASS: String like "a.b.c.ClassName".
47# 3. MODULE_CLASS: Combo of MODULE and CLASS as "module:class".
48# 4. PACKAGE: Package in java file. Same as file path to java file.
49# 5. MODULE_PACKAGE: Combo of MODULE and PACKAGE as "module:package".
50# 6. MODULE_FILE_PATH: File path to dir of tests or test itself.
51# 7. INTEGRATION_FILE_PATH: File path to config xml in one of the 4 integration
52#                           config directories.
53# 8. INTEGRATION: xml file name in one of the 4 integration config directories.
54# 9. SUITE: Value of the "run-suite-tag" in xml config file in 4 config dirs.
55#           Same as value of "test-suite-tag" in AndroidTest.xml files.
56# 10. CC_CLASS: Test case in cc file.
57# 11. SUITE_PLAN: Suite name such as cts.
58# 12. SUITE_PLAN_FILE_PATH: File path to config xml in the suite config
59#                           directories.
60# 13. CACHE: A pseudo type that runs cache_finder without finding test in real.
61_REFERENCE_TYPE = atest_enum.AtestEnum(['MODULE', 'CLASS', 'QUALIFIED_CLASS',
62                                        'MODULE_CLASS', 'PACKAGE',
63                                        'MODULE_PACKAGE', 'MODULE_FILE_PATH',
64                                        'INTEGRATION_FILE_PATH', 'INTEGRATION',
65                                        'SUITE', 'CC_CLASS', 'SUITE_PLAN',
66                                        'SUITE_PLAN_FILE_PATH', 'CACHE'])
67
68_REF_TYPE_TO_FUNC_MAP = {
69    _REFERENCE_TYPE.MODULE: module_finder.ModuleFinder.find_test_by_module_name,
70    _REFERENCE_TYPE.CLASS: module_finder.ModuleFinder.find_test_by_class_name,
71    _REFERENCE_TYPE.MODULE_CLASS: module_finder.ModuleFinder.find_test_by_module_and_class,
72    _REFERENCE_TYPE.QUALIFIED_CLASS: module_finder.ModuleFinder.find_test_by_class_name,
73    _REFERENCE_TYPE.PACKAGE: module_finder.ModuleFinder.find_test_by_package_name,
74    _REFERENCE_TYPE.MODULE_PACKAGE: module_finder.ModuleFinder.find_test_by_module_and_package,
75    _REFERENCE_TYPE.MODULE_FILE_PATH: module_finder.ModuleFinder.find_test_by_path,
76    _REFERENCE_TYPE.INTEGRATION_FILE_PATH:
77        tf_integration_finder.TFIntegrationFinder.find_int_test_by_path,
78    _REFERENCE_TYPE.INTEGRATION:
79        tf_integration_finder.TFIntegrationFinder.find_test_by_integration_name,
80    _REFERENCE_TYPE.CC_CLASS:
81        module_finder.ModuleFinder.find_test_by_cc_class_name,
82    _REFERENCE_TYPE.SUITE_PLAN:suite_plan_finder.SuitePlanFinder.find_test_by_suite_name,
83    _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH:
84        suite_plan_finder.SuitePlanFinder.find_test_by_suite_path,
85    _REFERENCE_TYPE.CACHE: cache_finder.CacheFinder.find_test_by_cache,
86}
87
88
89def _get_finder_instance_dict(module_info):
90    """Return dict of finder instances.
91
92    Args:
93        module_info: ModuleInfo for finder classes to use.
94
95    Returns:
96        Dict of finder instances keyed by their name.
97    """
98    instance_dict = {}
99    for finder in _get_test_finders():
100        instance_dict[finder.NAME] = finder(module_info=module_info)
101    return instance_dict
102
103
104def _get_test_finders():
105    """Returns the test finders.
106
107    If external test types are defined outside atest, they can be try-except
108    imported into here.
109
110    Returns:
111        Set of test finder classes.
112    """
113    test_finders_list = _TEST_FINDERS
114    # Example import of external test finder:
115    try:
116        from test_finders import example_finder
117        test_finders_list.add(example_finder.ExampleFinder)
118    except ImportError:
119        pass
120    return test_finders_list
121
122# pylint: disable=too-many-return-statements
123def _get_test_reference_types(ref):
124    """Determine type of test reference based on the content of string.
125
126    Examples:
127        The string 'SequentialRWTest' could be a reference to
128        a Module or a Class name.
129
130        The string 'cts/tests/filesystem' could be a Path, Integration
131        or Suite reference.
132
133    Args:
134        ref: A string referencing a test.
135
136    Returns:
137        A list of possible REFERENCE_TYPEs (ints) for reference string.
138    """
139    if ref.startswith('.') or '..' in ref:
140        return [_REFERENCE_TYPE.CACHE,
141                _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
142                _REFERENCE_TYPE.MODULE_FILE_PATH,
143                _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH]
144    if '/' in ref:
145        if ref.startswith('/'):
146            return [_REFERENCE_TYPE.CACHE,
147                    _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
148                    _REFERENCE_TYPE.MODULE_FILE_PATH,
149                    _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH]
150        return [_REFERENCE_TYPE.CACHE,
151                _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
152                _REFERENCE_TYPE.MODULE_FILE_PATH,
153                _REFERENCE_TYPE.INTEGRATION,
154                _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH,
155                # TODO: Uncomment in SUITE when it's supported
156                # _REFERENCE_TYPE.SUITE
157               ]
158    if '.' in ref:
159        ref_end = ref.rsplit('.', 1)[-1]
160        ref_end_is_upper = ref_end[0].isupper()
161    if ':' in ref:
162        if '.' in ref:
163            if ref_end_is_upper:
164                # Module:fully.qualified.Class or Integration:fully.q.Class
165                return [_REFERENCE_TYPE.CACHE,
166                        _REFERENCE_TYPE.INTEGRATION,
167                        _REFERENCE_TYPE.MODULE_CLASS]
168            # Module:some.package
169            return [_REFERENCE_TYPE.CACHE, _REFERENCE_TYPE.MODULE_PACKAGE,
170                    _REFERENCE_TYPE.MODULE_CLASS]
171        # Module:Class or IntegrationName:Class
172        return [_REFERENCE_TYPE.CACHE,
173                _REFERENCE_TYPE.INTEGRATION,
174                _REFERENCE_TYPE.MODULE_CLASS]
175    if '.' in ref:
176        # The string of ref_end possibly includes specific mathods, e.g.
177        # foo.java#method, so let ref_end be the first part of splitting '#'.
178        if "#" in ref_end:
179            ref_end = ref_end.split('#')[0]
180        if ref_end in ('java', 'kt', 'bp', 'mk', 'cc', 'cpp'):
181            return [_REFERENCE_TYPE.CACHE, _REFERENCE_TYPE.MODULE_FILE_PATH]
182        if ref_end == 'xml':
183            return [_REFERENCE_TYPE.CACHE,
184                    _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
185                    _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH]
186        if ref_end_is_upper:
187            return [_REFERENCE_TYPE.CACHE, _REFERENCE_TYPE.QUALIFIED_CLASS]
188        return [_REFERENCE_TYPE.CACHE,
189                _REFERENCE_TYPE.MODULE,
190                _REFERENCE_TYPE.PACKAGE]
191    # Note: We assume that if you're referencing a file in your cwd,
192    # that file must have a '.' in its name, i.e. foo.java, foo.xml.
193    # If this ever becomes not the case, then we need to include path below.
194    return [_REFERENCE_TYPE.CACHE,
195            _REFERENCE_TYPE.INTEGRATION,
196            # TODO: Uncomment in SUITE when it's supported
197            # _REFERENCE_TYPE.SUITE,
198            _REFERENCE_TYPE.MODULE,
199            _REFERENCE_TYPE.SUITE_PLAN,
200            _REFERENCE_TYPE.CLASS,
201            _REFERENCE_TYPE.CC_CLASS]
202
203
204def _get_registered_find_methods(module_info):
205    """Return list of registered find methods.
206
207    This is used to return find methods that were not listed in the
208    default find methods but just registered in the finder classes. These
209    find methods will run before the default find methods.
210
211    Args:
212        module_info: ModuleInfo for finder classes to instantiate with.
213
214    Returns:
215        List of registered find methods.
216    """
217    find_methods = []
218    finder_instance_dict = _get_finder_instance_dict(module_info)
219    for finder in _get_test_finders():
220        finder_instance = finder_instance_dict[finder.NAME]
221        for find_method_info in finder_instance.get_all_find_methods():
222            find_methods.append(test_finder_base.Finder(
223                finder_instance, find_method_info.find_method, finder.NAME))
224    return find_methods
225
226
227def _get_default_find_methods(module_info, test):
228    """Default find methods to be used based on the given test name.
229
230    Args:
231        module_info: ModuleInfo for finder instances to use.
232        test: String of test name to help determine which find methods
233              to utilize.
234
235    Returns:
236        List of find methods to use.
237    """
238    find_methods = []
239    finder_instance_dict = _get_finder_instance_dict(module_info)
240    test_ref_types = _get_test_reference_types(test)
241    logging.debug('Resolved input to possible references: %s', [
242        _REFERENCE_TYPE[t] for t in test_ref_types])
243    for test_ref_type in test_ref_types:
244        find_method = _REF_TYPE_TO_FUNC_MAP[test_ref_type]
245        finder_instance = finder_instance_dict[inspect._findclass(find_method).NAME]
246        finder_info = _REFERENCE_TYPE[test_ref_type]
247        find_methods.append(test_finder_base.Finder(finder_instance,
248                                                    find_method,
249                                                    finder_info))
250    return find_methods
251
252
253def get_find_methods_for_test(module_info, test):
254    """Return a list of ordered find methods.
255
256    Args:
257      test: String of test name to get find methods for.
258
259    Returns:
260        List of ordered find methods.
261    """
262    registered_find_methods = _get_registered_find_methods(module_info)
263    default_find_methods = _get_default_find_methods(module_info, test)
264    return registered_find_methods + default_find_methods
265