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-branches
123# pylint: disable=too-many-return-statements
124def _get_test_reference_types(ref):
125    """Determine type of test reference based on the content of string.
126
127    Examples:
128        The string 'SequentialRWTest' could be a reference to
129        a Module or a Class name.
130
131        The string 'cts/tests/filesystem' could be a Path, Integration
132        or Suite reference.
133
134    Args:
135        ref: A string referencing a test.
136
137    Returns:
138        A list of possible REFERENCE_TYPEs (ints) for reference string.
139    """
140    if ref.startswith('.') or '..' in ref:
141        return [_REFERENCE_TYPE.CACHE,
142                _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
143                _REFERENCE_TYPE.MODULE_FILE_PATH,
144                _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH]
145    if '/' in ref:
146        if ref.startswith('/'):
147            return [_REFERENCE_TYPE.CACHE,
148                    _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
149                    _REFERENCE_TYPE.MODULE_FILE_PATH,
150                    _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH]
151        if ':' in ref:
152            return [_REFERENCE_TYPE.CACHE,
153                    _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
154                    _REFERENCE_TYPE.MODULE_FILE_PATH,
155                    _REFERENCE_TYPE.INTEGRATION,
156                    _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH,
157                    _REFERENCE_TYPE.MODULE_CLASS]
158        return [_REFERENCE_TYPE.CACHE,
159                _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
160                _REFERENCE_TYPE.MODULE_FILE_PATH,
161                _REFERENCE_TYPE.INTEGRATION,
162                _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH,
163                _REFERENCE_TYPE.CC_CLASS,
164                # TODO: Uncomment in SUITE when it's supported
165                # _REFERENCE_TYPE.SUITE
166                ]
167    if '.' in ref:
168        ref_end = ref.rsplit('.', 1)[-1]
169        ref_end_is_upper = ref_end[0].isupper()
170    if ':' in ref:
171        if '.' in ref:
172            if ref_end_is_upper:
173                # Module:fully.qualified.Class or Integration:fully.q.Class
174                return [_REFERENCE_TYPE.CACHE,
175                        _REFERENCE_TYPE.INTEGRATION,
176                        _REFERENCE_TYPE.MODULE_CLASS]
177            # Module:some.package
178            return [_REFERENCE_TYPE.CACHE, _REFERENCE_TYPE.MODULE_PACKAGE,
179                    _REFERENCE_TYPE.MODULE_CLASS]
180        # Module:Class or IntegrationName:Class
181        return [_REFERENCE_TYPE.CACHE,
182                _REFERENCE_TYPE.INTEGRATION,
183                _REFERENCE_TYPE.MODULE_CLASS]
184    if '.' in ref:
185        # The string of ref_end possibly includes specific mathods, e.g.
186        # foo.java#method, so let ref_end be the first part of splitting '#'.
187        if "#" in ref_end:
188            ref_end = ref_end.split('#')[0]
189        if ref_end in ('java', 'kt', 'bp', 'mk', 'cc', 'cpp'):
190            return [_REFERENCE_TYPE.CACHE, _REFERENCE_TYPE.MODULE_FILE_PATH]
191        if ref_end == 'xml':
192            return [_REFERENCE_TYPE.CACHE,
193                    _REFERENCE_TYPE.INTEGRATION_FILE_PATH,
194                    _REFERENCE_TYPE.SUITE_PLAN_FILE_PATH]
195        if ref_end_is_upper:
196            return [_REFERENCE_TYPE.CACHE, _REFERENCE_TYPE.QUALIFIED_CLASS]
197        return [_REFERENCE_TYPE.CACHE,
198                _REFERENCE_TYPE.MODULE,
199                _REFERENCE_TYPE.PACKAGE]
200    # Note: We assume that if you're referencing a file in your cwd,
201    # that file must have a '.' in its name, i.e. foo.java, foo.xml.
202    # If this ever becomes not the case, then we need to include path below.
203    return [_REFERENCE_TYPE.CACHE,
204            _REFERENCE_TYPE.INTEGRATION,
205            # TODO: Uncomment in SUITE when it's supported
206            # _REFERENCE_TYPE.SUITE,
207            _REFERENCE_TYPE.MODULE,
208            _REFERENCE_TYPE.SUITE_PLAN,
209            _REFERENCE_TYPE.CLASS,
210            _REFERENCE_TYPE.CC_CLASS]
211
212
213def _get_registered_find_methods(module_info):
214    """Return list of registered find methods.
215
216    This is used to return find methods that were not listed in the
217    default find methods but just registered in the finder classes. These
218    find methods will run before the default find methods.
219
220    Args:
221        module_info: ModuleInfo for finder classes to instantiate with.
222
223    Returns:
224        List of registered find methods.
225    """
226    find_methods = []
227    finder_instance_dict = _get_finder_instance_dict(module_info)
228    for finder in _get_test_finders():
229        finder_instance = finder_instance_dict[finder.NAME]
230        for find_method_info in finder_instance.get_all_find_methods():
231            find_methods.append(test_finder_base.Finder(
232                finder_instance, find_method_info.find_method, finder.NAME))
233    return find_methods
234
235
236def _get_default_find_methods(module_info, test):
237    """Default find methods to be used based on the given test name.
238
239    Args:
240        module_info: ModuleInfo for finder instances to use.
241        test: String of test name to help determine which find methods
242              to utilize.
243
244    Returns:
245        List of find methods to use.
246    """
247    find_methods = []
248    finder_instance_dict = _get_finder_instance_dict(module_info)
249    test_ref_types = _get_test_reference_types(test)
250    logging.debug('Resolved input to possible references: %s', [
251        _REFERENCE_TYPE[t] for t in test_ref_types])
252    for test_ref_type in test_ref_types:
253        find_method = _REF_TYPE_TO_FUNC_MAP[test_ref_type]
254        finder_instance = finder_instance_dict[inspect._findclass(find_method).NAME]
255        finder_info = _REFERENCE_TYPE[test_ref_type]
256        find_methods.append(test_finder_base.Finder(finder_instance,
257                                                    find_method,
258                                                    finder_info))
259    return find_methods
260
261
262def get_find_methods_for_test(module_info, test):
263    """Return a list of ordered find methods.
264
265    Args:
266      test: String of test name to get find methods for.
267
268    Returns:
269        List of ordered find methods.
270    """
271    registered_find_methods = _get_registered_find_methods(module_info)
272    default_find_methods = _get_default_find_methods(module_info, test)
273    return registered_find_methods + default_find_methods
274