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
23# pylint: disable=import-error
24import constants
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.warn('Input (%s) not valid suite plan name, '
88                             'did you mean: %s?', suite_name, suite_plan_name)
89                return None
90        return test_info.TestInfo(
91            test_name=suite_plan_name,
92            test_runner=self._SUITE_PLAN_TEST_RUNNER,
93            build_targets=set([suite]),
94            suite=suite)
95
96    def find_test_by_suite_path(self, suite_path):
97        """Find the first test info matching the given path.
98
99        Strategy:
100            If suite_path is to file --> Return TestInfo if the file
101            exists in the suite plan dirs, else return None.
102            If suite_path is to dir --> Return None
103
104        Args:
105            suite_path: A string of the path to the test's file or dir.
106
107        Returns:
108            A list of populated TestInfo namedtuple if test found, else None.
109            This is a list with at most 1 element.
110        """
111        path, _ = test_finder_utils.split_methods(suite_path)
112        # Make sure we're looking for a config.
113        if not path.endswith('.xml'):
114            return None
115        path = os.path.realpath(path)
116        suite_plan_dir = test_finder_utils.get_int_dir_from_path(
117            path, self.suite_plan_dirs)
118        if suite_plan_dir:
119            rel_config = os.path.relpath(path, self.root_dir)
120            return [self._get_test_info_from_path(rel_config)]
121        return None
122
123    def find_test_by_suite_name(self, suite_name):
124        """Find the test for the given suite name.
125
126        Strategy:
127            If suite_name is cts --> Return TestInfo to indicate suite runner
128            to make cts and run test using cts-tradefed.
129            If suite_name is cts-common --> Return TestInfo to indicate suite
130            runner to make cts and run test using cts-tradefed if file exists
131            in the suite plan dirs, else return None.
132
133        Args:
134            suite_name: A string of suite name.
135
136        Returns:
137            A list of populated TestInfo namedtuple if suite_name matches
138            a suite in constants.SUITE_PLAN, else check if the file
139            existing in the suite plan dirs, else return None.
140        """
141        logging.debug('Finding test by suite: %s', suite_name)
142        test_infos = []
143        if suite_name in constants.SUITE_PLANS:
144            test_infos.append(test_info.TestInfo(
145                test_name=suite_name,
146                test_runner=self._SUITE_PLAN_TEST_RUNNER,
147                build_targets=set([suite_name]),
148                suite=suite_name))
149        else:
150            test_files = test_finder_utils.search_integration_dirs(
151                suite_name, self.suite_plan_dirs)
152            if not test_files:
153                return None
154            for test_file in test_files:
155                _test_info = self._get_test_info_from_path(test_file, suite_name)
156                if _test_info:
157                    test_infos.append(_test_info)
158        return test_infos
159