blob: c7393c9e28d131da40d4b4d5d528f9b4bfa9c841 [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A Batch of Bluetooth Quality Report tests"""
import collections
import logging
import time
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.bluetooth.bluetooth_audio_test_data import (
A2DP_MEDIUM, HFP_WBS, HFP_NBS, HFP_WBS_MEDIUM, HFP_NBS_MEDIUM)
from autotest_lib.server.cros.bluetooth.bluetooth_adapter_qr_tests import (
BluetoothAdapterQRTests, QR_UNSUPPORTED_CHIPSETS)
from autotest_lib.server.cros.bluetooth.bluetooth_adapter_quick_tests import (
BluetoothAdapterQuickTests)
class bluetooth_AdapterQRHealth(BluetoothAdapterQuickTests,
BluetoothAdapterQRTests):
"""A Batch of Bluetooth audio health tests"""
test_wrapper = BluetoothAdapterQuickTests.quick_test_test_decorator
batch_wrapper = BluetoothAdapterQuickTests.quick_test_batch_decorator
def run_test_method(self,
devices,
test_method,
test_profile,
logging_and_check=True):
"""Common procedure to run a specific test method.
@param devices: a list of devices.
@param test_method: the test method to run.
@param test_profile: audio test profile to use.
@param logging_and_check: set this to True to opend the quality
report log checking.
"""
if not isinstance(devices, collections.Iterable):
devices = (devices, )
num_devices = len(devices)
# Make sure WBS profile works fine.
if test_profile in (HFP_WBS, HFP_WBS_MEDIUM):
if self.check_wbs_capability():
if not self.bluetooth_facade.enable_wbs(True):
raise error.TestError('failed to enble wbs')
else:
raise error.TestNAError(
'The DUT does not support WBS. Skip the test.')
elif test_profile in (HFP_NBS, HFP_NBS_MEDIUM):
if not self.bluetooth_facade.enable_wbs(False):
raise error.TestError('failed to disable wbs')
time.sleep(3)
# The qr_enable_quality_report() will restart bluetoothd. Hence, this
# has to be executed before pairing.
self.qr_enable_quality_report()
try:
for device in devices:
if device.device_type == 'BLUETOOTH_AUDIO':
self.initialize_bluetooth_audio(device, test_profile)
self.test_discover_device(device.address)
self.test_pairing(device.address, device.pin, trusted=True)
self.test_connection_by_adapter(device.address)
time.sleep(2)
if logging_and_check:
self.dut_btmon_log_path = self.start_new_btmon()
# The qr_disabled_a2dp() test_method calls
# the self.test_disable_quality_report().
test_method()
if logging_and_check:
self.test_send_log()
self.check_qr_event_log(num_devices=num_devices)
finally:
# Disable the quality report and the quality debug log
# unconditionally for all test_method().
# The qr_disabled_a2dp() test_method will try to disable the quality
# report twice in this way. This is done on purpose to make enabling
# and disabling BQR related functions clean and symmetric.
# Also note that the kernel will only disable the BQR feature while
# it is enabled. The kernel will not disable the BQR feature while
# it is already disabled.
self.qr_disable_quality_report()
for device in devices:
self.test_disconnection_by_adapter(device.address)
if device.device_type == 'BLUETOOTH_AUDIO':
self.cleanup_bluetooth_audio(device, test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report A2DP test',
devices={'BLUETOOTH_AUDIO': 1},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_a2dp_test(self):
"""Quality Report A2DP test"""
device = self.devices['BLUETOOTH_AUDIO'][0]
test_profile = A2DP_MEDIUM
test_method = lambda: self.qr_a2dp(device, test_profile)
self.run_test_method(device, test_method, test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report power cycle and A2DP test',
devices={'BLUETOOTH_AUDIO': 1},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_power_cycle_a2dp_test(self):
"""Quality Report power cycle and A2DP test"""
device = self.devices['BLUETOOTH_AUDIO'][0]
test_profile = A2DP_MEDIUM
test_method = lambda: self.qr_power_cycle_a2dp(device, test_profile)
self.run_test_method(device, test_method, test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report HFP NBS dut as source test',
devices={'BLUETOOTH_AUDIO': 1},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_hfp_nbs_dut_as_src_test(self):
"""Quality Report HFP NBS dut as source test"""
device = self.devices['BLUETOOTH_AUDIO'][0]
test_profile = HFP_NBS_MEDIUM
test_method = lambda: self.qr_hfp_dut_as_src(device, test_profile)
self.run_test_method(device, test_method, test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report HFP WBS dut as source test',
devices={'BLUETOOTH_AUDIO': 1},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_hfp_wbs_dut_as_src_test(self):
"""Quality Report HFP WBS dut as source test"""
device = self.devices['BLUETOOTH_AUDIO'][0]
test_profile = HFP_WBS_MEDIUM
test_method = lambda: self.qr_hfp_dut_as_src(device, test_profile)
self.run_test_method(device, test_method, test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report disabled A2DP test',
devices={'BLUETOOTH_AUDIO': 1},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_disabled_a2dp_test(self):
"""Quality Report disabled A2DP test"""
device = self.devices['BLUETOOTH_AUDIO'][0]
test_profile = A2DP_MEDIUM
test_method = lambda: self.qr_disabled_a2dp(device, test_profile)
self.run_test_method(device,
test_method,
test_profile,
logging_and_check=False)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report A2DP and classic keyboard test',
devices={
'BLUETOOTH_AUDIO': 1,
"KEYBOARD": 1
},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_a2dp_cl_keyboard_test(self):
"""Quality Report A2DP and classic keyboard test"""
audio_device = self.devices['BLUETOOTH_AUDIO'][0]
keyboard_device = self.devices['KEYBOARD'][0]
test_profile = A2DP_MEDIUM
test_method = lambda: self.qr_a2dp_cl_keyboard(
audio_device, keyboard_device, test_profile)
self.run_test_method((audio_device, keyboard_device),
test_method,
test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper(
'Quality Report HFP WBS dut as sink and classic keyboard test',
devices={
'BLUETOOTH_AUDIO': 1,
'KEYBOARD': 1
},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_hfp_wbs_dut_as_sink_cl_keyboard_test(self):
"""Quality Report HFP WBS dut as sink and classic keyboard test"""
audio_device = self.devices['BLUETOOTH_AUDIO'][0]
keyboard_device = self.devices['KEYBOARD'][0]
test_profile = HFP_WBS_MEDIUM
test_method = lambda: self.qr_hfp_dut_as_sink_cl_keyboard(
audio_device, keyboard_device, test_profile)
self.run_test_method((audio_device, keyboard_device),
test_method,
test_profile)
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper(
'Quality Report HFP NBS dut as sink and classic keyboard test',
devices={
'BLUETOOTH_AUDIO': 1,
'KEYBOARD': 1
},
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_hfp_nbs_dut_as_sink_cl_keyboard_test(self):
"""Quality Report HFP NBS dut as sink and classic keyboard test"""
audio_device = self.devices['BLUETOOTH_AUDIO'][0]
keyboard_device = self.devices['KEYBOARD'][0]
test_profile = HFP_NBS_MEDIUM
test_method = lambda: self.qr_hfp_dut_as_sink_cl_keyboard(
audio_device, keyboard_device, test_profile)
self.run_test_method((audio_device, keyboard_device),
test_method,
test_profile)
def run_check_states_seq(self, test_name, test_method, expected_states):
"""Framework to run a test for BQR state check.
It first restarts btmon to clear the BQR toggle history. Then it runs
the specified test_method. After a short pause, it checks if the BQR
toggle history is expected.
@param test_name: the test name
@param test_method: the test method to run
@param expected_states: the list of expected BQR toggle history
"""
logging.info('')
logging.info('%s %s %s', 6 * '.', test_name, 6 * '.')
self.dut_btmon_log_path = self.start_new_btmon()
test_method()
# the D-Bus method may take near 1 second to arrive the
# lower layers in the kernel. Wait a while for safety.
time.sleep(2)
self.send_btsnoop_log()
self.test_check_qr_states(expected_states=expected_states)
# This test does not need a btpeer device.
# Remove flags=['Quick Health'] when this test is migrated to stable suite.
@test_wrapper('Quality Report state test',
flags=['Quick Health'],
skip_chipsets=QR_UNSUPPORTED_CHIPSETS)
def qr_check_states_test(self):
"""Quality Report check states test
This test checks whether the bluetooth software stack, including the
user space daemon and the kernel, enables or disables the Bluetooth
Quality Report feature correctly under combinations of the following
conditions:
- DUT reboots
- bluetoothd stops and starts
- the adapter powers off and on
- the system suspends and resumes
Note that this test purely verifies the bluetooth software stack
instead of the controller functionality. Hence, btpeer devices are not
employed so that the test can verify various scenarios at a reasonable
speed.
"""
self.init_check_qr_states()
# Assume that BQR is disabled initially.
# When BQR is enabled by default, we will need to modify the ordering of
# the following tests.
# - placing the self.test_disable_quality_report test as the first test,
# - placing the self.test_enable_quality_report test as the last test.
# At this time, the kernel HCI_QUALITY_REPORT flag is initially false.
# --------------------------------------------------------------------
# The expected_states below indicates enable (True) or disable (Flase)
# actions that are expected to observe in the captured btsnoop logs.
#
# Note that expected_states can be [], i.e., empty, because
# - The kernel won't enable the BQR feature if it is already enabled.
# - The kernel won't disable the BQR feature if it is already disabled.
#
# expected_states=[False] indicates the disabled action is expected.
# expected_states=[True] indicates the enabled action is expected.
# expected_states=[False, True] indicates the disabled action followed
# by the enabled action is expected, etc.
# Reboot the DUT so that both the kernel and the user space are in
# clean states. Do not check the expected_states for two reasons:
# - There is a race condition regarding whether the btmon log could
# capture the BQR disablement command in powering down the machine.
# - Whether the BQR is enabled on the bluetooth daemon startup is
# determined by the options in start_bluetoothd.sh which change
# over time.
self.reboot()
# Disable BQR so that there is a consistent BQR disabled state.
# Whether a BQR disablement command can be captured in the btmon log
# depends on whether BQR is enabled on the bluetooth daemon startup.
self.test_disable_quality_report()
# Stop bluetoothd and BQR should remain disabled.
# expected_states=[] as kernel won't disable the already disabled BQR.
self.run_check_states_seq('stop bluetoothd when BQR is disabled',
self.test_stop_bluetoothd,
expected_states=[])
# Start bluetoothd and BQR should remain disabled.
# expected_states=[] as kernel won't disable the already disabled BQR.
self.run_check_states_seq('start bluetoothd when BQR is disabled',
self.test_start_bluetoothd,
expected_states=[])
# Power off the adapter and BQR should remain disabled.
# expected_states=[] as kernel won't disable the already disabled BQR.
self.run_check_states_seq('power off the adapter when BQR is disabled',
self.test_power_off_adapter,
expected_states=[])
# Power on the adapter and BQR should remain disabled.
# expected_states=[] as kernel won't disable the already disabled BQR.
self.run_check_states_seq('power on the adapter when BQR is disabled',
self.test_power_on_adapter,
expected_states=[])
# Do system suspend and resume. BQR should remain disabled.
# expected_states=[] as kernel won't disable the already disabled BQR.
self.run_check_states_seq('system suspend and resume when BQR is disabled',
self.suspend_resume,
expected_states=[])
# Enable BQR and BQR should be enabled.
self.run_check_states_seq('enable BQR',
self.test_enable_quality_report,
expected_states=[True])
# Stop bluetoothd and BQR should be disabled.
self.run_check_states_seq('stop bluetoothd when BQR is enabled',
self.test_stop_bluetoothd,
expected_states=[False])
# Start bluetoothd and BQR should be enabled.
self.run_check_states_seq('start bluetoothd when BQR is enabled',
self.test_start_bluetoothd,
expected_states=[True])
# Power off the adapter and BQR should be disabled.
self.run_check_states_seq('power off the adapter when BQR is enabled',
self.test_power_off_adapter,
expected_states=[False])
# Power on the adapter and BQR should be enabled.
self.run_check_states_seq('power on the adapter when BQR is enabled',
self.test_power_on_adapter,
expected_states=[True])
# Do system suspend and resume. BQR should be disabled and then re-enabled.
self.run_check_states_seq('system suspend and resume when BQR is enabled',
self.suspend_resume,
expected_states=[False, True])
# Disable BQR and BQR should be disabled.
self.run_check_states_seq('Disable BQR',
self.test_disable_quality_report,
expected_states=[False])
@batch_wrapper('Bluetooth BQR Batch Health Tests')
def qr_health_batch_run(self, num_iterations=1, test_name=None):
"""Run the bluetooth audio health test batch or a specific given test.
@param num_iterations: how many iterations to run
@param test_name: specific test to run otherwise None to run the
whole batch
"""
self.qr_a2dp_test()
self.qr_power_cycle_a2dp_test()
self.qr_hfp_nbs_dut_as_src_test()
self.qr_hfp_wbs_dut_as_src_test()
self.qr_disabled_a2dp_test()
self.qr_a2dp_cl_keyboard_test()
self.qr_hfp_wbs_dut_as_sink_cl_keyboard_test()
self.qr_hfp_nbs_dut_as_sink_cl_keyboard_test()
def run_once(self,
host,
num_iterations=1,
args_dict=None,
test_name=None,
flag='Quick Health'):
"""Run the batch of Bluetooth stand health tests
@param host: the DUT, usually a chromebook
@param num_iterations: the number of rounds to execute the test
@param test_name: the test to run, or None for all tests
"""
self.host = host
# The btpeers are not needed in the qr_check_states_test.
use_btpeer_flag = (test_name != 'qr_check_states_test')
self.quick_test_init(host,
use_btpeer=use_btpeer_flag,
flag=flag,
args_dict=args_dict)
self.qr_health_batch_run(num_iterations, test_name)
self.quick_test_cleanup()