1# Lint as: python2, python3
2# Copyright 2019 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
6"""Server side bluetooth adapter stress tests involving power consumption."""
7
8import logging
9import multiprocessing
10import time
11
12from autotest_lib.client.common_lib import error
13from autotest_lib.server.cros.bluetooth.bluetooth_adapter_tests import (
14        test_case_log)
15from autotest_lib.server.cros.bluetooth.bluetooth_adapter_quick_tests import (
16        BluetoothAdapterQuickTests)
17
18
19class bluetooth_AdapterPowerMeasure(BluetoothAdapterQuickTests):
20    """Server side bluetooth adapter power consumption test."""
21
22    test_wrapper = BluetoothAdapterQuickTests.quick_test_test_decorator
23    batch_wrapper = BluetoothAdapterQuickTests.quick_test_batch_decorator
24
25
26    def _check_legitimate_board(self):
27        """Check if this is a legitimate board to run the test.
28
29        Only a limited set of boards are supported for now, primarily
30        the kukui family of barods.
31
32        @raises: TestNAError if the board is not legitimate.
33
34        """
35        board = self.host.get_board().split(':')[1]
36        if board not in ('kukui'):
37            raise error.TestNAError('%s not legitimate to run the test.' %
38                                    board)
39
40
41    def _initialize_servod(self, device):
42        """Perform initialize for servod task.
43
44        @param device: the peer device
45
46        @raises: TestError if not able to enable or start servo.
47        """
48        self.count_fail_to_sleep = 0
49        self.count_fail_to_resume = 0
50        self.count_system_resume_prematurely = 0
51        self.count_success = 0
52
53        # When the autotest restarts ui, chrome would issue some Bluetooth
54        # commands which may prevent the system from suspending properly.
55        # Hence, let's stop ui for now.
56        self.host.run_short('stop ui')
57
58        board = self.host.get_board().split(':')[1]
59        logging.info('board: %s', board)
60
61        # TODO(b/152737849): figure out a way to support more boards.
62        self._check_legitimate_board()
63
64        # device is a pure XMLRPC server running as chameleond
65        # on the bluetooth peer. We need to enable Servod.
66        if not device.EnableServod(board):
67            raise error.TestError('Failed to enable Servod.')
68
69        # Start the Servod process on the bluetooth peer.
70        if not device.servod.Start():
71            raise error.TestError('Failed to start Servod on bluetooth peer.')
72
73
74    def _cleanup_servod(self, device):
75        """Perform cleanup for servod.
76
77        @param device: the peer device
78        """
79        if not device.servod.Stop():
80            logging.error('Failed to stop Servod on bluetooth peer.')
81
82        self.host.run_short('start ui')
83
84        logging.info('count_fail_to_sleep: %d', self.count_fail_to_sleep)
85        logging.info('count_fail_to_resume: %d', self.count_fail_to_resume)
86        logging.info('count_system_resume_prematurely: %d',
87                     self.count_system_resume_prematurely)
88        logging.info('count_success: %d', self.count_success)
89
90
91    # ---------------------------------------------------------------
92    # Definitions of test cases
93    # ---------------------------------------------------------------
94
95    @test_case_log
96    def test_case_suspend_power_measurement(self, host, device, max_power_mw,
97                                            suspend_time_secs,
98                                            resume_network_timeout_secs=60):
99        """Test Case: measure the Bluetooth chip power consumption on suspend"""
100
101        def print_debug_count():
102            """Print the debug message about count values."""
103            logging.debug('count_fail_to_sleep: %d', self.count_fail_to_sleep)
104            logging.debug('count_fail_to_resume: %d', self.count_fail_to_resume)
105            logging.debug('count_system_resume_prematurely: %d',
106                          self.count_system_resume_prematurely)
107            logging.debug('count_success: %d', self.count_success)
108
109        def action_suspend():
110            """Calls the host method suspend."""
111            host.suspend(suspend_time=suspend_time_secs,
112                         allow_early_resume=True)
113
114        boot_id = host.get_boot_id()
115        proc = multiprocessing.Process(target=action_suspend)
116        proc.daemon = True
117        start_time = time.time()
118        proc.start()
119
120        # Block waiting until the system has suspended.
121        try:
122            host.test_wait_for_sleep(suspend_time_secs)
123        except Exception as e:
124            logging.error('host.test_wait_for_sleep failed: %s', e)
125            self.count_fail_to_sleep += 1
126            print_debug_count()
127            # Skip this time since the system failed to suspend.
128            proc.join()
129            return
130
131        # Test the Bluetooth chip power consumption.
132        if self.test_power_consumption(device, max_power_mw):
133            self.count_success += 1
134
135        # Block waiting until the system has resumed.
136        try:
137            host.test_wait_for_resume(
138                    boot_id, suspend_time_secs + resume_network_timeout_secs)
139        except Exception as e:
140            logging.error('host.test_wait_for_resume failed: %s', e)
141            self.count_fail_to_resume += 1
142
143        # If the system resumes prematurely, do not conduct the test in
144        # this iteration.
145        actual_suspend_time_secs = time.time() - start_time
146        if actual_suspend_time_secs < suspend_time_secs:
147            logging.error('actual suspension time %f is less than expected %f',
148                          actual_suspend_time_secs, suspend_time_secs)
149            self.count_system_resume_prematurely += 1
150
151        print_debug_count()
152        proc.join()
153
154        if self.count_success == 0:
155            raise error.TestError('System failed to suspend/resume.')
156
157
158    # ---------------------------------------------------------------
159    # Definitions of test wrapper tests and batch wrapper tests.
160    # ---------------------------------------------------------------
161
162
163    @test_wrapper('Power measurement test', devices={'BLUETOOTH_BASE':1})
164    def pw_measurement_suspension_test(self):
165        """power measurement test during system suspension."""
166        device = self.devices['BLUETOOTH_BASE'][0]
167        self._initialize_servod(device)
168        self.test_power_on_adapter()
169        self.test_bluetoothd_running()
170        self.test_case_suspend_power_measurement(self.host, device,
171                                                 self.max_power_mw,
172                                                 self.suspend_time_secs)
173        self._cleanup_servod(device)
174
175
176    @batch_wrapper('Bluetooth Power Measurement Health Tests')
177    def pw_health_batch_run(self, num_iterations=1, test_name=None):
178        """Run bluetooth power measurement health test batch or a specific test.
179
180        @param num_iterations: how many iterations to run
181        @param test_name: specific test to run otherwise None to run the
182                whole batch
183        """
184        self.pw_measurement_suspension_test()
185
186
187    def run_once(self,
188                 host,
189                 num_iterations=1,
190                 args_dict=None,
191                 test_name=None,
192                 max_power_mw=3,
193                 suspend_time_secs=30,
194                 flag='Quick Health'):
195        """Running Bluetooth adapter power consumption autotest during system
196        suspension.
197
198        @param host: the DUT host.
199        @param num_iterations: number of times to perform the tests.
200        @param test_name: the test to run, or None for all tests
201        @param max_power_mw: max power allowed in milli-watt
202        @param suspend_time_secs: the system suspension duration in seconds
203
204        """
205        self.host = host
206        self.max_power_mw = max_power_mw
207        self.suspend_time_secs = suspend_time_secs
208
209        self.quick_test_init(host,
210                             use_btpeer=True,
211                             flag=flag,
212                             args_dict=args_dict)
213        self.pw_health_batch_run(num_iterations, test_name)
214        self.quick_test_cleanup()
215