1# Copyright 2019 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6This class provides wrapper functions for Bluetooth quick sanity test 7batches or packages 8""" 9 10import functools 11import logging 12import time 13 14from autotest_lib.client.common_lib import error 15from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests 16from autotest_lib.server.cros.multimedia import remote_facade_factory 17 18 19class BluetoothAdapterQuickTests(bluetooth_adapter_tests.BluetoothAdapterTests): 20 """This class provide wrapper function for Bluetooth quick sanity test 21 batches or packages. 22 The Bluetooth quick test infrastructure provides a way to quickly run a set 23 of tests. As for today, auto-test ramp up time per test is about 90-120 24 seconds, where a typical Bluetooth test may take ~30-60 seconds to run. 25 26 The quick test infra, implemented in this class, saves this huge overhead 27 by running only the minimal reset and cleanup operations required between 28 each set of tests (takes a few seconds). 29 30 This class provides wrapper functions to start and end a test, a batch or a 31 package. A batch is defined as a set of tests, preferably with a common 32 subject. A package is a set of batches. 33 This class takes care of tests, batches, and packages test results, and 34 prints out summaries to results. The class also resets and cleans up 35 required states between tests, batches and packages. 36 37 A batch can also run as a separate auto-test. There is a place holder to 38 add a way to run a specific test of a batch autonomously. 39 40 A batch can be implemented by inheriting from this class, and using its 41 wrapper functions. A package can be implemented by inheriting from a set of 42 batches. 43 44 Adding a test to one of the batches is as easy as adding a method to the 45 class of the batch. 46 """ 47 48 # Some delay is needed between tests. TODO(yshavit): investigate and remove 49 TEST_SLEEP_SECS = 3 50 51 52 def restart_peers(self): 53 """Restart and clear peer devices""" 54 # Restart the link to device 55 logging.info('Restarting peer devices...') 56 57 # Grab current device list for initialization 58 connected_devices = self.devices 59 self.cleanup(test_state='MID') 60 61 for device_type, device_list in connected_devices.items(): 62 for device in device_list: 63 if device is not None: 64 logging.info('Restarting %s', device_type) 65 self.get_device(device_type, on_start=False) 66 67 68 def start_peers(self, devices): 69 """Start peer devices""" 70 # Start the link to devices 71 logging.info('Starting peer devices...') 72 73 if self.host.multi_chameleon: 74 self.get_device_rasp(devices) 75 else: 76 for device_type in devices: 77 logging.info('Getting device %s', device_type) 78 self.get_device(device_type) 79 80 81 def _print_delimiter(self): 82 logging.info('=======================================================') 83 84 85 def quick_test_init(self, host, use_chameleon=True, flag='Quick Sanity'): 86 """Inits the test batch""" 87 self.host = host 88 #factory can not be declared as local variable, otherwise 89 #factory._proxy.__del__ will be invoked, which shutdown the xmlrpc 90 # server, which log out the user. 91 92 try: 93 self.factory = remote_facade_factory.RemoteFacadeFactory(host, 94 disable_arc=True) 95 self.bluetooth_facade = self.factory.create_bluetooth_hid_facade() 96 97 # For b:142276989, catch 'object_path' fault and reboot to prevent 98 # failures from continuing into future tests 99 except Exception, e: 100 if (e.__class__.__name__ == 'Fault' and 101 """object has no attribute 'object_path'""" in str(e)): 102 103 logging.error('Caught b/142276989, rebooting DUT') 104 self.reboot() 105 # Raise the original exception 106 raise 107 108 self.use_chameleon = use_chameleon 109 if self.use_chameleon: 110 self.input_facade = self.factory.create_input_facade() 111 self.check_chameleon() 112 113 # Query connected devices on our chameleon at init time 114 self.available_devices = self.list_devices_available() 115 116 for chameleon in self.host.chameleon_list: 117 chameleon.register_raspPi_log(self.outputdir) 118 119 if self.host.multi_chameleon: 120 self.chameleon_group = dict() 121 # Create copy of chameleon_group 122 self.chameleon_group_copy = dict() 123 self.group_chameleons_type() 124 125 # Clear the active devices for this test 126 self.active_test_devices = {} 127 128 self.enable_disable_debug_log(enable=True) 129 130 # Delete files created in previous run 131 self.host.run('[ ! -d {0} ] || rm -rf {0} || true'.format( 132 self.BTMON_DIR_LOG_PATH)) 133 self.start_new_btmon() 134 135 self.flag = flag 136 self.test_iter = None 137 138 self.bat_tests_results = [] 139 self.bat_pass_count = 0 140 self.bat_fail_count = 0 141 self.bat_name = None 142 self.bat_iter = None 143 144 self.pkg_tests_results = [] 145 self.pkg_pass_count = 0 146 self.pkg_fail_count = 0 147 self.pkg_name = None 148 self.pkg_iter = None 149 self.pkg_is_running = False 150 151 152 @staticmethod 153 def quick_test_test_decorator(test_name, devices={}, flags=['All']): 154 """A decorator providing a wrapper to a quick test. 155 Using the decorator a test method can implement only the core 156 test and let the decorator handle the quick test wrapper methods 157 (test_start and test_end). 158 159 @param test_name: the name of the test to log. 160 @param devices: list of device names which are going to be used 161 in the following test. 162 @param flags: list of string to describe who should run the 163 test. The string could be one of the following: 164 ['AVL', 'Quick Sanity', 'All']. 165 """ 166 167 def decorator(test_method): 168 """A decorator wrapper of the decorated test_method. 169 @param test_method: the test method being decorated. 170 @returns the wrapper of the test method. 171 """ 172 173 def _check_runnable(self): 174 """Check if the test could be run""" 175 176 # Check that the test is runnable in current setting 177 if not(self.flag in flags or 'All' in flags): 178 logging.info('SKIPPING TEST %s', test_name) 179 logging.info('flag %s not in %s', self.flag, flags) 180 self._print_delimiter() 181 return False 182 183 # Check that chameleon has all required devices before running 184 for device_type, number in devices.items(): 185 if self.available_devices.get(device_type, 0) < number: 186 logging.info('SKIPPING TEST %s', test_name) 187 logging.info('%s not available', device_type) 188 self._print_delimiter() 189 return False 190 191 return True 192 193 @functools.wraps(test_method) 194 def wrapper(self): 195 """A wrapper of the decorated method.""" 196 if not _check_runnable(self): 197 return 198 self.quick_test_test_start(test_name, devices) 199 test_method(self) 200 self.quick_test_test_end() 201 return wrapper 202 203 return decorator 204 205 206 def quick_test_test_start(self, test_name=None, devices={}): 207 """Start a quick test. The method clears and restarts adapter on DUT 208 as well as peer devices. In addition the methods prints test start 209 traces. 210 """ 211 212 self.test_name = test_name 213 214 # Reset the adapter 215 self.test_reset_on_adapter() 216 # Initialize bluetooth_adapter_tests class (also clears self.fails) 217 self.initialize() 218 # Start and peer HID devices 219 self.start_peers(devices) 220 221 if test_name is not None: 222 time.sleep(self.TEST_SLEEP_SECS) 223 self._print_delimiter() 224 logging.info('Starting test: %s', test_name) 225 self.log_message('Starting test: %s'% test_name) 226 227 def quick_test_test_end(self): 228 """Log and track the test results""" 229 result_msgs = [] 230 231 if self.test_iter is not None: 232 result_msgs += ['Test Iter: ' + str(self.test_iter)] 233 234 if self.bat_iter is not None: 235 result_msgs += ['Batch Iter: ' + str(self.bat_iter)] 236 237 if self.pkg_is_running is True: 238 result_msgs += ['Package iter: ' + str(self.pkg_iter)] 239 240 if self.bat_name is not None: 241 result_msgs += ['Batch Name: ' + self.bat_name] 242 243 if self.test_name is not None: 244 result_msgs += ['Test Name: ' + self.test_name] 245 246 result_msg = ", ".join(result_msgs) 247 248 if not bool(self.fails): 249 result_msg = 'PASSED | ' + result_msg 250 self.bat_pass_count += 1 251 self.pkg_pass_count += 1 252 else: 253 result_msg = 'FAIL | ' + result_msg 254 self.bat_fail_count += 1 255 self.pkg_fail_count += 1 256 257 logging.info(result_msg) 258 self.log_message(result_msg) 259 self._print_delimiter() 260 self.bat_tests_results.append(result_msg) 261 self.pkg_tests_results.append(result_msg) 262 263 if self.test_name is not None: 264 logging.info('Cleanning up and restarting towards next test...') 265 266 self.bluetooth_facade.stop_discovery() 267 268 # Store a copy of active devices for raspi reset in the final step 269 self.active_test_devices = self.devices 270 271 # Disconnect devices used in the test, and remove the pairing. 272 for device_list in self.devices.values(): 273 for device in device_list: 274 if device is not None: 275 logging.info('Clear device %s', device.name) 276 self.bluetooth_facade.disconnect_device(device.address) 277 device_is_paired = self.bluetooth_facade.device_is_paired( 278 device.address) 279 if device_is_paired: 280 self.bluetooth_facade.remove_device_object( 281 device.address) 282 283 # Repopulate chameleon_group for next tests 284 if self.host.multi_chameleon: 285 # Clear previous tets's leftover entries. Don't delete the 286 # chameleon_group dictionary though, it'll be used as it is. 287 for device_type in self.chameleon_group: 288 if len(self.chameleon_group[device_type]) > 0: 289 del self.chameleon_group[device_type][:] 290 291 # Repopulate 292 self.group_chameleons_type() 293 294 # Close the connection between peers 295 self.cleanup(test_state='NEW') 296 297 298 @staticmethod 299 def quick_test_batch_decorator(batch_name): 300 """A decorator providing a wrapper to a batch. 301 Using the decorator a test batch method can implement only its 302 core tests invocations and let the decorator handle the wrapper, 303 which is taking care for whether to run a specific test or the 304 batch as a whole and and running the batch in iterations 305 306 @param batch_name: the name of the batch to log 307 """ 308 309 def decorator(batch_method): 310 """A decorator wrapper of the decorated test_method. 311 @param test_method: the test method being decorated. 312 @returns the wrapper of the test method. 313 """ 314 315 @functools.wraps(batch_method) 316 def wrapper(self, num_iterations=1, test_name=None): 317 """A wrapper of the decorated method. 318 @param num_iterations: how many interations to run 319 @param test_name: specifc test to run otherwise None to run 320 the whole batch 321 """ 322 if test_name is not None: 323 single_test_method = getattr(self, test_name) 324 for iter in xrange(1,num_iterations+1): 325 self.test_iter = iter 326 single_test_method() 327 328 if self.fails: 329 raise error.TestFail(self.fails) 330 else: 331 for iter in xrange(1,num_iterations+1): 332 self.quick_test_batch_start(batch_name, iter) 333 batch_method(self, num_iterations, test_name) 334 self.quick_test_batch_end() 335 return wrapper 336 337 return decorator 338 339 340 def quick_test_batch_start(self, bat_name, iteration=1): 341 """Start a test batch. The method clears and set batch variables""" 342 self.bat_tests_results = [] 343 self.bat_pass_count = 0 344 self.bat_fail_count = 0 345 self.bat_name = bat_name 346 self.bat_iter = iteration 347 348 349 def quick_test_batch_end(self): 350 """Print results summary of a test batch""" 351 logging.info('%s Test Batch Summary: total pass %d, total fail %d', 352 self.bat_name, self.bat_pass_count, self.bat_fail_count) 353 for result in self.bat_tests_results: 354 logging.info(result) 355 self._print_delimiter(); 356 if self.bat_fail_count > 0: 357 logging.error('===> Test Batch Failed! More than one failure') 358 self._print_delimiter(); 359 if self.pkg_is_running is False: 360 raise error.TestFail(self.bat_tests_results) 361 else: 362 logging.info('===> Test Batch Passed! zero failures') 363 self._print_delimiter(); 364 365 366 def quick_test_package_start(self, pkg_name): 367 """Start a test package. The method clears and set batch variables""" 368 self.pkg_tests_results = [] 369 self.pkg_pass_count = 0 370 self.pkg_fail_count = 0 371 self.pkg_name = pkg_name 372 self.pkg_is_running = True 373 374 375 def quick_test_print_summary(self): 376 """Print results summary of a test package""" 377 logging.info('%s Test Package Summary: total pass %d, total fail %d', 378 self.pkg_name, self.pkg_pass_count, self.pkg_fail_count) 379 for result in self.pkg_tests_results: 380 logging.info(result) 381 self._print_delimiter(); 382 383 384 def quick_test_package_update_iteration(self, iteration): 385 """Update state and print log per package iteration. 386 Must be called to have a proper package test result tracking. 387 """ 388 self.pkg_iter = iteration 389 if self.pkg_name is None: 390 logging.error('Error: no quick package is running') 391 raise error.TestFail('Error: no quick package is running') 392 logging.info('Starting %s Test Package iteration %d', 393 self.pkg_name, iteration) 394 395 396 def quick_test_package_end(self): 397 """Print final result of a test package""" 398 if self.pkg_fail_count > 0: 399 logging.error('===> Test Package Failed! More than one failure') 400 self._print_delimiter(); 401 raise error.TestFail(self.bat_tests_results) 402 else: 403 logging.info('===> Test Package Passed! zero failures') 404 self._print_delimiter(); 405 self.pkg_is_running = False 406 407 408 def quick_test_cleanup(self): 409 """ Cleanup any state test server and all device""" 410 411 # Clear any raspi devices at very end of test 412 for device_list in self.active_test_devices.values(): 413 for device in device_list: 414 if device is not None: 415 self.clear_raspi_device(device) 416 417 # Reset the adapter 418 self.test_reset_on_adapter() 419 # Initialize bluetooth_adapter_tests class (also clears self.fails) 420 self.initialize() 421