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 load_test_config_file(test_config_path, 48 tb_filters=None, 49 baseline_config=None): 50 """Processes the test configuration file provided by user. 51 52 Loads the configuration file into a json object, unpacks each testbed 53 config into its own json object, and validate the configuration in the 54 process. 55 56 Args: 57 test_config_path: Path to the test configuration file. 58 tb_filters: A list of strings, each is a test bed name. If None, all 59 test beds are picked up. Otherwise only test bed names 60 specified will be picked up. 61 baseline_config: dict, the baseline config to use (used iff 62 test_config_path does not have device info). 63 64 Returns: 65 A list of test configuration json objects to be passed to TestRunner. 66 """ 67 try: 68 configs = utils.load_config(test_config_path) 69 if keys.ConfigKeys.KEY_TESTBED not in configs and baseline_config: 70 configs.update(baseline_config) 71 72 if tb_filters: 73 tbs = [] 74 for tb in configs[keys.ConfigKeys.KEY_TESTBED]: 75 if tb[keys.ConfigKeys.KEY_TESTBED_NAME] in tb_filters: 76 tbs.append(tb) 77 if len(tbs) != len(tb_filters): 78 print("Expect to find %d test bed configs, found %d." % 79 (len(tb_filters), len(tbs))) 80 print("Check if you have the correct test bed names.") 81 return None 82 configs[keys.ConfigKeys.KEY_TESTBED] = tbs 83 _validate_test_config(configs) 84 _validate_testbed_configs(configs[keys.ConfigKeys.KEY_TESTBED]) 85 k_log_path = keys.ConfigKeys.KEY_LOG_PATH 86 configs[k_log_path] = utils.abs_path(configs[k_log_path]) 87 tps = configs[keys.ConfigKeys.KEY_TEST_PATHS] 88 except errors.USERError as e: 89 print("Something is wrong in the test configurations.") 90 print(str(e)) 91 return None 92 except Exception as e: 93 print("Error loading test config {}".format(test_config_path)) 94 print(traceback.format_exc()) 95 return None 96 # Unpack testbeds into separate json objects. 97 beds = configs.pop(keys.ConfigKeys.KEY_TESTBED) 98 config_jsons = [] 99 for original_bed_config in beds: 100 new_test_config = dict(configs) 101 new_test_config[keys.ConfigKeys.KEY_TESTBED] = original_bed_config 102 # Keys in each test bed config will be copied to a level up to be 103 # picked up for user_params. If the key already exists in the upper 104 # level, the local one defined in test bed config overwrites the 105 # general one. 106 new_test_config.update(original_bed_config) 107 config_jsons.append(new_test_config) 108 return config_jsons 109 110 111def parse_test_list(test_list): 112 """Parse user provided test list into internal format for test_runner. 113 114 Args: 115 test_list: A list of test classes/cases. 116 117 Returns: 118 A list of tuples, each has a test class name and a list of test case 119 names. 120 """ 121 result = [] 122 for elem in test_list: 123 result.append(_parse_one_test_specifier(elem)) 124 return result 125 126 127def _validate_test_config(test_config): 128 """Validates the raw configuration loaded from the config file. 129 130 Making sure all the required keys exist. 131 132 Args: 133 test_config: A dict that is the config to validate. 134 135 Raises: 136 errors.USERError is raised if any required key is missing from the 137 config. 138 """ 139 for k in keys.ConfigKeys.RESERVED_KEYS: 140 if k not in test_config: 141 raise errors.USERError(("Required key {} missing in test " 142 "config.").format(k)) 143 144 145def _parse_one_test_specifier(item): 146 """Parse one test specifier from command line input. 147 148 This also verifies that the test class name and test case names follow 149 ACTS's naming conventions. A test class name has to end with "Test"; a test 150 case name has to start with "test". 151 152 Args: 153 item: A string that specifies a test class or test cases in one test 154 class to run. 155 156 Returns: 157 A tuple of a string and a list of strings. The string is the test class 158 name, the list of strings is a list of test case names. The list can be 159 None. 160 """ 161 tokens = item.split(':') 162 if len(tokens) > 2: 163 raise errors.USERError("Syntax error in test specifier %s" % item) 164 if len(tokens) == 1: 165 # This should be considered a test class name 166 test_cls_name = tokens[0] 167 _validate_test_class_name(test_cls_name) 168 return (test_cls_name, None) 169 elif len(tokens) == 2: 170 # This should be considered a test class name followed by 171 # a list of test case names. 172 test_cls_name, test_case_names = tokens 173 clean_names = [] 174 _validate_test_class_name(test_cls_name) 175 for elem in test_case_names.split(','): 176 test_case_name = elem.strip() 177 if not test_case_name.startswith("test_"): 178 raise errors.USERError( 179 ("Requested test case '%s' in test class " 180 "'%s' does not follow the test case " 181 "naming convention test_*.") % (test_case_name, 182 test_cls_name)) 183 clean_names.append(test_case_name) 184 return (test_cls_name, clean_names) 185 186 187def _parse_test_file(fpath): 188 """Parses a test file that contains test specifiers. 189 190 Args: 191 fpath: A string that is the path to the test file to parse. 192 193 Returns: 194 A list of strings, each is a test specifier. 195 """ 196 try: 197 with open(fpath, 'r') as f: 198 tf = [] 199 for line in f: 200 line = line.strip() 201 if not line: 202 continue 203 if len(tf) and (tf[-1].endswith(':') or tf[-1].endswith(',')): 204 tf[-1] += line 205 else: 206 tf.append(line) 207 return tf 208 except: 209 print("Error loading test file.") 210 raise 211 212 213def _validate_test_class_name(test_cls_name): 214 """Checks if a string follows the test class name convention. 215 216 Args: 217 test_cls_name: A string that should be a test class name. 218 219 Raises: 220 errors.USERError is raised if the input does not follow test class 221 naming convention. 222 """ 223 if not test_cls_name.endswith("Test"): 224 raise errors.USERError( 225 ("Requested test class '%s' does not follow the test " 226 "class naming convention *Test.") % test_cls_name) 227 228 229def _validate_testbed_configs(testbed_configs): 230 """Validates the testbed configurations. 231 232 Args: 233 testbed_configs: A list of testbed configuration json objects. 234 235 Raises: 236 If any part of the configuration is invalid, errors.USERError is raised. 237 """ 238 seen_names = set() 239 # Cross checks testbed configs for resource conflicts. 240 for config in testbed_configs: 241 # Check for conflicts between multiple concurrent testbed configs. 242 # No need to call it if there's only one testbed config. 243 name = config[keys.ConfigKeys.KEY_TESTBED_NAME] 244 _validate_testbed_name(name) 245 # Test bed names should be unique. 246 if name in seen_names: 247 raise errors.USERError("Duplicate testbed name {} found.".format( 248 name)) 249 seen_names.add(name) 250 251 252def _validate_testbed_name(name): 253 """Validates the name of a test bed. 254 255 Since test bed names are used as part of the test run id, it needs to meet 256 certain requirements. 257 258 Args: 259 name: The test bed's name specified in config file. 260 261 Raises: 262 If the name does not meet any criteria, errors.USERError is raised. 263 """ 264 if not name: 265 raise errors.USERError("Test bed names can't be empty.") 266 if not isinstance(name, str) and not isinstance(name, basestring): 267 raise errors.USERError("Test bed names have to be string. Found: %s" % 268 type(name)) 269 for l in name: 270 if l not in utils.valid_filename_chars: 271 raise errors.USERError( 272 "Char '%s' is not allowed in test bed names." % l) 273