1# Lint as: python2, python3 2# Copyright 2020 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5""" Server-side bluetooth adapter tests that involve suspend/resume with peers 6 7paired and/or connected. 8 9Single btpeer tests: 10 - Reconnect on resume test 11 - Classic HID 12 - LE HID 13 - A2DP 14 - Wake from suspend test 15 - Classic HID 16 - LE HID 17 - A2DP shouldn't wake from suspend 18 - Suspend while discovering (discovering should pause and unpause) 19 - Suspend while advertising (advertising should pause and unpause) 20 21Multiple btpeer tests: 22 - Reconnect on resume test 23 - One classic HID, One LE HID 24 - Two classic HID 25 - Two LE HID 26 - Wake from suspend test 27 - Two classic HID 28 - Two classic LE 29""" 30from __future__ import absolute_import 31from __future__ import division 32from __future__ import print_function 33 34import logging 35import time 36 37from autotest_lib.client.cros.bluetooth.bluetooth_audio_test_data import A2DP 38from autotest_lib.server.cros.bluetooth.bluetooth_adapter_tests import ( 39 TABLET_MODELS, SUSPEND_POWER_DOWN_CHIPSETS) 40from autotest_lib.server.cros.bluetooth.bluetooth_adapter_audio_tests import ( 41 BluetoothAdapterAudioTests) 42from autotest_lib.server.cros.bluetooth.bluetooth_adapter_quick_tests import ( 43 BluetoothAdapterQuickTests) 44 45from six.moves import range 46 47test_wrapper = BluetoothAdapterQuickTests.quick_test_test_decorator 48batch_wrapper = BluetoothAdapterQuickTests.quick_test_batch_decorator 49 50PROFILE_CONNECT_WAIT = 15 51SUSPEND_SEC = 15 52EXPECT_NO_WAKE_SUSPEND_SEC = 30 53EXPECT_PEER_WAKE_SUSPEND_SEC = 60 54 55STRESS_ITERATIONS = 25 56 57 58class bluetooth_AdapterSRHealth(BluetoothAdapterQuickTests, 59 BluetoothAdapterAudioTests): 60 """Server side bluetooth adapter suspend resume test with peer.""" 61 62 def _test_keyboard_with_string(self, device): 63 self.test_hid_device_created(device.address) 64 return self.test_keyboard_input_from_trace(device, "simple_text") 65 66 def _test_mouse_left_click(self, device): 67 self.test_hid_device_created(device.address) 68 return self.test_mouse_left_click(device) 69 70 # --------------------------------------------------------------- 71 # Reconnect after suspend tests 72 # --------------------------------------------------------------- 73 74 def run_reconnect_device(self, 75 devtuples, 76 iterations=1, 77 auto_reconnect=False): 78 """ Reconnects a device after suspend/resume. 79 80 @param devtuples: array of tuples consisting of the following 81 * device_type: MOUSE, BLE_MOUSE, etc. 82 * device: meta object for peer device 83 * device_test: Optional; test function to run w/ 84 device (for example, mouse click) 85 @params iterations: number of suspend/resume + reconnect iterations 86 @params auto_reconnect: Expect host to automatically reconnect to peer 87 """ 88 boot_id = self.host.get_boot_id() 89 90 try: 91 # Set up the device; any failures should assert 92 for _, device, device_test in devtuples: 93 self.assert_discover_and_pair(device) 94 self.assert_on_fail( 95 self.test_device_set_discoverable(device, False)) 96 self.assert_on_fail( 97 self.test_connection_by_adapter(device.address)) 98 99 # Profile connection may not have completed yet and this will 100 # race with a subsequent disconnection (due to suspend). Use the 101 # device test to force profile connect or wait if no test was 102 # given. 103 if device_test is not None: 104 self.assert_on_fail(device_test(device)) 105 else: 106 time.sleep(PROFILE_CONNECT_WAIT) 107 108 for it in range(iterations): 109 logging.info('Running iteration {}/{} of suspend reconnection'. 110 format(it + 1, iterations)) 111 112 # Start the suspend process 113 suspend = self.suspend_async(suspend_time=SUSPEND_SEC) 114 start_time = self.bluetooth_facade.get_device_time() 115 116 # Trigger suspend, wait for regular resume, verify we can reconnect 117 # and run device specific test 118 self.test_suspend_and_wait_for_sleep(suspend, 119 sleep_timeout=SUSPEND_SEC) 120 self.test_wait_for_resume(boot_id, 121 suspend, 122 resume_timeout=SUSPEND_SEC, 123 test_start_time=start_time) 124 125 for device_type, device, device_test in devtuples: 126 # Only reconnect if we don't expect automatic reconnect 127 if not auto_reconnect: 128 if 'BLE' in device_type: 129 # LE can't reconnect without advertising/discoverable 130 self.test_device_set_discoverable(device, True) 131 # Make sure we're actually connected 132 self.test_device_is_connected(device.address) 133 else: 134 # Classic requires peer to initiate a connection to 135 # wake up the dut 136 self.test_connection_by_device(device) 137 138 if device_test is not None: 139 device_test(device) 140 141 finally: 142 for _, device, __ in devtuples: 143 self.test_remove_pairing(device.address) 144 145 @test_wrapper('Reconnect Classic HID', devices={'MOUSE': 1}) 146 def sr_reconnect_classic_hid(self): 147 """ Reconnects a classic HID device after suspend/resume. """ 148 device_type = 'MOUSE' 149 device = self.devices[device_type][0] 150 self.run_reconnect_device([(device_type, device, 151 self._test_mouse_left_click)]) 152 153 @test_wrapper('Reconnect LE HID', devices={'BLE_MOUSE': 1}) 154 def sr_reconnect_le_hid(self): 155 """ Reconnects a LE HID device after suspend/resume. """ 156 device_type = 'BLE_MOUSE' 157 device = self.devices[device_type][0] 158 self.run_reconnect_device([(device_type, device, 159 self._test_mouse_left_click)]) 160 161 # TODO(b/163143005) - Hana can't handle two concurrent HID connections 162 @test_wrapper('Reconnect Multiple Classic HID', 163 devices={ 164 'MOUSE': 1, 165 'KEYBOARD': 1 166 }, 167 skip_models=['hana']) 168 def sr_reconnect_multiple_classic_hid(self): 169 """ Reconnects multiple classic HID devices after suspend/resume. """ 170 devices = [('MOUSE', self.devices['MOUSE'][0], 171 self._test_mouse_left_click), 172 ('KEYBOARD', self.devices['KEYBOARD'][0], 173 self._test_keyboard_with_string)] 174 self.run_reconnect_device(devices) 175 176 @test_wrapper('Reconnect Multiple LE HID', 177 devices={ 178 'BLE_MOUSE': 1, 179 'BLE_KEYBOARD': 1 180 }) 181 def sr_reconnect_multiple_le_hid(self): 182 """ Reconnects multiple LE HID devices after suspend/resume. """ 183 devices = [('BLE_MOUSE', self.devices['BLE_MOUSE'][0], 184 self._test_mouse_left_click), 185 ('BLE_KEYBOARD', self.devices['BLE_KEYBOARD'][0], 186 self._test_keyboard_with_string)] 187 self.run_reconnect_device(devices) 188 189 @test_wrapper('Reconnect one of each classic+LE HID', 190 devices={ 191 'BLE_MOUSE': 1, 192 'KEYBOARD': 1 193 }) 194 def sr_reconnect_multiple_classic_le_hid(self): 195 """ Reconnects one of each classic and LE HID devices after 196 suspend/resume. 197 """ 198 devices = [('BLE_MOUSE', self.devices['BLE_MOUSE'][0], 199 self._test_mouse_left_click), 200 ('KEYBOARD', self.devices['KEYBOARD'][0], 201 self._test_keyboard_with_string)] 202 self.run_reconnect_device(devices) 203 204 @test_wrapper('Reconnect Classic HID Stress Test', devices={'MOUSE': 1}) 205 def sr_reconnect_classic_hid_stress(self): 206 """ Reconnects a classic HID device after suspend/resume. """ 207 device_type = 'MOUSE' 208 device = self.devices[device_type][0] 209 self.run_reconnect_device( 210 [(device_type, device, self._test_mouse_left_click)], 211 iterations=STRESS_ITERATIONS) 212 213 @test_wrapper('Reconnect LE HID Stress Test', devices={'BLE_MOUSE': 1}) 214 def sr_reconnect_le_hid_stress(self): 215 """ Reconnects a LE HID device after suspend/resume. """ 216 device_type = 'BLE_MOUSE' 217 device = self.devices[device_type][0] 218 self.run_reconnect_device( 219 [(device_type, device, self._test_mouse_left_click)], 220 iterations=STRESS_ITERATIONS) 221 222 @test_wrapper('Reconnect A2DP', 223 devices={'BLUETOOTH_AUDIO': 1}, 224 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 225 def sr_reconnect_a2dp(self): 226 """ Reconnects an A2DP device after suspend/resume. """ 227 device_type = 'BLUETOOTH_AUDIO' 228 device = self.devices[device_type][0] 229 self.initialize_bluetooth_audio(device, A2DP) 230 self.run_reconnect_device( 231 [(device_type, device, self.test_device_a2dp_connected)], 232 auto_reconnect=True) 233 234 # --------------------------------------------------------------- 235 # Wake from suspend tests 236 # --------------------------------------------------------------- 237 238 def run_peer_wakeup_device(self, 239 device_type, 240 device, 241 device_test=None, 242 iterations=1, 243 should_wake=True): 244 """ Uses paired peer device to wake the device from suspend. 245 246 @param device_type: the device type (used to determine if it's LE) 247 @param device: the meta device with the paired device 248 @param device_test: What to test to run after waking and connecting the 249 adapter/host 250 @param iterations: Number of suspend + peer wake loops to run 251 @param should_wake: Whether wakeup should occur on this test. With HID 252 peers, this should be True. With non-HID peers, this 253 should be false. 254 """ 255 boot_id = self.host.get_boot_id() 256 257 if should_wake: 258 sleep_time = EXPECT_PEER_WAKE_SUSPEND_SEC 259 resume_time = SUSPEND_SEC 260 resume_slack = 5 # Allow 5s slack for resume timeout 261 else: 262 sleep_time = EXPECT_NO_WAKE_SUSPEND_SEC 263 resume_time = EXPECT_NO_WAKE_SUSPEND_SEC 264 # Negative resume slack lets us wake a bit earlier than expected 265 # If suspend takes a while to enter, this may be necessary to get 266 # the timings right. 267 resume_slack = -5 268 269 # Clear wake before testing 270 self.test_adapter_set_wake_disabled() 271 272 try: 273 self.assert_discover_and_pair(device) 274 self.assert_on_fail( 275 self.test_device_set_discoverable(device, False)) 276 277 # Confirm connection completed 278 self.assert_on_fail(self.test_device_is_connected(device.address)) 279 280 # Profile connection may not have completed yet and this will 281 # race with a subsequent disconnection (due to suspend). Use the 282 # device test to force profile connect or wait if no test was 283 # given. 284 if device_test is not None: 285 self.assert_on_fail(device_test(device)) 286 else: 287 time.sleep(PROFILE_CONNECT_WAIT) 288 289 for it in range(iterations): 290 logging.info( 291 'Running iteration {}/{} of suspend peer wake'.format( 292 it + 1, iterations)) 293 294 # Start a new suspend instance 295 suspend = self.suspend_async(suspend_time=sleep_time, 296 expect_bt_wake=should_wake) 297 start_time = self.bluetooth_facade.get_device_time() 298 299 if should_wake: 300 self.test_device_wake_allowed(device.address) 301 # Also wait until powerd marks adapter as wake enabled 302 self.test_adapter_wake_enabled() 303 else: 304 self.test_device_wake_not_allowed(device.address) 305 306 # Trigger suspend, asynchronously wake and wait for resume 307 self.test_suspend_and_wait_for_sleep(suspend, sleep_timeout=5) 308 309 # Trigger peer wakeup 310 adapter_address = self.bluetooth_facade.address 311 peer_wake = self.device_connect_async(device_type, 312 device, 313 adapter_address, 314 delay_wake=5, 315 should_wake=should_wake) 316 peer_wake.start() 317 318 # Expect a quick resume. If a timeout occurs, test fails. Since 319 # we delay sending the wake signal, we should accommodate that 320 # in our expected timeout. 321 self.test_wait_for_resume(boot_id, 322 suspend, 323 resume_timeout=resume_time, 324 test_start_time=start_time, 325 resume_slack=resume_slack, 326 fail_on_timeout=should_wake, 327 fail_early_wake=not should_wake) 328 329 # Finish peer wake process 330 peer_wake.join() 331 332 # Only check peer device connection state if we expected to wake 333 # from it. Otherwise, we may or may not be connected based on 334 # the specific profile's reconnection policy. 335 if should_wake: 336 # Make sure we're actually connected 337 self.test_device_is_connected(device.address) 338 339 # Verify the profile is working 340 if device_test is not None: 341 device_test(device) 342 343 finally: 344 self.test_remove_pairing(device.address) 345 346 # TODO(b/151332866) - Bob can't wake from suspend due to wrong power/wakeup 347 # TODO(b/150897528) - Dru is powered down during suspend, won't wake up 348 @test_wrapper('Peer wakeup Classic HID', 349 devices={'MOUSE': 1}, 350 skip_models=TABLET_MODELS + ['bob', 'dru'], 351 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 352 def sr_peer_wake_classic_hid(self): 353 """ Use classic HID device to wake from suspend. """ 354 device = self.devices['MOUSE'][0] 355 self.run_peer_wakeup_device('MOUSE', 356 device, 357 device_test=self._test_mouse_left_click) 358 359 # TODO(b/151332866) - Bob can't wake from suspend due to wrong power/wakeup 360 # TODO(b/150897528) - Dru is powered down during suspend, won't wake up 361 @test_wrapper('Peer wakeup LE HID', 362 devices={'BLE_MOUSE': 1}, 363 skip_models=TABLET_MODELS + ['bob', 'dru'], 364 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 365 def sr_peer_wake_le_hid(self): 366 """ Use LE HID device to wake from suspend. """ 367 device = self.devices['BLE_MOUSE'][0] 368 self.run_peer_wakeup_device('BLE_MOUSE', 369 device, 370 device_test=self._test_mouse_left_click) 371 372 # TODO(b/151332866) - Bob can't wake from suspend due to wrong power/wakeup 373 # TODO(b/150897528) - Dru is powered down during suspend, won't wake up 374 @test_wrapper('Peer wakeup Classic HID', 375 devices={'MOUSE': 1}, 376 skip_models=TABLET_MODELS + ['bob', 'dru'], 377 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 378 def sr_peer_wake_classic_hid_stress(self): 379 """ Use classic HID device to wake from suspend. """ 380 device = self.devices['MOUSE'][0] 381 self.run_peer_wakeup_device('MOUSE', 382 device, 383 device_test=self._test_mouse_left_click, 384 iterations=STRESS_ITERATIONS) 385 386 # TODO(b/151332866) - Bob can't wake from suspend due to wrong power/wakeup 387 # TODO(b/150897528) - Dru is powered down during suspend, won't wake up 388 @test_wrapper('Peer wakeup LE HID', 389 devices={'BLE_MOUSE': 1}, 390 skip_models=TABLET_MODELS + ['bob', 'dru'], 391 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 392 def sr_peer_wake_le_hid_stress(self): 393 """ Use LE HID device to wake from suspend. """ 394 device = self.devices['BLE_MOUSE'][0] 395 self.run_peer_wakeup_device('BLE_MOUSE', 396 device, 397 device_test=self._test_mouse_left_click, 398 iterations=STRESS_ITERATIONS) 399 400 @test_wrapper('Peer wakeup with A2DP should fail', 401 devices={'BLUETOOTH_AUDIO': 1}, 402 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 403 def sr_peer_wake_a2dp_should_fail(self): 404 """ Use A2DP device to wake from suspend and fail. """ 405 device_type = 'BLUETOOTH_AUDIO' 406 device = self.devices[device_type][0] 407 self.initialize_bluetooth_audio(device, A2DP) 408 self.run_peer_wakeup_device( 409 device_type, 410 device, 411 device_test=self.test_device_a2dp_connected, 412 should_wake=False) 413 414 # --------------------------------------------------------------- 415 # Suspend while discovering and advertising 416 # --------------------------------------------------------------- 417 418 # TODO(b/150897528) - Scarlet Dru loses firmware around suspend 419 @test_wrapper('Suspend while discovering', 420 devices={'BLE_MOUSE': 1}, 421 skip_models=['dru'], 422 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 423 def sr_while_discovering(self): 424 """ Suspend while discovering. """ 425 device = self.devices['BLE_MOUSE'][0] 426 boot_id = self.host.get_boot_id() 427 428 self.test_device_set_discoverable(device, True) 429 430 # Test discovery without setting discovery filter 431 # ---------------------------------------------------------------------- 432 suspend = self.suspend_async(suspend_time=EXPECT_NO_WAKE_SUSPEND_SEC) 433 start_time = self.bluetooth_facade.get_device_time() 434 435 # We don't pair to the peer device because we don't want it in the 436 # allowlist. However, we want an advertising peer in this test 437 # responding to the discovery requests. 438 self.test_start_discovery() 439 self.test_suspend_and_wait_for_sleep(suspend, 440 sleep_timeout=SUSPEND_SEC) 441 442 # If discovery events wake us early, we will raise and suspend.exitcode 443 # will be non-zero 444 self.test_wait_for_resume(boot_id, 445 suspend, 446 resume_timeout=EXPECT_NO_WAKE_SUSPEND_SEC, 447 test_start_time=start_time) 448 449 # Discovering should restore after suspend 450 self.test_is_discovering() 451 self.test_stop_discovery() 452 453 # Test discovery with discovery filter set 454 # ---------------------------------------------------------------------- 455 suspend = self.suspend_async(suspend_time=EXPECT_NO_WAKE_SUSPEND_SEC) 456 start_time = self.bluetooth_facade.get_device_time() 457 458 self.test_set_discovery_filter({'Transport': 'auto'}) 459 self.test_start_discovery() 460 self.test_suspend_and_wait_for_sleep(suspend, 461 sleep_timeout=SUSPEND_SEC) 462 463 # If discovery events wake us early, we will raise and suspend.exitcode 464 # will be non-zero 465 self.test_wait_for_resume(boot_id, 466 suspend, 467 resume_timeout=EXPECT_NO_WAKE_SUSPEND_SEC, 468 test_start_time=start_time) 469 470 # Discovering should restore after suspend 471 self.test_is_discovering() 472 self.test_stop_discovery() 473 474 # TODO(b/150897528) - Scarlet Dru loses firmware around suspend 475 @test_wrapper('Suspend while advertising', 476 devices={'MOUSE': 1}, 477 skip_models=['dru'], 478 skip_chipsets=SUSPEND_POWER_DOWN_CHIPSETS) 479 def sr_while_advertising(self): 480 """ Suspend while advertising. """ 481 device = self.devices['MOUSE'][0] 482 boot_id = self.host.get_boot_id() 483 suspend = self.suspend_async(suspend_time=EXPECT_NO_WAKE_SUSPEND_SEC) 484 start_time = self.bluetooth_facade.get_device_time() 485 486 self.test_discoverable() 487 self.test_suspend_and_wait_for_sleep(suspend, 488 sleep_timeout=SUSPEND_SEC) 489 490 # Peer device should not be able to discover us in suspend 491 self.test_discover_by_device_fails(device) 492 493 self.test_wait_for_resume(boot_id, 494 suspend, 495 resume_timeout=EXPECT_NO_WAKE_SUSPEND_SEC, 496 test_start_time=start_time) 497 498 # Test that we are properly discoverable again 499 self.test_is_discoverable() 500 self.test_discover_by_device(device) 501 502 self.test_nondiscoverable() 503 504 # --------------------------------------------------------------- 505 # Health checks 506 # --------------------------------------------------------------- 507 508 @test_wrapper('Suspend while powered off', devices={'MOUSE': 1}) 509 def sr_while_powered_off(self): 510 """ Suspend while adapter is powered off. """ 511 device = self.devices['MOUSE'][0] 512 boot_id = self.host.get_boot_id() 513 suspend = self.suspend_async(suspend_time=SUSPEND_SEC) 514 start_time = self.bluetooth_facade.get_device_time() 515 516 # Pair device so we have something to do in suspend 517 self.assert_discover_and_pair(device) 518 519 # Trigger power down and quickly suspend 520 self.test_power_off_adapter() 521 self.test_suspend_and_wait_for_sleep(suspend, 522 sleep_timeout=SUSPEND_SEC) 523 # Suspend and resume should succeed 524 self.test_wait_for_resume(boot_id, 525 suspend, 526 resume_timeout=SUSPEND_SEC, 527 test_start_time=start_time) 528 529 # We should be able to power it back on 530 self.test_power_on_adapter() 531 532 # Test that we can reconnect to the device after powering back on 533 self.test_connection_by_device(device) 534 535 @batch_wrapper('SR with Peer Health') 536 def sr_health_batch_run(self, num_iterations=1, test_name=None): 537 """ Batch of suspend/resume peer health tests. """ 538 self.sr_reconnect_classic_hid() 539 self.sr_reconnect_le_hid() 540 self.sr_peer_wake_classic_hid() 541 self.sr_peer_wake_le_hid() 542 self.sr_while_discovering() 543 self.sr_while_advertising() 544 self.sr_reconnect_multiple_classic_hid() 545 self.sr_reconnect_multiple_le_hid() 546 self.sr_reconnect_multiple_classic_le_hid() 547 548 def run_once(self, 549 host, 550 num_iterations=1, 551 args_dict=None, 552 test_name=None, 553 flag='Quick Health'): 554 """Running Bluetooth adapter suspend resume with peer autotest. 555 556 @param host: the DUT, usually a chromebook 557 @param num_iterations: the number of times to execute the test 558 @param test_name: the test to run or None for all tests 559 @param flag: run tests with this flag (default: Quick Health) 560 561 """ 562 563 # Initialize and run the test batch or the requested specific test 564 self.quick_test_init(host, 565 use_btpeer=True, 566 flag=flag, 567 args_dict=args_dict) 568 self.sr_health_batch_run(num_iterations, test_name) 569 self.quick_test_cleanup() 570