• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3.4
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import queue
18import time
19
20import acts.base_test
21import acts.test_utils.wifi.wifi_test_utils as wutils
22
23from acts import asserts
24
25WifiChannelUS = wutils.WifiChannelUS
26WifiEnums = wutils.WifiEnums
27
28SCAN_EVENT_TAG = "WifiScannerScan"
29
30class WifiScanResultEvents():
31    """This class stores the setting of a scan, parameters generated
32    from starting the scan, and events reported later from the scan
33    for validation.
34
35    Attributes:
36        scan_setting: Setting used to perform the scan.
37        scan_channels: Channels used for scanning.
38        events: A list to store the scan result events.
39    """
40
41    def __init__(self, scan_setting, scan_channels):
42        self.scan_setting = scan_setting
43        self.scan_channels = scan_channels
44        self.results_events = []
45
46    def add_results_event(self, event):
47        self.results_events.append(event)
48
49    def check_interval(self, scan_result, scan_result_next):
50        """Verifies that the time gap between two consecutive results is within
51        expected range.
52
53        Right now it is hard coded to be 20 percent of the interval specified
54        by scan settings. This threshold can be imported from the configuration
55        file in the future if necessary later.
56
57        Note the scan result timestamps are in microseconds, but "periodInMs"
58        in scan settings is in milliseconds.
59
60        Args:
61            scan_result: A dictionary representing a scan result for a BSSID.
62            scan_result_next: A dictionary representing a scan result for a
63                BSSID, whose scan happened after scan_result.
64        """
65        actual_interval = scan_result_next["timestamp"] - scan_result["timestamp"]
66        expected_interval = self.scan_setting['periodInMs'] * 1000
67        delta = abs(actual_interval - expected_interval)
68        margin = expected_interval * 0.20 # 20% of the expected_interval
69        assert delta < margin, ("The difference in time between scan %s and "
70                "%s is %dms, which is out of the expected range %sms") % (
71                    scan_result,
72                    scan_result_next,
73                    delta / 1000,
74                    self.scan_setting['periodInMs']
75                )
76
77    def verify_one_scan_result(self, scan_result):
78        """Verifies the scan result of a single BSSID.
79
80        1. Verifies the frequency of the network is within the range requested
81        in the scan.
82
83        Args:
84            scan_result: A dictionary representing the scan result of a single
85                BSSID.
86        """
87        freq = scan_result["frequency"]
88        assert freq in self.scan_channels, ("Frequency %d of "
89            "result entry %s is out of the expected range %s.") % (
90                freq, scan_result, self.scan_channels)
91        # TODO(angli): add RSSI check.
92
93    def verify_one_scan_result_group(self, batch):
94        """Verifies a group of scan results obtained during one scan.
95
96        1. Verifies the number of BSSIDs in the batch is less than the
97        threshold set by scan settings.
98        2. Verifies each scan result for individual BSSID.
99
100        Args:
101            batch: A list of dictionaries, each dictionary represents a scan
102                result.
103        """
104        scan_results = batch["ScanResults"]
105        actual_num_of_results = len(scan_results)
106        expected_num_of_results = self.scan_setting['numBssidsPerScan']
107        assert actual_num_of_results <= expected_num_of_results, (
108            "Expected no more than %d BSSIDs, got %d.") % (
109                expected_num_of_results,
110                actual_num_of_results
111            )
112        for scan_result in scan_results:
113            self.verify_one_scan_result(scan_result)
114
115    def have_enough_events(self):
116        """Check if there are enough events to properly validate the scan"""
117        return len(self.results_events) >= 2
118
119    def check_scan_results(self):
120        """Validate the reported scan results against the scan settings.
121        Assert if any error detected in the results.
122
123        1. For each scan setting there should be no less than 2 events received.
124        2. For batch scan, the number of buffered results in each event should
125           be exactly what the scan setting specified.
126        3. Each scan result should contain no more BBSIDs than what scan
127           setting specified.
128        4. The frequency reported by each scan result should comply with its
129           scan setting.
130        5. The time gap between two consecutive scan results should be
131           approximately equal to the scan interval specified by the scan
132           setting.
133        """
134        num_of_events = len(self.results_events)
135        assert num_of_events >= 2, (
136                "Expected more than one scan result events, got %d."
137            ) % num_of_events
138        for event_idx in range(num_of_events):
139            batches = self.results_events[event_idx]["data"]["Results"]
140            actual_num_of_batches = len(batches)
141            # For batch scan results.
142            report_type = self.scan_setting['reportEvents']
143            if not (report_type & WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN):
144                # Verifies that the number of buffered results matches the
145                # number defined in scan settings.
146                expected_num_of_batches = self.scan_setting['maxScansToCache']
147                assert actual_num_of_batches <= expected_num_of_batches, (
148                    "Expected to get at most %d batches in event No.%d, got %d.") % (
149                        expected_num_of_batches,
150                        event_idx,
151                        actual_num_of_batches
152                    )
153                # Check the results within each event of batch scan
154                for batch_idx in range(1, actual_num_of_batches):
155                    self.check_interval(
156                        batches[batch_idx-1]["ScanResults"][0],
157                        batches[batch_idx]["ScanResults"][0]
158                    )
159            for batch in batches:
160                self.verify_one_scan_result_group(batch)
161
162            # Check the time gap between the first result of an event and
163            # the last result of its previous event
164            # Skip the very first event.
165            if event_idx >= 1:
166                previous_batches = self.results_events[event_idx-1]["data"]["Results"]
167                self.check_interval(
168                    previous_batches[-1]["ScanResults"][0],
169                    batches[0]["ScanResults"][0]
170                )
171
172class WifiScannerMultiScanTest(acts.base_test.BaseTestClass):
173    """This class is the WiFi Scanner Multi-Scan Test suite.
174    It collects a number of test cases, sets up and executes
175    the tests, and validates the scan results.
176
177    Attributes:
178        tests: A collection of tests to excute.
179        leeway: Scan interval drift time (in seconds).
180        stime_channels: Dwell time plus 2ms.
181        dut: Android device(s).
182        wifi_chs: WiFi channels according to the device model.
183        max_bugreports: Max number of bug reports allowed.
184    """
185
186    def __init__(self, controllers):
187        acts.base_test.BaseTestClass.__init__(self, controllers)
188        # A list of all test cases to be executed in this class.
189        self.tests = ("test_wifi_two_scans_at_same_interval",
190                      "test_wifi_two_scans_at_different_interval",
191                      "test_wifi_scans_24GHz_and_both",
192                      "test_wifi_scans_5GHz_and_both",
193                      "test_wifi_scans_24GHz_5GHz_and_DFS",
194                      "test_wifi_scans_batch_and_24GHz",
195                      "test_wifi_scans_batch_and_5GHz",
196                      "test_wifi_scans_24GHz_5GHz_full_result",)
197        self.leeway = 5 # seconds, for event wait time computation
198        self.stime_channel = 47 #dwell time plus 2ms
199
200    def setup_class(self):
201        self.dut = self.android_devices[0]
202        wutils.wifi_test_device_init(self.dut)
203        asserts.assert_true(self.dut.droid.wifiIsScannerSupported(),
204            "Device %s doesn't support WifiScanner, abort." % self.dut.model)
205
206        """ Setup the required dependencies and fetch the user params from
207        config file.
208        """
209        req_params = ("bssid_2g", "bssid_5g", "bssid_dfs", "max_bugreports")
210        self.wifi_chs = WifiChannelUS(self.dut.model)
211        self.unpack_userparams(req_params)
212
213    def on_fail(self, test_name, begin_time):
214        if self.max_bugreports > 0:
215            self.dut.take_bug_report(test_name, begin_time)
216            self.max_bugreports -= 1
217        self.dut.cat_adb_log(test_name, begin_time)
218
219    """ Helper Functions Begin """
220    def start_scan(self, scan_setting):
221        data = wutils.start_wifi_background_scan(self.dut, scan_setting)
222        idx = data["Index"]
223        # Calculate event wait time from scan setting plus leeway
224        scan_time, scan_channels = wutils.get_scan_time_and_channels(
225                                        self.wifi_chs,
226                                        scan_setting,
227                                        self.stime_channel
228                                    )
229        scan_period = scan_setting['periodInMs']
230        report_type = scan_setting['reportEvents']
231        if report_type & WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN:
232            scan_time += scan_period
233        else:
234            max_scan = scan_setting['maxScansToCache']
235            scan_time += max_scan * scan_period
236        wait_time = scan_time / 1000 + self.leeway
237        return idx, wait_time, scan_channels
238
239    def validate_scan_results(self, scan_results_dict):
240        # Sanity check to make sure the dict is not empty
241        asserts.assert_true(scan_results_dict, "Scan result dict is empty.")
242        for scan_result_obj in scan_results_dict.values():
243            # Validate the results received for each scan setting
244            scan_result_obj.check_scan_results()
245
246    def wait_for_scan_events(self, wait_time_list, scan_results_dict):
247        """Poll for WifiScanner events and record them"""
248
249        # Compute the event wait time
250        event_wait_time = min(wait_time_list)
251
252        # Compute the maximum test time that guarantee that even the scan
253        # which requires the most wait time will receive at least two
254        # results.
255        max_wait_time = max(wait_time_list)
256        max_end_time = time.monotonic() + max_wait_time
257        self.log.debug("Event wait time {} seconds".format(event_wait_time))
258
259        try:
260            # Wait for scan results on all the caller specified bands
261            event_name = SCAN_EVENT_TAG
262            while True:
263                self.log.debug("Waiting for events '{}' for up to {} seconds".
264                               format(event_name, event_wait_time))
265                events = self.dut.ed.pop_events(event_name, event_wait_time)
266                for event in events:
267                    self.log.debug("Event received: {}".format(event))
268                    # Event name is the key to the scan results dictionary
269                    actual_event_name = event["name"]
270                    asserts.assert_true(actual_event_name in scan_results_dict,
271                        ("Expected one of these event names: %s, got '%s'."
272                        ) % (scan_results_dict.keys(), actual_event_name))
273
274                    # TODO validate full result callbacks also
275                    if event["name"].endswith("onResults"):
276                        # Append the event
277                        scan_results_dict[actual_event_name].add_results_event(event)
278
279                # If we time out then stop waiting for events.
280                if time.monotonic() >= max_end_time:
281                    break
282                # If enough scan results have been returned to validate the
283                # results then break early.
284                have_enough_events = True
285                for key in scan_results_dict:
286                    if not scan_results_dict[key].have_enough_events():
287                        have_enough_events = False
288                if have_enough_events:
289                  break
290        except queue.Empty:
291            asserts.fail("Event did not trigger for {} in {} seconds".
292                      format(event_name, event_wait_time))
293
294
295
296    def scan_and_validate_results(self, scan_settings):
297        """Perform WifiScanner scans and check the scan results
298
299        Procedures:
300          * Start scans for each caller specified setting
301          * Wait for at least two results for each scan
302          * Check the results received for each scan
303        """
304        # Awlays get a clean start
305        self.dut.ed.clear_all_events()
306
307        # Start scanning with the caller specified settings and
308        # compute parameters for receiving events
309        idx_list = []
310        wait_time_list = []
311        scan_results_dict = {}
312
313        try:
314            for scan_setting in scan_settings:
315                self.log.debug("Scan setting: band {}, interval {}, reportEvents "
316                               "{}, numBssidsPerScan {}".format(
317                                    scan_setting["band"],
318                                    scan_setting["periodInMs"],
319                                    scan_setting["reportEvents"],
320                                    scan_setting["numBssidsPerScan"]
321                                ))
322                idx, wait_time, scan_chan = self.start_scan(scan_setting)
323                self.log.debug(("Scan started for band {}: idx {}, wait_time {} s,"
324                                " scan_channels {}").format(
325                                scan_setting["band"], idx, wait_time, scan_chan))
326                idx_list.append(idx)
327                wait_time_list.append(wait_time)
328
329                report_type = scan_setting['reportEvents']
330                scan_results_events = WifiScanResultEvents(scan_setting, scan_chan)
331                scan_results_dict["{}{}onResults".format(SCAN_EVENT_TAG, idx)] = scan_results_events
332                if (scan_setting['reportEvents'] & WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT):
333                    scan_results_dict["{}{}onFullResult".format(SCAN_EVENT_TAG, idx)] = scan_results_events
334
335            self.wait_for_scan_events(wait_time_list, scan_results_dict)
336
337            # Validate the scan results
338            self.validate_scan_results(scan_results_dict)
339
340        finally:
341            # Tear down and clean up
342            for idx in idx_list:
343                self.dut.droid.wifiScannerStopBackgroundScan(idx)
344            self.dut.ed.clear_all_events()
345    """ Helper Functions End """
346
347
348    """ Tests Begin """
349    def test_wifi_two_scans_at_same_interval(self):
350        """Perform two WifiScanner background scans, one at 2.4GHz and the other
351        at 5GHz, the same interval and number of BSSIDs per scan.
352
353        Initial Conditions:
354          * Set multiple APs broadcasting 2.4GHz and 5GHz.
355
356        Expected Results:
357          * DUT reports success for starting both scans
358          * Scan results for each callback contains only the results on the
359            frequency scanned
360          * Wait for at least two scan results and confirm that separation
361            between them approximately equals to the expected interval
362          * Number of BSSIDs doesn't exceed
363        """
364        scan_settings = [{ "band": WifiEnums.WIFI_BAND_24_GHZ,
365                           "periodInMs": 10000, # ms
366                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
367                           "numBssidsPerScan": 24},
368                         { "band": WifiEnums.WIFI_BAND_5_GHZ,
369                           "periodInMs": 10000, # ms
370                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
371                           "numBssidsPerScan": 24}]
372
373        self.scan_and_validate_results(scan_settings)
374
375    def test_wifi_two_scans_at_different_interval(self):
376        """Perform two WifiScanner background scans, one at 2.4GHz and the other
377        at 5GHz, different interval and number of BSSIDs per scan.
378
379        Initial Conditions:
380          * Set multiple APs broadcasting 2.4GHz and 5GHz.
381
382        Expected Results:
383          * DUT reports success for starting both scans
384          * Scan results for each callback contains only the results on the
385            frequency scanned
386          * Wait for at least two scan results and confirm that separation
387            between them approximately equals to the expected interval
388          * Number of BSSIDs doesn't exceed
389        """
390        scan_settings = [{ "band": WifiEnums.WIFI_BAND_24_GHZ,
391                           "periodInMs": 10000, # ms
392                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
393                           "numBssidsPerScan": 20},
394                         { "band": WifiEnums.WIFI_BAND_5_GHZ,
395                           "periodInMs": 20000, # ms
396                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
397                           "numBssidsPerScan": 24}]
398
399        self.scan_and_validate_results(scan_settings)
400
401    def test_wifi_scans_24GHz_and_both(self):
402        """Perform two WifiScanner background scans, one at 2.4GHz and
403           the other at both 2.4GHz and 5GHz
404
405        Initial Conditions:
406          * Set multiple APs broadcasting 2.4GHz and 5GHz.
407
408        Expected Results:
409          * DUT reports success for starting both scans
410          * Scan results for each callback contains only the results on the
411            frequency scanned
412          * Wait for at least two scan results and confirm that separation
413            between them approximately equals to the expected interval
414          * Number of BSSIDs doesn't exceed
415        """
416        scan_settings = [{ "band": WifiEnums.WIFI_BAND_BOTH,
417                           "periodInMs": 10000, # ms
418                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
419                           "numBssidsPerScan": 24},
420                         { "band": WifiEnums.WIFI_BAND_24_GHZ,
421                           "periodInMs": 10000, # ms
422                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
423                           "numBssidsPerScan": 24}]
424
425        self.scan_and_validate_results(scan_settings)
426
427    def test_wifi_scans_5GHz_and_both(self):
428        """Perform two WifiScanner scans, one at 5GHz and the other at both
429           2.4GHz and 5GHz
430
431        Initial Conditions:
432          * Set multiple APs broadcasting 2.4GHz and 5GHz.
433
434        Expected Results:
435          * DUT reports success for starting both scans
436          * Scan results for each callback contains only the results on the
437            frequency scanned
438          * Wait for at least two scan results and confirm that separation
439            between them approximately equals to the expected interval
440          * Number of BSSIDs doesn't exceed
441        """
442        scan_settings = [{ "band": WifiEnums.WIFI_BAND_BOTH,
443                           "periodInMs": 10000, # ms
444                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
445                           "numBssidsPerScan": 24},
446                         { "band": WifiEnums.WIFI_BAND_5_GHZ,
447                           "periodInMs": 10000, # ms
448                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
449                           "numBssidsPerScan": 24}]
450
451        self.scan_and_validate_results(scan_settings)
452
453    def test_wifi_scans_24GHz_5GHz_and_DFS(self):
454        """Perform three WifiScanner scans, one at 5GHz, one at 2.4GHz and the
455        other at just 5GHz DFS channels
456
457        Initial Conditions:
458          * Set multiple APs broadcasting 2.4GHz and 5GHz.
459
460        Expected Results:
461          * DUT reports success for starting both scans
462          * Scan results for each callback contains only the results on the
463            frequency scanned
464          * Wait for at least two scan results and confirm that separation
465            between them approximately equals to the expected interval
466          * Number of BSSIDs doesn't exceed
467        """
468        scan_settings = [{ "band": WifiEnums.WIFI_BAND_5_GHZ_DFS_ONLY,
469                           "periodInMs": 20000, # ms
470                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
471                           "numBssidsPerScan": 24},
472                         { "band": WifiEnums.WIFI_BAND_5_GHZ,
473                           "periodInMs": 20000, # ms
474                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
475                           "numBssidsPerScan": 24},
476                         { "band": WifiEnums.WIFI_BAND_24_GHZ,
477                           "periodInMs": 40000, # ms
478                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
479                           "numBssidsPerScan": 24}]
480
481        self.scan_and_validate_results(scan_settings)
482
483    def test_wifi_scans_batch_and_24GHz(self):
484        """Perform two WifiScanner background scans, one in batch mode for both
485        bands and the other in periodic mode at 2.4GHz
486
487        Initial Conditions:
488          * Set multiple APs broadcasting 2.4GHz and 5GHz.
489
490        Expected Results:
491          * DUT reports success for starting both scans
492          * Scan results for each callback contains only the results on the
493            frequency scanned
494          * Wait for at least two scan results and confirm that separation
495            between them approximately equals to the expected interval
496          * Number of results in batch mode should match the setting
497          * Number of BSSIDs doesn't exceed
498        """
499        scan_settings = [{ "band": WifiEnums.WIFI_BAND_BOTH,
500                           "periodInMs": 10000, # ms
501                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL,
502                           "numBssidsPerScan": 24,
503                           "maxScansToCache": 2},
504                         { "band": WifiEnums.WIFI_BAND_24_GHZ,
505                           "periodInMs": 10000, # ms
506                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
507                           "numBssidsPerScan": 24}]
508
509        self.scan_and_validate_results(scan_settings)
510
511    def test_wifi_scans_batch_and_5GHz(self):
512        """Perform two WifiScanner background scans, one in batch mode for both
513        bands and the other in periodic mode at 5GHz
514
515        Initial Conditions:
516          * Set multiple APs broadcasting 2.4GHz and 5GHz.
517
518        Expected Results:
519          * DUT reports success for starting both scans
520          * Scan results for each callback contains only the results on the
521            frequency scanned
522          * Wait for at least two scan results and confirm that separation
523            between them approximately equals to the expected interval
524          * Number of results in batch mode should match the setting
525          * Number of BSSIDs doesn't exceed
526        """
527        scan_settings = [{ "band": WifiEnums.WIFI_BAND_BOTH,
528                           "periodInMs": 10000, # ms
529                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL,
530                           "numBssidsPerScan": 24,
531                           "maxScansToCache": 2},
532                         { "band": WifiEnums.WIFI_BAND_5_GHZ,
533                           "periodInMs": 10000, # ms
534                           "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
535                           "numBssidsPerScan": 24}]
536
537        self.scan_and_validate_results(scan_settings)
538
539    def test_wifi_scans_24GHz_5GHz_full_result(self):
540        """Perform two WifiScanner background scans, one at 2.4GHz and
541           the other at 5GHz. Report full scan results.
542
543        Initial Conditions:
544          * Set multiple APs broadcasting 2.4GHz and 5GHz.
545
546        Expected Results:
547          * DUT reports success for starting both scans
548          * Scan results for each callback contains only the results on the
549            frequency scanned
550          * Wait for at least two scan results and confirm that separation
551            between them approximately equals to the expected interval
552          * Number of BSSIDs doesn't exceed
553        """
554        scan_settings = [{ "band": WifiEnums.WIFI_BAND_24_GHZ,
555                           "periodInMs": 10000, # ms
556                           "reportEvents": WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT
557                           | WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
558                           "numBssidsPerScan": 24},
559                         { "band": WifiEnums.WIFI_BAND_5_GHZ,
560                           "periodInMs": 10000, # ms
561                           "reportEvents": WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT
562                           | WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
563                           "numBssidsPerScan": 24}]
564
565        self.scan_and_validate_results(scan_settings)
566
567    """ Tests End """
568