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"""
16Suite Plan Finder class.
17"""
18
19import logging
20import os
21import re
22
23import constants
24
25from test_finders import test_finder_base
26from test_finders import test_finder_utils
27from test_finders import test_info
28from test_runners import suite_plan_test_runner
29
30_SUITE_PLAN_NAME_RE = re.compile(r'^.*\/(?P<suite>.*)-tradefed\/res\/config\/'
31                                 r'(?P<suite_plan_name>.*).xml$')
32
33
34class SuitePlanFinder(test_finder_base.TestFinderBase):
35    """Suite Plan Finder class."""
36    NAME = 'SUITE_PLAN'
37    _SUITE_PLAN_TEST_RUNNER = suite_plan_test_runner.SuitePlanTestRunner.NAME
38
39    def __init__(self, module_info=None):
40        super(SuitePlanFinder, self).__init__()
41        self.root_dir = os.environ.get(constants.ANDROID_BUILD_TOP)
42        self.mod_info = module_info
43        self.suite_plan_dirs = self._get_suite_plan_dirs()
44
45    def _get_mod_paths(self, module_name):
46        """Return the paths of the given module name."""
47        if self.mod_info:
48            return self.mod_info.get_paths(module_name)
49        return []
50
51    def _get_suite_plan_dirs(self):
52        """Get suite plan dirs from MODULE_INFO based on targets.
53
54        Strategy:
55            Search module-info.json using SUITE_PLANS to get all the suite
56            plan dirs.
57
58        Returns:
59            A tuple of lists of strings of suite plan dir rel to repo root.
60            None if the path can not be found in module-info.json.
61        """
62        return [d for x in constants.SUITE_PLANS for d in
63                self._get_mod_paths(x+'-tradefed') if d is not None]
64
65    def _get_test_info_from_path(self, path, suite_name=None):
66        """Get the test info from the result of using regular expression
67        matching with the give path.
68
69        Args:
70            path: A string of the test's absolute or relative path.
71            suite_name: A string of the suite name.
72
73        Returns:
74            A populated TestInfo namedtuple if regular expression
75            matches, else None.
76        """
77        # Don't use names that simply match the path,
78        # must be the actual name used by *TS to run the test.
79        match = _SUITE_PLAN_NAME_RE.match(path)
80        if not match:
81            logging.error('Suite plan test outside config dir: %s', path)
82            return None
83        suite = match.group('suite')
84        suite_plan_name = match.group('suite_plan_name')
85        if suite_name:
86            if suite_plan_name != suite_name:
87                logging.warning('Input (%s) not valid suite plan name, '
88                                'did you mean: %s?',
89                                suite_name, suite_plan_name)
90                return None
91        return test_info.TestInfo(
92            test_name=suite_plan_name,
93            test_runner=self._SUITE_PLAN_TEST_RUNNER,
94            build_targets=set([suite]),
95            suite=suite)
96
97    def find_test_by_suite_path(self, suite_path):
98        """Find the first test info matching the given path.
99
100        Strategy:
101            If suite_path is to file --> Return TestInfo if the file
102            exists in the suite plan dirs, else return None.
103            If suite_path is to dir --> Return None
104
105        Args:
106            suite_path: A string of the path to the test's file or dir.
107
108        Returns:
109            A list of populated TestInfo namedtuple if test found, else None.
110            This is a list with at most 1 element.
111        """
112        path, _ = test_finder_utils.split_methods(suite_path)
113        # Make sure we're looking for a config.
114        if not path.endswith('.xml'):
115            return None
116        path = os.path.realpath(path)
117        suite_plan_dir = test_finder_utils.get_int_dir_from_path(
118            path, self.suite_plan_dirs)
119        if suite_plan_dir:
120            rel_config = os.path.relpath(path, self.root_dir)
121            return [self._get_test_info_from_path(rel_config)]
122        return None
123
124    def find_test_by_suite_name(self, suite_name):
125        """Find the test for the given suite name.
126
127        Strategy:
128            If suite_name is cts --> Return TestInfo to indicate suite runner
129            to make cts and run test using cts-tradefed.
130            If suite_name is cts-common --> Return TestInfo to indicate suite
131            runner to make cts and run test using cts-tradefed if file exists
132            in the suite plan dirs, else return None.
133
134        Args:
135            suite_name: A string of suite name.
136
137        Returns:
138            A list of populated TestInfo namedtuple if suite_name matches
139            a suite in constants.SUITE_PLAN, else check if the file
140            existing in the suite plan dirs, else return None.
141        """
142        logging.debug('Finding test by suite: %s', suite_name)
143        test_infos = []
144        if suite_name in constants.SUITE_PLANS:
145            test_infos.append(test_info.TestInfo(
146                test_name=suite_name,
147                test_runner=self._SUITE_PLAN_TEST_RUNNER,
148                build_targets=set([suite_name]),
149                suite=suite_name))
150        else:
151            test_files = test_finder_utils.search_integration_dirs(
152                suite_name, self.suite_plan_dirs)
153            if not test_files:
154                return None
155            for test_file in test_files:
156                _test_info = self._get_test_info_from_path(test_file,
157                                                           suite_name)
158                if _test_info:
159                    test_infos.append(_test_info)
160        return test_infos
161