| # 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() |