bluetooth: add a new bluetooth_AdapterPowerMeasure test
This patch adds a new test to measure the power consumption
during system suspension.
To run the test, a Raspberry Pi is required to act as the
chameleon host which installs the servo dependency as specified
in Cq-Depend:chromium:1981429
The only supported board is 'kukui' for now. The other boards will
be supported when a more generic method is figured out to specify the
config path for servod.
BUG=b:143862071
TEST=Follow the two steps below
Step 1: connect a servo micro between a DUT, i.e., Kukui, and a
Raspberry Pi.
Step 2: run the autotest as
(cros) test_that --autotest_dir ~/trunk/src/third_party/autotest/files/
--args "chameleon_host=$CHAMELEON_IP" $DUT_IP
bluetooth_AdapterPowerMeasure.suspension
For a stress test of 100 iterations, use the control file:
bluetooth_AdapterPowerMeasure.suspension_100
Cq-Depend:chromium:1981429, chromium:1980403
Change-Id: Id66f8b4dad911a462096fcada561a82113d78066
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1900950
Reviewed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Reviewed-by: Yun-Hao Chung <howardchung@google.com>
Commit-Queue: Shyh-In Hwang <josephsih@chromium.org>
Tested-by: Shyh-In Hwang <josephsih@chromium.org>
diff --git a/client/cros/chameleon/chameleon.py b/client/cros/chameleon/chameleon.py
index 37bd6a1..4f14c8c 100644
--- a/client/cros/chameleon/chameleon.py
+++ b/client/cros/chameleon/chameleon.py
@@ -400,6 +400,16 @@
return self._usb_ctrl
+ def get_bluetooth_base(self):
+ """Gets the Bluetooth base object on Chameleon.
+
+ This is a base object that does not emulate any Bluetooth device.
+
+ @return: A BluetoothBaseFlow object.
+ """
+ return self._chameleond_proxy.bluetooth_base
+
+
def get_bluetooth_hid_mouse(self):
"""Gets the emulated Bluetooth (BR/EDR) HID mouse on Chameleon.
diff --git a/server/cros/bluetooth/bluetooth_adapter_tests.py b/server/cros/bluetooth/bluetooth_adapter_tests.py
index bb2d4c5..780d270 100644
--- a/server/cros/bluetooth/bluetooth_adapter_tests.py
+++ b/server/cros/bluetooth/bluetooth_adapter_tests.py
@@ -40,6 +40,11 @@
'BLE_MOUSE': lambda chameleon: chameleon.get_ble_mouse,
'BLE_KEYBOARD': lambda chameleon: chameleon.get_ble_keyboard,
'A2DP_SINK': lambda chameleon: chameleon.get_bluetooth_a2dp_sink,
+
+ # This is a base object that does not emulate any Bluetooth device.
+ # This object is preferred when only a pure XMLRPC server is needed
+ # on the chameleon host, e.g., to perform servod methods.
+ 'BLUETOOTH_BASE': lambda chameleon: chameleon.get_bluetooth_base,
}
@@ -546,8 +551,7 @@
def group_chameleons_type(self):
- """Group all chameleons by the type of their detected device
- """
+ """Group all chameleons by the type of their detected device."""
# Use previously created chameleon_group instead of creating new
if len(self.chameleon_group_copy) > 0:
@@ -2779,6 +2783,27 @@
# -------------------------------------------------------------------
+ # Servod related tests
+ # -------------------------------------------------------------------
+
+ @_test_retry_and_log
+ def test_power_consumption(self, max_power_mw):
+ """Test the average power consumption."""
+ power_mw = self.device.servod.MeasurePowerConsumption()
+ self.results = {'power_mw': power_mw}
+
+ if (power_mw is None):
+ logging.error('Failed to measure power consumption')
+ return False
+
+ power_mw = float(power_mw)
+ logging.info('power consumption (mw): %f (max allowed: %f)',
+ power_mw, max_power_mw)
+
+ return power_mw <= max_power_mw
+
+
+ # -------------------------------------------------------------------
# Autotest methods
# -------------------------------------------------------------------
diff --git a/server/hosts/cros_label.py b/server/hosts/cros_label.py
index a25b12a..186a2e1 100644
--- a/server/hosts/cros_label.py
+++ b/server/hosts/cros_label.py
@@ -366,6 +366,14 @@
logging.error('Error with initializing bt_a2dp_sink on '
'chameleon %s', chameleon_host.hostname)
+ try:
+ bt_base_device = chameleon.get_bluetooth_base()
+ if bt_base_device.IsDetected():
+ labels.append('bt_base')
+ except:
+ logging.error('Error in detecting bt_base on '
+ 'chameleon %s', chameleon_host.hostname)
+
if labels != []:
labels.append('bt_peer')
diff --git a/server/site_tests/bluetooth_AdapterPowerMeasure/bluetooth_AdapterPowerMeasure.py b/server/site_tests/bluetooth_AdapterPowerMeasure/bluetooth_AdapterPowerMeasure.py
new file mode 100644
index 0000000..14073eb
--- /dev/null
+++ b/server/site_tests/bluetooth_AdapterPowerMeasure/bluetooth_AdapterPowerMeasure.py
@@ -0,0 +1,172 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Server side bluetooth adapter stress tests involving power consumption."""
+
+import logging
+import multiprocessing
+import time
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests
+from autotest_lib.server.cros.multimedia import remote_facade_factory
+
+
+test_case_log = bluetooth_adapter_tests.test_case_log
+
+
+class bluetooth_AdapterPowerMeasure(
+ bluetooth_adapter_tests.BluetoothAdapterTests):
+ """Server side bluetooth adapter power consumption test."""
+
+ # ---------------------------------------------------------------
+ # Definitions of all test cases
+ # ---------------------------------------------------------------
+
+ @test_case_log
+ def test_case_suspend_power_measurement(self, host, max_power_mw,
+ suspend_time_secs,
+ resume_network_timeout_secs=60):
+ """Test Case: measure the Bluetooth chip power consumption on suspend"""
+
+ def print_debug_count():
+ """Print the debug message about count values."""
+ logging.debug('count_fail_to_sleep: %d', self.count_fail_to_sleep)
+ logging.debug('count_fail_to_resume: %d', self.count_fail_to_resume)
+ logging.debug('count_system_resume_prematurely: %d',
+ self.count_system_resume_prematurely)
+ logging.debug('count_success: %d', self.count_success)
+
+ def action_suspend():
+ """Calls the host method suspend."""
+ host.suspend(suspend_time=suspend_time_secs,
+ allow_early_resume=True)
+
+ boot_id = host.get_boot_id()
+ proc = multiprocessing.Process(target=action_suspend)
+ proc.daemon = True
+ start_time = time.time()
+ proc.start()
+
+ # Block waiting until the system has suspended.
+ try:
+ host.test_wait_for_sleep(suspend_time_secs)
+ except Exception as e:
+ logging.error('host.test_wait_for_sleep failed: %s', e)
+ self.count_fail_to_sleep += 1
+ print_debug_count()
+ # Skip this time since the system failed to suspend.
+ proc.join()
+ return
+
+ # Test the Bluetooth chip power consumption.
+ if self.test_power_consumption(max_power_mw):
+ self.count_success += 1
+
+ # Block waiting until the system has resumed.
+ try:
+ host.test_wait_for_resume(
+ boot_id, suspend_time_secs + resume_network_timeout_secs)
+ except Exception as e:
+ logging.error('host.test_wait_for_resume failed: %s', e)
+ self.count_fail_to_resume += 1
+
+ # If the system resumes prematurely, do not conduct the test in
+ # this iteration.
+ actual_suspend_time_secs = time.time() - start_time
+ if actual_suspend_time_secs < suspend_time_secs:
+ logging.error('actual suspension time %f is less than expected %f',
+ actual_suspend_time_secs, suspend_time_secs)
+ self.count_system_resume_prematurely += 1
+
+ print_debug_count()
+ proc.join()
+
+
+ def initialize_servod(self):
+ """Peform initialize for servod task."""
+ self.count_fail_to_sleep = 0
+ self.count_fail_to_resume = 0
+ self.count_system_resume_prematurely = 0
+ self.count_success = 0
+
+ # When the autotest restarts ui, chrome would issue some Bluetooth
+ # commands which may prevent the system from suspending properly.
+ # Hence, let's stop ui for now.
+ self.host.run_short('stop ui')
+
+ board = self.host.get_board().split(':')[1]
+ logging.info('board: %s', board)
+
+ # TODO: figure out a way to support other boards.
+ if board != 'kukui':
+ raise error.TestError('Only kukui is supported for now.')
+
+ # self.device is a pure XMLRPC server running as chameleond
+ # on the chameleon host. We need to enable Servod.
+ if not self.device.EnableServod(board):
+ raise error.TestError('Failed to enable Servod.')
+
+ # Start the Servod process on the chameleon host.
+ if not self.device.servod.Start():
+ raise error.TestError('Failed to start Servod on chameleon host.')
+
+
+ def cleanup_servod(self):
+ """Peform cleanup for servod."""
+ if not self.device.servod.Stop():
+ logging.error('Failed to stop Servod on chameleon host.')
+
+ self.host.run_short('start ui')
+
+ logging.info('count_fail_to_sleep: %d', self.count_fail_to_sleep)
+ logging.info('count_fail_to_resume: %d', self.count_fail_to_resume)
+ logging.info('count_system_resume_prematurely: %d',
+ self.count_system_resume_prematurely)
+ logging.info('count_success: %d', self.count_success)
+
+
+ def run_once(self, host, max_power_mw=3, device_type='BLUETOOTH_BASE',
+ num_iterations=1, suspend_time_secs=30,
+ test_category='suspension'):
+ """Running Bluetooth adapter power consumption autotest during system
+ suspension.
+
+ @param host: the DUT host.
+ @param max_power_mw: max power allowed in milli-watt
+ @param device_type: the device type emulated by the chameleon host
+ @param num_iterations: number of times to perform the tests.
+ @param suspend_time_secs: the system suspension duration in seconds
+ @param test_category: the test category
+
+ """
+
+ self.host = host
+ factory = remote_facade_factory.RemoteFacadeFactory(host)
+ self.bluetooth_facade = factory.create_bluetooth_hid_facade()
+
+ self.check_chameleon()
+ self.device = self.get_device(device_type)
+ self.initialize_servod()
+ self.test_power_on_adapter()
+ self.test_bluetoothd_running()
+
+ for i in xrange(1, num_iterations + 1):
+ logging.info('Starting iteration: %d / %d', i, num_iterations)
+
+ if test_category == 'suspension':
+ self.test_case_suspend_power_measurement(host, max_power_mw,
+ suspend_time_secs)
+ else:
+ logging.error('Do not support the test category: %s',
+ test_category)
+
+ if device_type == 'BLUETOOTH_BASE':
+ self.cleanup_servod()
+
+ if self.count_success == 0:
+ raise error.TestError('System failed to suspend/resume.')
+
+ if self.fails:
+ raise error.TestFail(self.fails)
diff --git a/server/site_tests/bluetooth_AdapterPowerMeasure/control.suspension b/server/site_tests/bluetooth_AdapterPowerMeasure/control.suspension
new file mode 100644
index 0000000..d74679b
--- /dev/null
+++ b/server/site_tests/bluetooth_AdapterPowerMeasure/control.suspension
@@ -0,0 +1,36 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from autotest_lib.server import utils
+from autotest_lib.server.cros.bluetooth import advertisements_data
+
+
+AUTHOR = 'Chrome OS Team'
+NAME = 'bluetooth_AdapterPowerMeasure.suspension'
+PURPOSE = 'Test power consumption of Bluetooth chip during system suspension.'
+CRITERIA = 'Bluetooth chip should consume power less than specified.'
+ATTRIBUTES = 'suite:bluetooth'
+TIME = 'SHORT' # Takes about 2 mins
+TEST_CATEGORY = 'Functional'
+TEST_CLASS = 'bluetooth'
+TEST_TYPE = 'server'
+DEPENDENCIES = 'bluetooth, chameleon:bt_base'
+
+DOC = """
+This test case verifies that the Bluetooth chip of the DUT does
+not consume power more than specified during system suspension.
+
+This autotest include the following test cases:
+ self.test_case_suspend_power_measurement()
+"""
+
+args_dict = utils.args_to_dict(args)
+chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
+
+def run(machine):
+ host = hosts.create_host(machine, chameleon_args=chameleon_args)
+ job.run_test('bluetooth_AdapterPowerMeasure', host=host, max_power_mw=3,
+ num_iterations=1)
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/bluetooth_AdapterPowerMeasure/control.suspension_100 b/server/site_tests/bluetooth_AdapterPowerMeasure/control.suspension_100
new file mode 100644
index 0000000..79c557d
--- /dev/null
+++ b/server/site_tests/bluetooth_AdapterPowerMeasure/control.suspension_100
@@ -0,0 +1,36 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from autotest_lib.server import utils
+from autotest_lib.server.cros.bluetooth import advertisements_data
+
+
+AUTHOR = 'Chrome OS Team'
+NAME = 'bluetooth_AdapterPowerMeasure.suspension_100'
+PURPOSE = 'Test power consumption of Bluetooth chip during system suspension.'
+CRITERIA = 'Bluetooth chip should consume power less than specified.'
+ATTRIBUTES = 'suite:bluetooth'
+TIME = 'LONG' # Takes about 60 mins
+TEST_CATEGORY = 'Functional'
+TEST_CLASS = 'bluetooth'
+TEST_TYPE = 'server'
+DEPENDENCIES = 'bluetooth, chameleon:bt_base'
+
+DOC = """
+This test case verifies that the Bluetooth chip of the DUT does
+not consume power more than specified during system suspension.
+
+This autotest include the following test cases:
+ self.test_case_suspend_power_measurement()
+"""
+
+args_dict = utils.args_to_dict(args)
+chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
+
+def run(machine):
+ host = hosts.create_host(machine, chameleon_args=chameleon_args)
+ job.run_test('bluetooth_AdapterPowerMeasure', host=host, max_power_mw=3,
+ num_iterations=100)
+
+parallel_simple(run, machines)