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