xref: /aosp_15_r20/external/autotest/server/cros/bluetooth/bluetooth_dbus_api_tests.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2020 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"""Bluetooth DBus API tests."""
7
8from __future__ import absolute_import
9
10import logging
11
12import common
13from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests
14
15# Assigning local names for some frequently used long method names.
16method_name = bluetooth_adapter_tests.method_name
17_test_retry_and_log = bluetooth_adapter_tests.test_retry_and_log
18
19DEFAULT_START_DELAY_SECS = 2
20DEFAULT_HOLD_INTERVAL = 10
21DEFAULT_HOLD_TIMEOUT = 60
22
23# String representation of DBus exceptions
24DBUS_ERRORS  = {
25    'InProgress' : 'org.bluez.Error.InProgress: Operation already in progress',
26    'NotReady' : 'org.bluez.Error.NotReady: Resource Not Ready',
27    'Failed': {
28        'discovery_start' : 'org.bluez.Error.Failed: No discovery started',
29        'discovery_unpause' : 'org.bluez.Error.Failed: Discovery not paused'
30              }
31               }
32
33
34class BluetoothDBusAPITests(bluetooth_adapter_tests.BluetoothAdapterTests):
35    """Bluetooth DBus API Test
36
37       These test verifies return values and functionality of various Bluetooth
38       DBus APIs. It tests both success and failures cases of each API. It
39       checks the following
40       - Expected return value
41       - Expected exceptions for negative cases
42       - Expected change in Dbus variables
43       - TODO Expected change in (hci) state of the adapter
44    """
45
46    def _reset_state(self):
47        """ Reset adapter to a known state.
48        These tests changes adapter state. This function resets the adapter
49        to known state
50
51        @returns True if reset was successful False otherwise
52
53        """
54        logging.debug("resetting state of the adapter")
55        power_off = self._wait_till_power_off()
56        power_on = self._wait_till_power_on()
57        not_discovering = self._wait_till_discovery_stops()
58        reset_results = {'power_off' : power_off,
59                         'power_on' : power_on,
60                         'not_discovering' : not_discovering}
61        if not all(reset_results.values()):
62            logging.error('_reset_state failed %s',reset_results)
63            return False
64        else:
65            return True
66
67    def _compare_error(self, actual, expected):
68        """ Helper function to compare error and log. """
69        if expected in actual:
70            return True
71        else:
72            logging.debug("Expected error is %s Actual error is %s",expected,
73                          actual)
74            return False
75
76    def _get_hci_state(self, msg=''):
77        """ get state of bluetooth controller. """
78        hci_state = self.log_flags(msg, self.get_dev_info()[3])
79        logging.debug("hci_state is %s", hci_state)
80        return hci_state
81
82    def _wait_till_hci_state_inquiry(self):
83        """ Wait till adapter is in INQUIRY state.
84
85        @return: True if adapter does INQUIRY before timeout, False otherwise
86        """
87        return self._wait_for_condition(
88            lambda: 'INQUIRY' in self._get_hci_state('Expecting INQUIRY'),
89            method_name(),
90            start_delay = DEFAULT_START_DELAY_SECS)
91
92    def _wait_till_hci_state_no_inquiry_holds(self):
93        """ Wait till adapter does not enter INQUIRY for a period of time
94
95        @return : True if adapter is not in INQUIRY for a period of time before
96                  timeout. Otherwise False.
97        """
98        return self._wait_till_condition_holds(
99            lambda: 'INQUIRY' not in self._get_hci_state('Expecting NOINQUIRY'),
100            method_name(),
101            hold_interval = DEFAULT_HOLD_INTERVAL,
102            timeout = DEFAULT_HOLD_TIMEOUT,
103            start_delay = DEFAULT_START_DELAY_SECS)
104
105
106
107    def _wait_till_discovery_stops(self, stop_discovery=True):
108        """stop discovery if specified and wait for discovery to stop
109
110        @params: stop_discovery: Specifies whether stop_discovery should be
111                 executed
112        @returns: True if discovery is stopped
113        """
114        if stop_discovery:
115            self.bluetooth_facade.stop_discovery()
116        is_not_discovering = self._wait_for_condition(
117            lambda: not self.bluetooth_facade.is_discovering(),
118            method_name())
119        return is_not_discovering
120
121    def _wait_till_discovery_starts(self, start_discovery=True):
122        """start discovery if specified and wait for discovery to start
123
124        @params: start_discovery: Specifies whether start_discovery should be
125                 executed
126        @returns: True if discovery is started
127        """
128
129        if start_discovery:
130            self.bluetooth_facade.start_discovery()
131        is_discovering = self._wait_for_condition(
132            self.bluetooth_facade.is_discovering, method_name())
133        return is_discovering
134
135    def _wait_till_power_off(self):
136        """power off the adapter and wait for it to be powered off
137
138        @returns: True if adapter can be powered off
139        """
140
141        power_off = self.bluetooth_facade.set_powered(False)
142        is_powered_off = self._wait_for_condition(
143                lambda: not self.bluetooth_facade.is_powered_on(),
144                method_name())
145        return is_powered_off
146
147    def _wait_till_power_on(self):
148        """power on the adapter and wait for it to be powered on
149
150        @returns: True if adapter can be powered on
151        """
152        power_on = self.bluetooth_facade.set_powered(True)
153        is_powered_on = self._wait_for_condition(
154            self.bluetooth_facade.is_powered_on, method_name())
155        return is_powered_on
156
157
158########################################################################
159# dbus call : start_discovery
160#
161#####################################################
162# Positive cases
163# Case 1
164# preconditions: Adapter powered on AND
165#                Currently not discovering
166# result: Success
167######################################################
168# Negative cases
169#
170# Case 1
171# preconditions: Adapter powered off
172# result: Failure
173# error : NotReady
174#
175# Case 2
176# precondition: Adapter power on AND
177#               Currently discovering
178# result: Failure
179# error: Inprogress
180#########################################################################
181
182    @_test_retry_and_log(False)
183    def test_dbus_start_discovery_success(self):
184        """ Test success case of start_discovery call. """
185        reset = self._reset_state()
186        is_power_on = self._wait_till_power_on()
187        is_not_discovering = self._wait_till_discovery_stops()
188
189        start_discovery, error =  self.bluetooth_facade.start_discovery()
190
191        is_discovering = self._wait_till_discovery_starts(start_discovery=False)
192        inquiry_state = self._wait_till_hci_state_inquiry()
193
194        self.results = {'reset' : reset,
195                        'is_power_on' : is_power_on,
196                        'is_not_discovering': is_not_discovering,
197                        'start_discovery' : start_discovery,
198                        'is_discovering': is_discovering,
199                        'inquiry_state' : inquiry_state
200                        }
201        return all(self.results.values())
202
203    @_test_retry_and_log(False)
204    def test_dbus_start_discovery_fail_discovery_in_progress(self):
205        """ Test Failure case of start_discovery call.
206
207        start discovery when discovery is in progress and confirm it fails with
208        'org.bluez.Error.InProgress: Operation already in progress'.
209        """
210        reset = self._reset_state()
211        is_discovering = self._wait_till_discovery_starts()
212
213        start_discovery, error =  self.bluetooth_facade.start_discovery()
214
215
216        self.results = {'reset' : reset,
217                        'is_discovering' : is_discovering,
218                        'start_discovery_failed' : not start_discovery,
219                        'error_matches' : self._compare_error(error,
220                                                    DBUS_ERRORS['InProgress'])
221        }
222        return all(self.results.values())
223
224    @_test_retry_and_log(False)
225    def test_dbus_start_discovery_fail_power_off(self):
226        """ Test Failure case of start_discovery call.
227
228        start discovery when adapter is turned off and confirm it fails with
229        'NotReady' : 'org.bluez.Error.NotReady: Resource Not Ready'.
230        """
231        reset = self._reset_state()
232        is_power_off = self._wait_till_power_off()
233
234        start_discovery, error =  self.bluetooth_facade.start_discovery()
235
236        is_power_on = self._wait_till_power_on()
237        self.results = {'reset' : reset,
238                        'power_off' : is_power_off,
239                        'start_discovery_failed' : not start_discovery,
240                        'error_matches' : self._compare_error(error,
241                                                    DBUS_ERRORS['NotReady']),
242                        'power_on' : is_power_on}
243        return all(self.results.values())
244
245
246########################################################################
247# dbus call : stop_discovery
248#
249#####################################################
250# Positive cases
251# Case 1
252# preconditions: Adapter powered on AND
253#                Currently discovering
254# result: Success
255#####################################################
256# Negative cases
257#
258# Case 1
259# preconditions: Adapter powered off
260# result: Failure
261# error : NotReady
262#
263# Case 2
264# precondition: Adapter power on AND
265#               Currently not discovering
266# result: Failure
267# error: Failed
268#
269#TODO
270#Case 3  org.bluez.Error.NotAuthorized
271#########################################################################
272
273    @_test_retry_and_log(False)
274    def test_dbus_stop_discovery_success(self):
275        """ Test success case of stop_discovery call. """
276        reset = self._reset_state()
277        is_power_on = self._wait_till_power_on()
278        is_discovering = self._wait_till_discovery_starts()
279
280        stop_discovery, error =  self.bluetooth_facade.stop_discovery()
281        is_not_discovering = self._wait_till_discovery_stops(
282            stop_discovery=False)
283        self._wait_till_hci_state_no_inquiry_holds()
284        self.results = {'reset' : reset,
285                        'is_power_on' : is_power_on,
286                        'is_discovering': is_discovering,
287                        'stop_discovery' : stop_discovery,
288                        'is_not_discovering' : is_not_discovering}
289        return all(self.results.values())
290
291    @_test_retry_and_log(False)
292    def test_dbus_stop_discovery_fail_discovery_not_in_progress(self):
293        """ Test Failure case of stop_discovery call.
294
295        stop discovery when discovery is not in progress and confirm it fails
296        with 'org.bluez.Error.Failed: No discovery started'.
297        """
298        reset = self._reset_state()
299        is_not_discovering = self._wait_till_discovery_stops()
300
301        stop_discovery, error =  self.bluetooth_facade.stop_discovery()
302
303        still_not_discovering = self._wait_till_discovery_stops(
304            stop_discovery=False)
305
306        self.results = {
307            'reset' : reset,
308            'is_not_discovering' : is_not_discovering,
309            'stop_discovery_failed' : not stop_discovery,
310            'error_matches' : self._compare_error(error,
311                                DBUS_ERRORS['Failed']['discovery_start']),
312            'still_not_discovering': still_not_discovering}
313        return all(self.results.values())
314
315    @_test_retry_and_log(False)
316    def test_dbus_stop_discovery_fail_power_off(self):
317        """ Test Failure case of stop_discovery call.
318
319        stop discovery when adapter is turned off and confirm it fails with
320        'NotReady' : 'org.bluez.Error.NotReady: Resource Not Ready'.
321        """
322        reset = self._reset_state()
323        is_power_off = self._wait_till_power_off()
324
325        stop_discovery, error =  self.bluetooth_facade.stop_discovery()
326
327        is_power_on = self._wait_till_power_on()
328        self.results = {'reset' : reset,
329                        'is_power_off' : is_power_off,
330                        'stop_discovery_failed' : not stop_discovery,
331                        'error_matches' : self._compare_error(error,
332                                                    DBUS_ERRORS['NotReady']),
333                        'is_power_on' : is_power_on}
334        return all(self.results.values())
335
336
337########################################################################
338# dbus call: get_suppported_capabilities
339# arguments: None
340# returns : The dictionary is following the format
341#           {capability : value}, where:
342#
343#           string capability:  The supported capability under
344#                       discussion.
345#           variant value:      A more detailed description of
346#                       the capability.
347#####################################################
348# Positive cases
349# Case 1
350# Precondition: Adapter Powered on
351# results: Result dictionary returned
352#
353# Case 2
354# Precondition: Adapter Powered Off
355# result : Result dictionary returned
356################################################################################
357
358    @_test_retry_and_log(False)
359    def test_dbus_get_supported_capabilities_success(self):
360        """ Test success case of get_supported_capabilities call. """
361        reset = self._reset_state()
362        is_power_on = self._wait_till_power_on()
363
364        capabilities, error = self.bluetooth_facade.get_supported_capabilities()
365        logging.debug('supported capabilities is %s', capabilities)
366
367        self.results = {'reset' : reset,
368                        'is_power_on' : is_power_on,
369                        'get_supported_capabilities': error is None
370                        }
371        return all(self.results.values())
372
373    @_test_retry_and_log(False)
374    def test_dbus_get_supported_capabilities_success_power_off(self):
375        """ Test success case of get_supported_capabilities call.
376        Call get_supported_capabilities call with adapter powered off and
377        confirm that it succeeds
378        """
379
380        reset = self._reset_state()
381        is_power_off = self._wait_till_power_off()
382
383        capabilities, error = self.bluetooth_facade.get_supported_capabilities()
384        logging.debug('supported capabilities is %s', capabilities)
385
386        self.results = {'reset' : reset,
387                        'is_power_off' : is_power_off,
388                        'get_supported_capabilities': error is None,
389                        }
390        return all(self.results.values())
391