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 20from typing import Set 21 22from atest import constants 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 # TODO: remove all arguments but only test_name, test_runner, build_targets, 32 # data and compatibility_suites. 33 def __init__(self, test_name, test_runner, build_targets, data=None, 34 suite=None, module_class=None, install_locations=None, 35 test_finder='', compatibility_suites=None): 36 """Init for TestInfo. 37 38 Args: 39 test_name: String of test name. 40 test_runner: String of test runner. 41 build_targets: Set of build targets. 42 data: Dict of data for test runners to use. 43 suite: Suite for test runners to use. 44 module_class: A list of test classes. It's a snippet of class 45 in module_info. e.g. ["EXECUTABLES", "NATIVE_TESTS"] 46 install_locations: Set of install locations. 47 e.g. set(['host', 'device']) 48 test_finder: String of test finder. 49 compatibility_suites: A list of compatibility_suites. It's a 50 snippet of compatibility_suites in module_info. e.g. 51 ["device-tests", "vts10"] 52 """ 53 self.test_name = test_name 54 self.raw_test_name = test_name 55 self.test_runner = test_runner 56 self.data = data if data else {} 57 self.suite = suite 58 self.module_class = module_class if module_class else [] 59 # robolectric test types: 60 # 0: Not robolectric test 61 # 1. Modern robolectric test(Tradefed Runner) 62 # 2: Legacy robolectric test(Robolectric Runner) 63 self.robo_type = 0 64 self.install_locations = (install_locations if install_locations 65 else set()) 66 # True if the TestInfo is built from a test configured in TEST_MAPPING. 67 self.from_test_mapping = False 68 # True if the test should run on host and require no device. The 69 # attribute is only set through TEST_MAPPING file. 70 self.host = False 71 self.test_finder = test_finder 72 self.compatibility_suites = (compatibility_suites 73 if compatibility_suites else []) 74 # True if test need to generate aggregate metrics result. 75 self.aggregate_metrics_result = False 76 self.artifacts = set() 77 78 self._build_targets = set(build_targets) if build_targets else set() 79 self._mainline_modules = set() 80 81 def __str__(self): 82 host_info = (' - runs on host without device required.' if self.host 83 else '') 84 return (f'test_name:{self.test_name} - ' 85 f'raw_test_name:{self.raw_test_name} - ' 86 f'test_runner:{self.test_runner} - ' 87 f'build_targets:{self._build_targets} - data:{self.data} - ' 88 f'suite:{self.suite} - module_class:{self.module_class} - ' 89 f'install_locations:{self.install_locations}{host_info} - ' 90 f'test_finder:{self.test_finder} - ' 91 f'compatibility_suites:{self.compatibility_suites} - ' 92 f'mainline_modules:{self._mainline_modules} - ' 93 f'aggregate_metrics_result:{self.aggregate_metrics_result} - ' 94 f'robo_type:{self.robo_type} - ' 95 f'artifacts:{self.artifacts}') 96 97 @property 98 def build_targets(self) -> Set[str]: 99 """Gets all build targets of the test. 100 101 Gets all build targets of the test including mainline 102 modules build targets if it's a mainline test. 103 """ 104 return frozenset(self._build_targets) 105 106 def add_build_target(self, target: str): 107 """Sets build targets. 108 109 Args: 110 target: a string of build target name. 111 """ 112 self._build_targets.add(target) 113 114 @property 115 def mainline_modules(self) -> Set[str]: 116 """Gets mainline module build targets.""" 117 return frozenset(self._mainline_modules) 118 119 def add_mainline_module(self, module: str): 120 """Sets mainline modules. 121 122 Args: 123 module: the build module name of a mainline module. 124 """ 125 self._build_targets.add(module) 126 self._mainline_modules.add(module) 127 128 def get_supported_exec_mode(self): 129 """Get the supported execution mode of the test. 130 131 Determine which execution mode does the test support by strategy: 132 Modern Robolectric --> 'host' 133 Legacy Robolectric --> 'both' 134 JAVA_LIBRARIES --> 'both' 135 Not native tests or installed only in out/target --> 'device' 136 Installed only in out/host --> 'both' 137 Installed under host and target --> 'both' 138 139 Return: 140 String of execution mode. 141 """ 142 install_path = self.install_locations 143 if not self.module_class: 144 return constants.DEVICE_TEST 145 # Let Robolectric test support host/both accordingly. 146 if self.robo_type == constants.ROBOTYPE_MODERN: 147 return constants.DEVICELESS_TEST 148 if self.robo_type == constants.ROBOTYPE_LEGACY: 149 return constants.BOTH_TEST 150 if constants.MODULE_CLASS_JAVA_LIBRARIES in self.module_class: 151 return constants.BOTH_TEST 152 if not install_path: 153 return constants.DEVICE_TEST 154 # Non-Native test runs on device-only. 155 if constants.MODULE_CLASS_NATIVE_TESTS not in self.module_class: 156 return constants.DEVICE_TEST 157 # Native test with install path as host should be treated as both. 158 # Otherwise, return device test. 159 if install_path == {constants.DEVICE_TEST}: 160 return constants.DEVICE_TEST 161 return constants.BOTH_TEST 162 163 def get_test_paths(self): 164 """Get the relative path of test_info. 165 166 Search build target's MODULE-IN as the test path. 167 168 Return: 169 A list of string of the relative path for test(build target 170 formats, e.g., platform_testing-tests-example-native), 171 None if test path information not found. 172 """ 173 test_paths = [] 174 for build_target in self.build_targets: 175 if str(build_target).startswith(constants.MODULES_IN): 176 test_paths.append( 177 str(build_target).replace( 178 constants.MODULES_IN, '')) 179 return test_paths if test_paths else None 180 181class TestFilter(TestFilterBase): 182 """Information needed to filter a test in Tradefed""" 183 184 def to_set_of_tf_strings(self): 185 """Return TestFilter as set of strings in TradeFed filter format.""" 186 if self.methods: 187 return {'%s#%s' % (self.class_name, m) for m in self.methods} 188 return {self.class_name} 189