• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2019 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"""
6This class provides wrapper functions for Bluetooth quick sanity test
7batches or packages
8"""
9
10import functools
11import logging
12import time
13
14from autotest_lib.client.common_lib import error
15from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests
16from autotest_lib.server.cros.multimedia import remote_facade_factory
17
18
19class BluetoothAdapterQuickTests(bluetooth_adapter_tests.BluetoothAdapterTests):
20    """This class provide wrapper function for Bluetooth quick sanity test
21    batches or packages.
22    The Bluetooth quick test infrastructure provides a way to quickly run a set
23    of tests. As for today, auto-test ramp up time per test is about 90-120
24    seconds, where a typical Bluetooth test may take ~30-60 seconds to run.
25
26    The quick test infra, implemented in this class, saves this huge overhead
27    by running only the minimal reset and cleanup operations required between
28    each set of tests (takes a few seconds).
29
30    This class provides wrapper functions to start and end a test, a batch or a
31    package. A batch is defined as a set of tests, preferably with a common
32    subject. A package is a set of batches.
33    This class takes care of tests, batches, and packages test results, and
34    prints out summaries to results. The class also resets and cleans up
35    required states between tests, batches and packages.
36
37    A batch can also run as a separate auto-test. There is a place holder to
38    add a way to run a specific test of a batch autonomously.
39
40    A batch can be implemented by inheriting from this class, and using its
41    wrapper functions. A package can be implemented by inheriting from a set of
42    batches.
43
44    Adding a test to one of the batches is as easy as adding a method to the
45    class of the batch.
46    """
47
48    # Some delay is needed between tests. TODO(yshavit): investigate and remove
49    TEST_SLEEP_SECS = 3
50
51
52    def restart_peers(self):
53        """Restart and clear peer devices"""
54        # Restart the link to device
55        logging.info('Restarting peer devices...')
56
57        # Grab current device list for initialization
58        connected_devices = self.devices
59        self.cleanup(test_state='MID')
60
61        for device_type, device_list in connected_devices.items():
62            for device in device_list:
63                if device is not None:
64                    logging.info('Restarting %s', device_type)
65                    self.get_device(device_type, on_start=False)
66
67
68    def start_peers(self, devices):
69        """Start peer devices"""
70        # Start the link to devices
71        logging.info('Starting peer devices...')
72
73        if self.host.multi_chameleon:
74            self.get_device_rasp(devices)
75        else:
76            for device_type in devices:
77                logging.info('Getting device %s', device_type)
78                self.get_device(device_type)
79
80
81    def _print_delimiter(self):
82        logging.info('=======================================================')
83
84
85    def quick_test_init(self, host, use_chameleon=True, flag='Quick Sanity'):
86        """Inits the test batch"""
87        self.host = host
88        #factory can not be declared as local variable, otherwise
89        #factory._proxy.__del__ will be invoked, which shutdown the xmlrpc
90        # server, which log out the user.
91
92        try:
93            self.factory = remote_facade_factory.RemoteFacadeFactory(host,
94                           disable_arc=True)
95            self.bluetooth_facade = self.factory.create_bluetooth_hid_facade()
96
97        # For b:142276989, catch 'object_path' fault and reboot to prevent
98        # failures from continuing into future tests
99        except Exception, e:
100            if (e.__class__.__name__ == 'Fault' and
101                """object has no attribute 'object_path'""" in str(e)):
102
103                logging.error('Caught b/142276989, rebooting DUT')
104                self.reboot()
105            # Raise the original exception
106            raise
107
108        self.use_chameleon = use_chameleon
109        if self.use_chameleon:
110            self.input_facade = self.factory.create_input_facade()
111            self.check_chameleon()
112
113            # Query connected devices on our chameleon at init time
114            self.available_devices = self.list_devices_available()
115
116            for chameleon in self.host.chameleon_list:
117                chameleon.register_raspPi_log(self.outputdir)
118
119            if self.host.multi_chameleon:
120                self.chameleon_group = dict()
121                # Create copy of chameleon_group
122                self.chameleon_group_copy = dict()
123                self.group_chameleons_type()
124
125        # Clear the active devices for this test
126        self.active_test_devices = {}
127
128        self.enable_disable_debug_log(enable=True)
129
130        # Delete files created in previous run
131        self.host.run('[ ! -d {0} ] || rm -rf {0} || true'.format(
132                                                    self.BTMON_DIR_LOG_PATH))
133        self.start_new_btmon()
134
135        self.flag = flag
136        self.test_iter = None
137
138        self.bat_tests_results = []
139        self.bat_pass_count = 0
140        self.bat_fail_count = 0
141        self.bat_name = None
142        self.bat_iter = None
143
144        self.pkg_tests_results = []
145        self.pkg_pass_count = 0
146        self.pkg_fail_count = 0
147        self.pkg_name = None
148        self.pkg_iter = None
149        self.pkg_is_running = False
150
151
152    @staticmethod
153    def quick_test_test_decorator(test_name, devices={}, flags=['All']):
154        """A decorator providing a wrapper to a quick test.
155           Using the decorator a test method can implement only the core
156           test and let the decorator handle the quick test wrapper methods
157           (test_start and test_end).
158
159           @param test_name: the name of the test to log.
160           @param devices:   list of device names which are going to be used
161                             in the following test.
162           @param flags: list of string to describe who should run the
163                         test. The string could be one of the following:
164                         ['AVL', 'Quick Sanity', 'All'].
165        """
166
167        def decorator(test_method):
168            """A decorator wrapper of the decorated test_method.
169               @param test_method: the test method being decorated.
170               @returns the wrapper of the test method.
171            """
172
173            def _check_runnable(self):
174                """Check if the test could be run"""
175
176                # Check that the test is runnable in current setting
177                if not(self.flag in flags or 'All' in flags):
178                    logging.info('SKIPPING TEST %s', test_name)
179                    logging.info('flag %s not in %s', self.flag, flags)
180                    self._print_delimiter()
181                    return False
182
183                # Check that chameleon has all required devices before running
184                for device_type, number in devices.items():
185                    if self.available_devices.get(device_type, 0) < number:
186                        logging.info('SKIPPING TEST %s', test_name)
187                        logging.info('%s not available', device_type)
188                        self._print_delimiter()
189                        return False
190
191                return True
192
193            @functools.wraps(test_method)
194            def wrapper(self):
195                """A wrapper of the decorated method."""
196                if not _check_runnable(self):
197                    return
198                self.quick_test_test_start(test_name, devices)
199                test_method(self)
200                self.quick_test_test_end()
201            return wrapper
202
203        return decorator
204
205
206    def quick_test_test_start(self, test_name=None, devices={}):
207        """Start a quick test. The method clears and restarts adapter on DUT
208           as well as peer devices. In addition the methods prints test start
209           traces.
210        """
211
212        self.test_name = test_name
213
214        # Reset the adapter
215        self.test_reset_on_adapter()
216        # Initialize bluetooth_adapter_tests class (also clears self.fails)
217        self.initialize()
218        # Start and peer HID devices
219        self.start_peers(devices)
220
221        if test_name is not None:
222            time.sleep(self.TEST_SLEEP_SECS)
223            self._print_delimiter()
224            logging.info('Starting test: %s', test_name)
225            self.log_message('Starting test: %s'% test_name)
226
227    def quick_test_test_end(self):
228        """Log and track the test results"""
229        result_msgs = []
230
231        if self.test_iter is not None:
232            result_msgs += ['Test Iter: ' + str(self.test_iter)]
233
234        if self.bat_iter is not None:
235            result_msgs += ['Batch Iter: ' + str(self.bat_iter)]
236
237        if self.pkg_is_running is True:
238            result_msgs += ['Package iter: ' + str(self.pkg_iter)]
239
240        if self.bat_name is not None:
241            result_msgs += ['Batch Name: ' + self.bat_name]
242
243        if self.test_name is not None:
244            result_msgs += ['Test Name: ' + self.test_name]
245
246        result_msg = ", ".join(result_msgs)
247
248        if not bool(self.fails):
249            result_msg = 'PASSED | ' + result_msg
250            self.bat_pass_count += 1
251            self.pkg_pass_count += 1
252        else:
253            result_msg = 'FAIL   | ' + result_msg
254            self.bat_fail_count += 1
255            self.pkg_fail_count += 1
256
257        logging.info(result_msg)
258        self.log_message(result_msg)
259        self._print_delimiter()
260        self.bat_tests_results.append(result_msg)
261        self.pkg_tests_results.append(result_msg)
262
263        if self.test_name is not None:
264            logging.info('Cleanning up and restarting towards next test...')
265
266        self.bluetooth_facade.stop_discovery()
267
268        # Store a copy of active devices for raspi reset in the final step
269        self.active_test_devices = self.devices
270
271        # Disconnect devices used in the test, and remove the pairing.
272        for device_list in self.devices.values():
273            for device in device_list:
274                if device is not None:
275                    logging.info('Clear device %s', device.name)
276                    self.bluetooth_facade.disconnect_device(device.address)
277                    device_is_paired = self.bluetooth_facade.device_is_paired(
278                            device.address)
279                    if device_is_paired:
280                        self.bluetooth_facade.remove_device_object(
281                                device.address)
282
283        # Repopulate chameleon_group for next tests
284        if self.host.multi_chameleon:
285            # Clear previous tets's leftover entries. Don't delete the
286            # chameleon_group dictionary though, it'll be used as it is.
287            for device_type in self.chameleon_group:
288                if len(self.chameleon_group[device_type]) > 0:
289                    del self.chameleon_group[device_type][:]
290
291            # Repopulate
292            self.group_chameleons_type()
293
294        # Close the connection between peers
295        self.cleanup(test_state='NEW')
296
297
298    @staticmethod
299    def quick_test_batch_decorator(batch_name):
300        """A decorator providing a wrapper to a batch.
301           Using the decorator a test batch method can implement only its
302           core tests invocations and let the decorator handle the wrapper,
303           which is taking care for whether to run a specific test or the
304           batch as a whole and and running the batch in iterations
305
306           @param batch_name: the name of the batch to log
307        """
308
309        def decorator(batch_method):
310            """A decorator wrapper of the decorated test_method.
311               @param test_method: the test method being decorated.
312               @returns the wrapper of the test method.
313            """
314
315            @functools.wraps(batch_method)
316            def wrapper(self, num_iterations=1, test_name=None):
317                """A wrapper of the decorated method.
318                  @param num_iterations: how many interations to run
319                  @param test_name: specifc test to run otherwise None to run
320                                    the whole batch
321                """
322                if test_name is not None:
323                    single_test_method = getattr(self,  test_name)
324                    for iter in xrange(1,num_iterations+1):
325                        self.test_iter = iter
326                        single_test_method()
327
328                    if self.fails:
329                        raise error.TestFail(self.fails)
330                else:
331                    for iter in xrange(1,num_iterations+1):
332                        self.quick_test_batch_start(batch_name, iter)
333                        batch_method(self, num_iterations, test_name)
334                        self.quick_test_batch_end()
335            return wrapper
336
337        return decorator
338
339
340    def quick_test_batch_start(self, bat_name, iteration=1):
341        """Start a test batch. The method clears and set batch variables"""
342        self.bat_tests_results = []
343        self.bat_pass_count = 0
344        self.bat_fail_count = 0
345        self.bat_name = bat_name
346        self.bat_iter = iteration
347
348
349    def quick_test_batch_end(self):
350        """Print results summary of a test batch"""
351        logging.info('%s Test Batch Summary: total pass %d, total fail %d',
352                     self.bat_name, self.bat_pass_count, self.bat_fail_count)
353        for result in self.bat_tests_results:
354            logging.info(result)
355        self._print_delimiter();
356        if self.bat_fail_count > 0:
357            logging.error('===> Test Batch Failed! More than one failure')
358            self._print_delimiter();
359            if self.pkg_is_running is False:
360                raise error.TestFail(self.bat_tests_results)
361        else:
362           logging.info('===> Test Batch Passed! zero failures')
363           self._print_delimiter();
364
365
366    def quick_test_package_start(self, pkg_name):
367        """Start a test package. The method clears and set batch variables"""
368        self.pkg_tests_results = []
369        self.pkg_pass_count = 0
370        self.pkg_fail_count = 0
371        self.pkg_name = pkg_name
372        self.pkg_is_running = True
373
374
375    def quick_test_print_summary(self):
376        """Print results summary of a test package"""
377        logging.info('%s Test Package Summary: total pass %d, total fail %d',
378                     self.pkg_name, self.pkg_pass_count, self.pkg_fail_count)
379        for result in self.pkg_tests_results:
380            logging.info(result)
381        self._print_delimiter();
382
383
384    def quick_test_package_update_iteration(self, iteration):
385        """Update state and print log per package iteration.
386           Must be called to have a proper package test result tracking.
387        """
388        self.pkg_iter = iteration
389        if self.pkg_name is None:
390            logging.error('Error: no quick package is running')
391            raise error.TestFail('Error: no quick package is running')
392        logging.info('Starting %s Test Package iteration %d',
393                     self.pkg_name, iteration)
394
395
396    def quick_test_package_end(self):
397        """Print final result of a test package"""
398        if self.pkg_fail_count > 0:
399            logging.error('===> Test Package Failed! More than one failure')
400            self._print_delimiter();
401            raise error.TestFail(self.bat_tests_results)
402        else:
403           logging.info('===> Test Package Passed! zero failures')
404           self._print_delimiter();
405        self.pkg_is_running = False
406
407
408    def quick_test_cleanup(self):
409        """ Cleanup any state test server and all device"""
410
411        # Clear any raspi devices at very end of test
412        for device_list in self.active_test_devices.values():
413            for device in device_list:
414                if device is not None:
415                    self.clear_raspi_device(device)
416
417        # Reset the adapter
418        self.test_reset_on_adapter()
419        # Initialize bluetooth_adapter_tests class (also clears self.fails)
420        self.initialize()
421