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"""
16TestInfo class.
17"""
18
19from collections import namedtuple
20
21import constants
22
23
24TestFilterBase = namedtuple('TestFilter', ['class_name', 'methods'])
25
26
27class TestInfo:
28    """Information needed to identify and run a test."""
29
30    # pylint: disable=too-many-arguments
31    def __init__(self, test_name, test_runner, build_targets, data=None,
32                 suite=None, module_class=None, install_locations=None,
33                 test_finder='', compatibility_suites=None,
34                 mainline_modules=None):
35        """Init for TestInfo.
36
37        Args:
38            test_name: String of test name.
39            test_runner: String of test runner.
40            build_targets: Set of build targets.
41            data: Dict of data for test runners to use.
42            suite: Suite for test runners to use.
43            module_class: A list of test classes. It's a snippet of class
44                        in module_info. e.g. ["EXECUTABLES",  "NATIVE_TESTS"]
45            install_locations: Set of install locations.
46                        e.g. set(['host', 'device'])
47            test_finder: String of test finder.
48            compatibility_suites: A list of compatibility_suites. It's a
49                        snippet of compatibility_suites in module_info. e.g.
50                        ["device-tests",  "vts10"]
51            mainline_modules: A list of mainline modules.
52                    e.g. ['some1.apk', 'some2.apex', 'some3.apks',
53                          'some1.apk+some2.apex']
54        """
55        self.test_name = test_name
56        self.test_runner = test_runner
57        self.build_targets = build_targets
58        self.data = data if data else {}
59        self.suite = suite
60        self.module_class = module_class if module_class else []
61        self.install_locations = (install_locations if install_locations
62                                  else set())
63        # True if the TestInfo is built from a test configured in TEST_MAPPING.
64        self.from_test_mapping = False
65        # True if the test should run on host and require no device. The
66        # attribute is only set through TEST_MAPPING file.
67        self.host = False
68        self.test_finder = test_finder
69        self.compatibility_suites = (compatibility_suites
70                                     if compatibility_suites else [])
71        self.mainline_modules = mainline_modules if mainline_modules else []
72
73    def __str__(self):
74        host_info = (' - runs on host without device required.' if self.host
75                     else '')
76        return ('test_name: %s - test_runner:%s - build_targets:%s - data:%s - '
77                'suite:%s - module_class: %s - install_locations:%s%s - '
78                'test_finder: %s - compatibility_suites:%s -'
79                'mainline_modules:%s' % (
80                    self.test_name, self.test_runner, self.build_targets,
81                    self.data, self.suite, self.module_class,
82                    self.install_locations, host_info, self.test_finder,
83                    self.compatibility_suites, self.mainline_modules))
84
85    def get_supported_exec_mode(self):
86        """Get the supported execution mode of the test.
87
88        Determine the test supports which execution mode by strategy:
89        Robolectric/JAVA_LIBRARIES --> 'both'
90        Not native tests or installed only in out/target --> 'device'
91        Installed only in out/host --> 'both'
92        Installed under host and target --> 'both'
93
94        Return:
95            String of execution mode.
96        """
97        install_path = self.install_locations
98        if not self.module_class:
99            return constants.DEVICE_TEST
100        # Let Robolectric test support both.
101        if constants.MODULE_CLASS_ROBOLECTRIC in self.module_class:
102            return constants.BOTH_TEST
103        # Let JAVA_LIBRARIES support both.
104        if constants.MODULE_CLASS_JAVA_LIBRARIES in self.module_class:
105            return constants.BOTH_TEST
106        if not install_path:
107            return constants.DEVICE_TEST
108        # Non-Native test runs on device-only.
109        if constants.MODULE_CLASS_NATIVE_TESTS not in self.module_class:
110            return constants.DEVICE_TEST
111        # Native test with install path as host should be treated as both.
112        # Otherwise, return device test.
113        if len(install_path) == 1 and constants.DEVICE_TEST in install_path:
114            return constants.DEVICE_TEST
115        return constants.BOTH_TEST
116
117    def get_test_paths(self):
118        """Get the relative path of test_info.
119
120        Search build target's MODULE-IN as the test path.
121
122        Return:
123            A list of string of the relative path for test, None if test
124            path information not found.
125        """
126        test_paths = []
127        for build_target in self.build_targets:
128            if str(build_target).startswith(constants.MODULES_IN):
129                test_paths.append(
130                    str(build_target).replace(
131                        constants.MODULES_IN, '').replace('-', '/'))
132        return test_paths if test_paths else None
133
134class TestFilter(TestFilterBase):
135    """Information needed to filter a test in Tradefed"""
136
137    def to_set_of_tf_strings(self):
138        """Return TestFilter as set of strings in TradeFed filter format."""
139        if self.methods:
140            return {'%s#%s' % (self.class_name, m) for m in self.methods}
141        return {self.class_name}
142