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 17 import queue 18 import time 19 20 import acts.base_test 21 import acts.test_utils.wifi.wifi_test_utils as wutils 22 23 from acts import asserts 24 25 WifiChannelUS = wutils.WifiChannelUS 26 WifiEnums = wutils.WifiEnums 27 28 SCAN_EVENT_TAG = "WifiScannerScan" 29 30 class 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 172 class 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