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