blob: b13e384c8b566b62b69f0ebbc53acb05ac231b3b [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import re
import sys
from autotest_lib.client.bin import utils as client_utils
from autotest_lib.client.common_lib import error
from autotest_lib.server import utils
from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests
from autotest_lib.server.cros.tradefed import bundle_utils
from autotest_lib.server.cros.tradefed.tradefed_test import BundleSpecification
from autotest_lib.server.cros.tradefed.tradefed_test import TradefedTest
CTS_TARGET_MODULE = 'CtsBluetooth'
CTS_BUNDLE = 'arm'
CTS_URI = 'DEV'
CTS_TIMEOUT_SECONDS = 5400
CTS_CONFIG_RELATIVE_PATH = '../../cheets_CTS_{}/bundle_url_config.json'
ARC_FIELD_ID = 'CHROMEOS_ARC_ANDROID_SDK_VERSION'
ARC_VERSION_MAPPING = {'28': 'P', '30': 'R', '33': 'T'}
BT_ADDR_PATTERN = '([0-9A-F]{2}:){5}[0-9A-F]{2}'
class bluetooth_AdapterCTSHealth(TradefedTest,
bluetooth_adapter_tests.BluetoothAdapterTests
):
"""Class to run Bluetooth CTS health tests"""
def _tradefed_retry_command(self, template, session_id):
"""Needed by tradefed.
Content is copy-pasted, except the forget device part.
The reason is, we need to forget the device AFTER chrome is restarted,
so that cannot be done earlier.
"""
cmd = []
for arg in template:
cmd.append(arg.format(session_id=session_id))
self.forget_bonded_devices()
return cmd
def _tradefed_run_command(self, template):
"""Needed by tradefed.
Content is copy-pasted, except the forget device part.
The reason is, we need to forget the device AFTER chrome is restarted,
so that cannot be done earlier.
"""
cmd = template[:]
if self.arc_version == 'P':
# Apply this PATH change only for chroot environment
if not utils.is_in_container() and not client_utils.is_moblab():
try:
os.environ['JAVA_HOME'] = '/opt/icedtea-bin-3.4.0'
os.environ['PATH'] = os.environ['JAVA_HOME']\
+ '/bin:' + os.environ['PATH']
except OSError:
logging.error('Can\'t change current PATH directory')
# Suppress redundant output from tradefed.
cmd.append('--quiet-output=true')
self.forget_bonded_devices()
return cmd
def _get_tradefed_base_dir(self):
"""Needed by tradefed. Content is copy-pasted."""
return 'android-cts'
def _tradefed_cmd_path(self):
"""Needed by tradefed. Content is copy-pasted."""
return os.path.join(self._repository, 'tools', 'cts-tradefed')
def _get_bundle_specification(self, uri, bundle):
"""Tradefed function to get the test bundle.
This function is overrided to obtain the correct path for each arc
versions.
@param uri: determined by tradefed, could be 'DEV' or 'LATEST'
@param bundle: always 'arm' in our case, but can also be 'x86'
"""
test_path = sys.modules[self.__class__.__module__].__file__
config_path = os.path.abspath(
os.path.join(test_path,
CTS_CONFIG_RELATIVE_PATH.format(
self.arc_version)))
url_config = bundle_utils.load_config(config_path)
bundle_password = bundle_utils.get_bundle_password(url_config)
suite_name = bundle_utils.get_suite_name(url_config)
return BundleSpecification(
bundle_utils.make_bundle_url(url_config, uri, bundle),
bundle_password, suite_name)
def probe_arc_version(self):
"""Detects the installed ARC version."""
arc_version = ''
cmd_result = self.host.run('cat', args=('/etc/lsb-release', ))
output_text = cmd_result.stdout
for line in output_text.splitlines():
if ARC_FIELD_ID not in line:
continue
arc_version_num = line.split('=')[1].strip()
arc_version = ARC_VERSION_MAPPING.get(arc_version_num)
if not arc_version:
raise error.TestFail('Unknown ARC version: %s; expected: %s',
arc_version_num,
ARC_VERSION_MAPPING.keys())
if arc_version == '':
raise error.TestFail('ARC version not found')
self.arc_version = arc_version
logging.info('ARC version %s detected', arc_version)
def initialize_arc_templates(self):
"""Assigns the ARC templates.
The templates are required by the tradefed test. The content is copy
pasted from CTS test files.
"""
if self.arc_version == 'P':
self.run_template = [
'run', 'commandAndExit', 'cts', '--module',
'CtsBluetoothTestCases', '--logcat-on-failure',
'--dynamic-config-url='
]
self.retry_template = [
'run', 'commandAndExit', 'retry', '--retry',
'{session_id}', '--dynamic-config-url='
]
elif self.arc_version == 'R' or self.arc_version == 'T':
self.run_template = [
'run', 'commandAndExit', 'cts', '--include-filter',
'CtsBluetoothTestCases', '--include-filter',
'CtsBluetoothTestCases[secondary_user]',
'--logcat-on-failure'
]
self.retry_template = [
'run', 'commandAndExit', 'retry', '--retry', '{session_id}'
]
def initialize(self, host):
"""Tradefed function to initialize the test.
This function is overrided to store the arc_version, in order to
avoid creating one test file for each of the arc versions.
@param host: The DUT, usually a chromebook
"""
if not host:
raise error.TestFail('The host is unspecified')
self.host = host
self.probe_arc_version()
self.initialize_arc_templates()
TradefedTest.initialize(self,
bundle=CTS_BUNDLE,
uri=CTS_URI,
host=host)
bluetooth_adapter_tests.BluetoothAdapterTests.initialize(self)
def prepare_btpeers(self, args_dict):
"""Prepares the btpeers for the CTS test.
We need two btpeers: a classic peer to be discoverable and an LE peer
to advertise. We need to be able to receive both inquiry and
advertisement to pass all CTS tests, therefore we require the two peers.
It's ideal to use the same framework as the other BT adapter tests, but
that is difficult to achieve since Tradefed tests has it's own quirks.
Directly using both of them makes things complicated (such as both are
trying to restart UI). Therefore, here we only reuse some methods and
copy some parts of Bluetooth adapter tests.
@param args_dict: The arguments passed from the command line
"""
btpeer_args = self.host.get_btpeer_arguments(args_dict)
self.host.initialize_btpeer(btpeer_args=btpeer_args)
for btpeer in self.host.btpeer_list:
btpeer.register_raspPi_log(self.outputdir)
self.btpeer_group = dict()
self.btpeer_group_copy = dict()
self.group_btpeers_type()
devices = {'KEYBOARD': 1, 'BLE_MOUSE': 1}
is_enough_peer = self.get_device_rasp(devices)
if not is_enough_peer:
raise error.TestNAError('Not enough peers!')
classic_peer = self.devices['KEYBOARD'][0]
classic_peer.SetDiscoverable(True)
ble_peer = self.devices['BLE_MOUSE'][0]
ble_peer.SetDiscoverable(True)
def stop_btpeers(self):
"""Stops the btpeers from being discoverable.
This is just a courtesy to stop the scanning that is started on the
prepare function. The outcome of the test is not impacted by anything
inside this function, and it's safe not to call this at all.
"""
classic_peer = self.devices['KEYBOARD'][0]
classic_peer.SetDiscoverable(False)
ble_peer = self.devices['BLE_MOUSE'][0]
ble_peer.SetDiscoverable(False)
def forget_bonded_devices(self):
"""Removes the bonded devices.
We need to remove the bonded devices in order to prevent them
interfering with the test result (e.g. DUT auto connects to them).
This is done in the beginning of the test, similar to other Bluetooth
tests. However, instead of calling test_reset_on_adapter(), we directly
call some commands. This is done because test_reset_on_adapter needs
bluetooth_facade with conflicts with the tradefed test.
"""
if self.floss:
# in btclient, peer address is enclosed with [], host address isn't.
regex = re.compile('\[' + BT_ADDR_PATTERN + '\]')
self.host.run('btclient',
args=('--command', 'adapter enable', '--timeout',
'5'))
cmd_list = self.host.run('btclient',
args=('--command', 'list bonded',
'--timeout', '5'))
else:
regex = re.compile(BT_ADDR_PATTERN)
self.host.run('bluetoothctl', args=('power', 'on'))
cmd_list = self.host.run('bluetoothctl',
args=('devices', 'Bonded'))
output_list = cmd_list.stdout
for line in output_list.splitlines():
result = regex.search(line)
if result is None:
continue
addr = result.group().strip('[]')
if self.floss:
self.host.run('btclient',
args=('--command', 'bond remove ' + addr,
'--timeout', '5'))
else:
self.host.run('bluetoothctl', args=('remove', addr))
def run_once(self, test_name=None, floss=False, args_dict=None):
"""Runs the batch of Bluetooth CTS health tests
@param test_name: The name of the test
@param floss: Is Bluetooth running Floss?
@param args_dict: The arguments passed from the command line
"""
self.floss = floss
self.prepare_btpeers(args_dict)
chrome_feature = None
if floss:
# A dirty way to disable floss prevention only for this test
chrome_feature = 'Floss --disable-feature=FlossIsAvailabilityCheckNeeded'
try:
self._run_tradefed_with_retries(test_name=test_name,
run_template=self.run_template,
retry_template=self.retry_template,
target_module=CTS_TARGET_MODULE,
bundle=CTS_BUNDLE,
timeout=CTS_TIMEOUT_SECONDS,
chrome_feature=chrome_feature)
finally:
self.stop_btpeers()