1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17from builtins import str 18 19import copy 20import signal 21import sys 22import traceback 23 24from vts.runners.host import keys 25from vts.runners.host import errors 26from vts.runners.host import signals 27from vts.runners.host import utils 28 29_DEFAULT_CONFIG_TEMPLATE = { 30 "test_bed": { 31 "AndroidDevice": "*", 32 }, 33 "log_path": "/tmp/logs", 34 "test_paths": ["./"], 35 "enable_web": False, 36} 37 38 39def GetDefaultConfig(test_name): 40 """Returns a default config data structure (when no config file is given).""" 41 result = copy.deepcopy(_DEFAULT_CONFIG_TEMPLATE) 42 result[keys.ConfigKeys.KEY_TESTBED][ 43 keys.ConfigKeys.KEY_TESTBED_NAME] = test_name 44 return result 45 46 47def gen_term_signal_handler(test_runners): 48 """Generates a termination signal handler function. 49 50 Args: 51 test_runners: A list of TestRunner objects. 52 53 Returns: 54 A function to be called when termination signals are received from 55 command line. This function stops all TestRunner objects. 56 """ 57 58 def termination_sig_handler(signal_num, frame): 59 for t in test_runners: 60 t.stop() 61 sys.exit(1) 62 63 return termination_sig_handler 64 65 66def load_test_config_file(test_config_path, 67 tb_filters=None, 68 baseline_config=None): 69 """Processes the test configuration file provided by user. 70 71 Loads the configuration file into a json object, unpacks each testbed 72 config into its own json object, and validate the configuration in the 73 process. 74 75 Args: 76 test_config_path: Path to the test configuration file. 77 tb_filters: A list of strings, each is a test bed name. If None, all 78 test beds are picked up. Otherwise only test bed names 79 specified will be picked up. 80 baseline_config: dict, the baseline config to use (used iff 81 test_config_path does not have device info). 82 83 Returns: 84 A list of test configuration json objects to be passed to TestRunner. 85 """ 86 try: 87 configs = utils.load_config(test_config_path) 88 if keys.ConfigKeys.KEY_TESTBED not in configs and baseline_config: 89 configs.update(baseline_config) 90 91 if tb_filters: 92 tbs = [] 93 for tb in configs[keys.ConfigKeys.KEY_TESTBED]: 94 if tb[keys.ConfigKeys.KEY_TESTBED_NAME] in tb_filters: 95 tbs.append(tb) 96 if len(tbs) != len(tb_filters): 97 print("Expect to find %d test bed configs, found %d." % 98 (len(tb_filters), len(tbs))) 99 print("Check if you have the correct test bed names.") 100 return None 101 configs[keys.ConfigKeys.KEY_TESTBED] = tbs 102 _validate_test_config(configs) 103 _validate_testbed_configs(configs[keys.ConfigKeys.KEY_TESTBED]) 104 k_log_path = keys.ConfigKeys.KEY_LOG_PATH 105 configs[k_log_path] = utils.abs_path(configs[k_log_path]) 106 tps = configs[keys.ConfigKeys.KEY_TEST_PATHS] 107 except errors.USERError as e: 108 print("Something is wrong in the test configurations.") 109 print(str(e)) 110 return None 111 except Exception as e: 112 print("Error loading test config {}".format(test_config_path)) 113 print(traceback.format_exc()) 114 return None 115 # Unpack testbeds into separate json objects. 116 beds = configs.pop(keys.ConfigKeys.KEY_TESTBED) 117 config_jsons = [] 118 for original_bed_config in beds: 119 new_test_config = dict(configs) 120 new_test_config[keys.ConfigKeys.KEY_TESTBED] = original_bed_config 121 # Keys in each test bed config will be copied to a level up to be 122 # picked up for user_params. If the key already exists in the upper 123 # level, the local one defined in test bed config overwrites the 124 # general one. 125 new_test_config.update(original_bed_config) 126 config_jsons.append(new_test_config) 127 return config_jsons 128 129 130def parse_test_list(test_list): 131 """Parse user provided test list into internal format for test_runner. 132 133 Args: 134 test_list: A list of test classes/cases. 135 136 Returns: 137 A list of tuples, each has a test class name and a list of test case 138 names. 139 """ 140 result = [] 141 for elem in test_list: 142 result.append(_parse_one_test_specifier(elem)) 143 return result 144 145 146def _validate_test_config(test_config): 147 """Validates the raw configuration loaded from the config file. 148 149 Making sure all the required keys exist. 150 151 Args: 152 test_config: A dict that is the config to validate. 153 154 Raises: 155 errors.USERError is raised if any required key is missing from the 156 config. 157 """ 158 for k in keys.ConfigKeys.RESERVED_KEYS: 159 if k not in test_config: 160 raise errors.USERError(("Required key {} missing in test " 161 "config.").format(k)) 162 163 164def _parse_one_test_specifier(item): 165 """Parse one test specifier from command line input. 166 167 This also verifies that the test class name and test case names follow 168 ACTS's naming conventions. A test class name has to end with "Test"; a test 169 case name has to start with "test". 170 171 Args: 172 item: A string that specifies a test class or test cases in one test 173 class to run. 174 175 Returns: 176 A tuple of a string and a list of strings. The string is the test class 177 name, the list of strings is a list of test case names. The list can be 178 None. 179 """ 180 tokens = item.split(':') 181 if len(tokens) > 2: 182 raise errors.USERError("Syntax error in test specifier %s" % item) 183 if len(tokens) == 1: 184 # This should be considered a test class name 185 test_cls_name = tokens[0] 186 _validate_test_class_name(test_cls_name) 187 return (test_cls_name, None) 188 elif len(tokens) == 2: 189 # This should be considered a test class name followed by 190 # a list of test case names. 191 test_cls_name, test_case_names = tokens 192 clean_names = [] 193 _validate_test_class_name(test_cls_name) 194 for elem in test_case_names.split(','): 195 test_case_name = elem.strip() 196 if not test_case_name.startswith("test_"): 197 raise errors.USERError( 198 ("Requested test case '%s' in test class " 199 "'%s' does not follow the test case " 200 "naming convention test_*.") % (test_case_name, 201 test_cls_name)) 202 clean_names.append(test_case_name) 203 return (test_cls_name, clean_names) 204 205 206def _parse_test_file(fpath): 207 """Parses a test file that contains test specifiers. 208 209 Args: 210 fpath: A string that is the path to the test file to parse. 211 212 Returns: 213 A list of strings, each is a test specifier. 214 """ 215 try: 216 with open(fpath, 'r') as f: 217 tf = [] 218 for line in f: 219 line = line.strip() 220 if not line: 221 continue 222 if len(tf) and (tf[-1].endswith(':') or tf[-1].endswith(',')): 223 tf[-1] += line 224 else: 225 tf.append(line) 226 return tf 227 except: 228 print("Error loading test file.") 229 raise 230 231 232def _validate_test_class_name(test_cls_name): 233 """Checks if a string follows the test class name convention. 234 235 Args: 236 test_cls_name: A string that should be a test class name. 237 238 Raises: 239 errors.USERError is raised if the input does not follow test class 240 naming convention. 241 """ 242 if not test_cls_name.endswith("Test"): 243 raise errors.USERError( 244 ("Requested test class '%s' does not follow the test " 245 "class naming convention *Test.") % test_cls_name) 246 247 248def _validate_testbed_configs(testbed_configs): 249 """Validates the testbed configurations. 250 251 Args: 252 testbed_configs: A list of testbed configuration json objects. 253 254 Raises: 255 If any part of the configuration is invalid, errors.USERError is raised. 256 """ 257 seen_names = set() 258 # Cross checks testbed configs for resource conflicts. 259 for config in testbed_configs: 260 # Check for conflicts between multiple concurrent testbed configs. 261 # No need to call it if there's only one testbed config. 262 name = config[keys.ConfigKeys.KEY_TESTBED_NAME] 263 _validate_testbed_name(name) 264 # Test bed names should be unique. 265 if name in seen_names: 266 raise errors.USERError("Duplicate testbed name {} found.".format( 267 name)) 268 seen_names.add(name) 269 270 271def _validate_testbed_name(name): 272 """Validates the name of a test bed. 273 274 Since test bed names are used as part of the test run id, it needs to meet 275 certain requirements. 276 277 Args: 278 name: The test bed's name specified in config file. 279 280 Raises: 281 If the name does not meet any criteria, errors.USERError is raised. 282 """ 283 if not name: 284 raise errors.USERError("Test bed names can't be empty.") 285 if not isinstance(name, str) and not isinstance(name, basestring): 286 raise errors.USERError("Test bed names have to be string. Found: %s" % 287 type(name)) 288 for l in name: 289 if l not in utils.valid_filename_chars: 290 raise errors.USERError( 291 "Char '%s' is not allowed in test bed names." % l) 292