1#!/usr/bin/env python3.4 2# 3# Copyright 2018 - 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 pprint 18import queue 19import threading 20import time 21 22import acts.base_test 23import acts_contrib.test_utils.wifi.wifi_test_utils as wutils 24import acts_contrib.test_utils.tel.tel_test_utils as tutils 25 26from acts import asserts 27from acts import signals 28from acts import utils 29from acts.test_decorators import test_tracker_info 30from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth 31from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth 32from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest 33WifiEnums = wutils.WifiEnums 34 35WAIT_FOR_AUTO_CONNECT = 40 36WAIT_BEFORE_CONNECTION = 30 37 38TIMEOUT = 5 39PING_ADDR = 'www.google.com' 40 41class WifiStressTest(WifiBaseTest): 42 """WiFi Stress test class. 43 44 Test Bed Requirement: 45 * Two Android device 46 * Several Wi-Fi networks visible to the device, including an open Wi-Fi 47 network. 48 """ 49 def __init__(self, configs): 50 super().__init__(configs) 51 self.enable_packet_log = True 52 53 def setup_class(self): 54 super().setup_class() 55 56 self.dut = self.android_devices[0] 57 # Note that test_stress_softAP_startup_and_stop_5g will always fail 58 # when testing with a single device. 59 if len(self.android_devices) > 1: 60 self.dut_client = self.android_devices[1] 61 else: 62 self.dut_client = None 63 wutils.wifi_test_device_init(self.dut) 64 req_params = [] 65 opt_param = [ 66 "open_network", "reference_networks", "iperf_server_address", 67 "stress_count", "stress_hours", "attn_vals", "pno_interval", 68 "iperf_server_port"] 69 self.unpack_userparams( 70 req_param_names=req_params, opt_param_names=opt_param) 71 72 if "AccessPoint" in self.user_params: 73 self.legacy_configure_ap_and_start(ap_count=2) 74 elif "OpenWrtAP" in self.user_params: 75 self.configure_openwrt_ap_and_start(open_network=True, 76 wpa_network=True, 77 ap_count=2) 78 asserts.assert_true( 79 len(self.reference_networks) > 0, 80 "Need at least one reference network with psk.") 81 self.wpa_2g = self.reference_networks[0]["2g"] 82 self.wpa_5g = self.reference_networks[0]["5g"] 83 self.open_2g = self.open_network[0]["2g"] 84 self.open_5g = self.open_network[0]["5g"] 85 self.networks = [self.wpa_2g, self.wpa_5g, self.open_2g, self.open_5g] 86 87 def setup_test(self): 88 super().setup_test() 89 self.dut.droid.wakeLockAcquireBright() 90 self.dut.droid.wakeUpNow() 91 92 def teardown_test(self): 93 super().teardown_test() 94 if self.dut.droid.wifiIsApEnabled(): 95 wutils.stop_wifi_tethering(self.dut) 96 self.dut.droid.wakeLockRelease() 97 self.dut.droid.goToSleepNow() 98 wutils.reset_wifi(self.dut) 99 100 def teardown_class(self): 101 wutils.reset_wifi(self.dut) 102 if "AccessPoint" in self.user_params: 103 del self.user_params["reference_networks"] 104 del self.user_params["open_network"] 105 106 """Helper Functions""" 107 108 def scan_and_connect_by_ssid(self, ad, network): 109 """Scan for network and connect using network information. 110 111 Args: 112 network: A dictionary representing the network to connect to. 113 114 """ 115 ssid = network[WifiEnums.SSID_KEY] 116 wutils.start_wifi_connection_scan_and_ensure_network_found(ad, ssid) 117 wutils.wifi_connect(ad, network, num_of_tries=3) 118 119 def scan_and_connect_by_id(self, network, net_id): 120 """Scan for network and connect using network id. 121 122 Args: 123 net_id: Integer specifying the network id of the network. 124 125 """ 126 ssid = network[WifiEnums.SSID_KEY] 127 wutils.start_wifi_connection_scan_and_ensure_network_found(self.dut, 128 ssid) 129 wutils.wifi_connect_by_id(self.dut, net_id) 130 131 def run_ping(self, sec): 132 """Run ping for given number of seconds. 133 134 Args: 135 sec: Time in seconds to run teh ping traffic. 136 137 """ 138 self.log.info("Running ping for %d seconds" % sec) 139 result = self.dut.adb.shell("ping -w %d %s" %(sec, PING_ADDR), 140 timeout=sec+1) 141 self.log.debug("Ping Result = %s" % result) 142 if "100% packet loss" in result: 143 raise signals.TestFailure("100% packet loss during ping") 144 145 def start_youtube_video(self, url=None, secs=60): 146 """Start a youtube video and check if it's playing. 147 148 Args: 149 url: The URL of the youtube video to play. 150 secs: Time to play video in seconds. 151 152 """ 153 ad = self.dut 154 ad.log.info("Start a youtube video") 155 ad.ensure_screen_on() 156 video_played = False 157 for count in range(2): 158 ad.unlock_screen() 159 ad.adb.shell('am start -a android.intent.action.VIEW -d "%s"' % url) 160 if tutils.wait_for_state(ad.droid.audioIsMusicActive, True, 15, 1): 161 ad.log.info("Started a video in youtube.") 162 # Play video for given seconds. 163 time.sleep(secs) 164 video_played = True 165 break 166 if not video_played: 167 raise signals.TestFailure("Youtube video did not start. Current WiFi " 168 "state is %d" % self.dut.droid.wifiCheckState()) 169 170 def add_networks(self, ad, networks): 171 """Add Wi-Fi networks to an Android device and verify the networks were 172 added correctly. 173 174 Args: 175 ad: the AndroidDevice object to add networks to. 176 networks: a list of dicts, each dict represents a Wi-Fi network. 177 """ 178 for network in networks: 179 ret = ad.droid.wifiAddNetwork(network) 180 asserts.assert_true(ret != -1, "Failed to add network %s" % 181 network) 182 ad.droid.wifiEnableNetwork(ret, 0) 183 configured_networks = ad.droid.wifiGetConfiguredNetworks() 184 self.log.debug("Configured networks: %s", configured_networks) 185 186 def connect_and_verify_connected_ssid(self, expected_con, is_pno=False): 187 """Start a scan to get the DUT connected to an AP and verify the DUT 188 is connected to the correct SSID. 189 190 Args: 191 expected_con: The expected info of the network to we expect the DUT 192 to roam to. 193 """ 194 connection_info = self.dut.droid.wifiGetConnectionInfo() 195 self.log.info("Triggering network selection from %s to %s", 196 connection_info[WifiEnums.SSID_KEY], 197 expected_con[WifiEnums.SSID_KEY]) 198 self.attenuators[0].set_atten(0) 199 if is_pno: 200 self.log.info("Wait %ss for PNO to trigger.", self.pno_interval) 201 time.sleep(self.pno_interval) 202 else: 203 # force start a single scan so we don't have to wait for the scheduled scan. 204 wutils.start_wifi_connection_scan_and_return_status(self.dut) 205 self.log.info("Wait 60s for network selection.") 206 time.sleep(60) 207 try: 208 self.log.info("Connected to %s network after network selection" 209 % self.dut.droid.wifiGetConnectionInfo()) 210 expected_ssid = expected_con[WifiEnums.SSID_KEY] 211 verify_con = {WifiEnums.SSID_KEY: expected_ssid} 212 wutils.verify_wifi_connection_info(self.dut, verify_con) 213 self.log.info("Connected to %s successfully after network selection", 214 expected_ssid) 215 finally: 216 pass 217 218 def run_long_traffic(self, sec, args, q): 219 try: 220 # Start IPerf traffic 221 self.log.info("Running iperf client {}".format(args)) 222 result, data = self.dut.run_iperf_client(self.iperf_server_address, 223 args, timeout=sec+1) 224 if not result: 225 self.log.debug("Error occurred in iPerf traffic.") 226 self.run_ping(sec) 227 q.put(True) 228 except: 229 q.put(False) 230 231 """Tests""" 232 233 @test_tracker_info(uuid="cd0016c6-58cf-4361-b551-821c0b8d2554") 234 def test_stress_toggle_wifi_state(self): 235 """Toggle WiFi state ON and OFF for N times.""" 236 for count in range(self.stress_count): 237 """Test toggling wifi""" 238 try: 239 self.log.debug("Going from on to off.") 240 wutils.wifi_toggle_state(self.dut, False) 241 self.log.debug("Going from off to on.") 242 startTime = time.time() 243 wutils.wifi_toggle_state(self.dut, True) 244 startup_time = time.time() - startTime 245 self.log.debug("WiFi was enabled on the device in %s s." % 246 startup_time) 247 except: 248 signals.TestFailure(details="", extras={"Iterations":"%d" % 249 self.stress_count, "Pass":"%d" %count}) 250 raise signals.TestPass(details="", extras={"Iterations":"%d" % 251 self.stress_count, "Pass":"%d" %(count+1)}) 252 253 @test_tracker_info(uuid="4e591cec-9251-4d52-bc6e-6621507524dc") 254 def test_stress_toggle_wifi_state_bluetooth_on(self): 255 """Toggle WiFi state ON and OFF for N times when bluetooth ON.""" 256 enable_bluetooth(self.dut.droid, self.dut.ed) 257 for count in range(self.stress_count): 258 """Test toggling wifi""" 259 try: 260 self.log.debug("Going from on to off.") 261 wutils.wifi_toggle_state(self.dut, False) 262 self.log.debug("Going from off to on.") 263 startTime = time.time() 264 wutils.wifi_toggle_state(self.dut, True) 265 startup_time = time.time() - startTime 266 self.log.debug("WiFi was enabled on the device in %s s." % 267 startup_time) 268 except: 269 signals.TestFailure(details="", extras={"Iterations":"%d" % 270 self.stress_count, "Pass":"%d" %count}) 271 disable_bluetooth(self.dut.droid) 272 raise signals.TestPass(details="", extras={"Iterations":"%d" % 273 self.stress_count, "Pass":"%d" %(count+1)}) 274 275 @test_tracker_info(uuid="49e3916a-9580-4bf7-a60d-a0f2545dcdde") 276 def test_stress_connect_traffic_disconnect_5g(self): 277 """Test to connect and disconnect from a network for N times. 278 279 Steps: 280 1. Scan and connect to a network. 281 2. Run IPerf to upload data for few seconds. 282 3. Disconnect. 283 4. Repeat 1-3. 284 285 """ 286 for count in range(self.stress_count): 287 try: 288 net_id = self.dut.droid.wifiAddNetwork(self.wpa_5g) 289 asserts.assert_true(net_id != -1, "Add network %r failed" % self.wpa_5g) 290 self.scan_and_connect_by_id(self.wpa_5g, net_id) 291 # Start IPerf traffic from phone to server. 292 # Upload data for 10s. 293 args = "-p {} -t {}".format(self.iperf_server_port, 10) 294 self.log.info("Running iperf client {}".format(args)) 295 result, data = self.dut.run_iperf_client(self.iperf_server_address, args) 296 if not result: 297 self.log.debug("Error occurred in iPerf traffic.") 298 self.run_ping(10) 299 wutils.wifi_forget_network(self.dut,self.wpa_5g[WifiEnums.SSID_KEY]) 300 time.sleep(WAIT_BEFORE_CONNECTION) 301 except: 302 raise signals.TestFailure("Network connect-disconnect failed." 303 "Look at logs", extras={"Iterations":"%d" % 304 self.stress_count, "Pass":"%d" %count}) 305 raise signals.TestPass(details="", extras={"Iterations":"%d" % 306 self.stress_count, "Pass":"%d" %(count+1)}) 307 308 @test_tracker_info(uuid="e9827dff-0755-43ec-8b50-1f9756958460") 309 def test_stress_connect_long_traffic_5g(self): 310 """Test to connect to network and hold connection for few hours. 311 312 Steps: 313 1. Scan and connect to a network. 314 2. Run IPerf to download data for few hours. 315 3. Run IPerf to upload data for few hours. 316 4. Verify no WiFi disconnects/data interruption. 317 318 """ 319 self.scan_and_connect_by_ssid(self.dut, self.wpa_5g) 320 self.scan_and_connect_by_ssid(self.dut_client, self.wpa_5g) 321 322 q = queue.Queue() 323 sec = self.stress_hours * 60 * 60 324 start_time = time.time() 325 326 dl_args = "-p {} -t {} -R".format(self.iperf_server_port, sec) 327 dl = threading.Thread(target=self.run_long_traffic, args=(sec, dl_args, q)) 328 dl.start() 329 dl.join() 330 331 total_time = time.time() - start_time 332 self.log.debug("WiFi state = %d" %self.dut.droid.wifiCheckState()) 333 while(q.qsize() > 0): 334 if not q.get(): 335 raise signals.TestFailure("Network long-connect failed.", 336 extras={"Total Hours":"%d" %self.stress_hours, 337 "Seconds Run":"%d" %total_time}) 338 raise signals.TestPass(details="", extras={"Total Hours":"%d" % 339 self.stress_hours, "Seconds Run":"%d" %total_time}) 340 341 @test_tracker_info(uuid="591d257d-9477-4a89-a220-5715c93a76a7") 342 def test_stress_youtube_5g(self): 343 """Test to connect to network and play various youtube videos. 344 345 Steps: 346 1. Scan and connect to a network. 347 2. Loop through and play a list of youtube videos. 348 3. Verify no WiFi disconnects/data interruption. 349 350 """ 351 # List of Youtube 4K videos. 352 videos = ["https://www.youtube.com/watch?v=TKmGU77INaM", 353 "https://www.youtube.com/watch?v=WNCl-69POro", 354 "https://www.youtube.com/watch?v=dVkK36KOcqs", 355 "https://www.youtube.com/watch?v=0wCC3aLXdOw", 356 "https://www.youtube.com/watch?v=rN6nlNC9WQA", 357 "https://www.youtube.com/watch?v=RK1K2bCg4J8"] 358 try: 359 self.scan_and_connect_by_ssid(self.dut, self.wpa_5g) 360 start_time = time.time() 361 for video in videos: 362 self.start_youtube_video(url=video, secs=10*60) 363 except: 364 total_time = time.time() - start_time 365 raise signals.TestFailure("The youtube stress test has failed." 366 "WiFi State = %d" %self.dut.droid.wifiCheckState(), 367 extras={"Total Hours":"1", "Seconds Run":"%d" %total_time}) 368 total_time = time.time() - start_time 369 self.log.debug("WiFi state = %d" %self.dut.droid.wifiCheckState()) 370 raise signals.TestPass(details="", extras={"Total Hours":"1", 371 "Seconds Run":"%d" %total_time}) 372 373 @test_tracker_info(uuid="d367c83e-5b00-4028-9ed8-f7b875997d13") 374 def test_stress_wifi_failover(self): 375 """This test does aggressive failover to several networks in list. 376 377 Steps: 378 1. Add and enable few networks. 379 2. Let device auto-connect. 380 3. Remove the connected network. 381 4. Repeat 2-3. 382 5. Device should connect to a network until all networks are 383 exhausted. 384 385 """ 386 for count in range(int(self.stress_count/4)): 387 wutils.reset_wifi(self.dut) 388 ssids = list() 389 for network in self.networks: 390 ssids.append(network[WifiEnums.SSID_KEY]) 391 ret = self.dut.droid.wifiAddNetwork(network) 392 asserts.assert_true(ret != -1, "Add network %r failed" % network) 393 self.dut.droid.wifiEnableNetwork(ret, 0) 394 self.dut.droid.wifiStartScan() 395 time.sleep(WAIT_FOR_AUTO_CONNECT) 396 cur_network = self.dut.droid.wifiGetConnectionInfo() 397 cur_ssid = cur_network[WifiEnums.SSID_KEY] 398 self.log.info("Cur_ssid = %s" % cur_ssid) 399 for i in range(0,len(self.networks)): 400 self.log.debug("Forget network %s" % cur_ssid) 401 wutils.wifi_forget_network(self.dut, cur_ssid) 402 time.sleep(WAIT_FOR_AUTO_CONNECT) 403 cur_network = self.dut.droid.wifiGetConnectionInfo() 404 cur_ssid = cur_network[WifiEnums.SSID_KEY] 405 self.log.info("Cur_ssid = %s" % cur_ssid) 406 if i == len(self.networks) - 1: 407 break 408 if cur_ssid not in ssids: 409 raise signals.TestFailure("Device did not failover to the " 410 "expected network. SSID = %s" % cur_ssid) 411 network_config = self.dut.droid.wifiGetConfiguredNetworks() 412 self.log.info("Network Config = %s" % network_config) 413 if len(network_config): 414 raise signals.TestFailure("All the network configurations were not " 415 "removed. Configured networks = %s" % network_config, 416 extras={"Iterations":"%d" % self.stress_count, 417 "Pass":"%d" %(count*4)}) 418 raise signals.TestPass(details="", extras={"Iterations":"%d" % 419 self.stress_count, "Pass":"%d" %((count+1)*4)}) 420 421 @test_tracker_info(uuid="2c19e8d1-ac16-4d7e-b309-795144e6b956") 422 def test_stress_softAP_startup_and_stop_5g(self): 423 """Test to bring up softAP and down for N times. 424 425 Steps: 426 1. Bring up softAP on 5G. 427 2. Check for softAP on teh client device. 428 3. Turn ON WiFi. 429 4. Verify softAP is turned down and WiFi is up. 430 431 """ 432 ap_ssid = "softap_" + utils.rand_ascii_str(8) 433 ap_password = utils.rand_ascii_str(8) 434 self.dut.log.info("softap setup: %s %s", ap_ssid, ap_password) 435 config = {wutils.WifiEnums.SSID_KEY: ap_ssid} 436 config[wutils.WifiEnums.PWD_KEY] = ap_password 437 # Set country code explicitly to "US". 438 wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US) 439 wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US) 440 for count in range(self.stress_count): 441 initial_wifi_state = self.dut.droid.wifiCheckState() 442 wutils.start_wifi_tethering(self.dut, 443 ap_ssid, 444 ap_password, 445 WifiEnums.WIFI_CONFIG_APBAND_5G) 446 wutils.start_wifi_connection_scan_and_ensure_network_found( 447 self.dut_client, ap_ssid) 448 wutils.stop_wifi_tethering(self.dut) 449 asserts.assert_false(self.dut.droid.wifiIsApEnabled(), 450 "SoftAp failed to shutdown!") 451 # Give some time for WiFi to come back to previous state. 452 time.sleep(2) 453 cur_wifi_state = self.dut.droid.wifiCheckState() 454 if initial_wifi_state != cur_wifi_state: 455 raise signals.TestFailure("Wifi state was %d before softAP and %d now!" % 456 (initial_wifi_state, cur_wifi_state), 457 extras={"Iterations":"%d" % self.stress_count, 458 "Pass":"%d" %count}) 459 raise signals.TestPass(details="", extras={"Iterations":"%d" % 460 self.stress_count, "Pass":"%d" %(count+1)}) 461 462 @test_tracker_info(uuid="eb22e26b-95d1-4580-8c76-85dfe6a42a0f") 463 def test_stress_wifi_roaming(self): 464 AP1_network = self.reference_networks[0]["5g"] 465 AP2_network = self.reference_networks[1]["5g"] 466 wutils.set_attns(self.attenuators, "AP1_on_AP2_off") 467 self.scan_and_connect_by_ssid(self.dut, AP1_network) 468 # Reduce iteration to half because each iteration does two roams. 469 for count in range(int(self.stress_count/2)): 470 self.log.info("Roaming iteration %d, from %s to %s", count, 471 AP1_network, AP2_network) 472 try: 473 wutils.trigger_roaming_and_validate(self.dut, self.attenuators, 474 "AP1_off_AP2_on", AP2_network) 475 self.log.info("Roaming iteration %d, from %s to %s", count, 476 AP2_network, AP1_network) 477 wutils.trigger_roaming_and_validate(self.dut, self.attenuators, 478 "AP1_on_AP2_off", AP1_network) 479 except: 480 raise signals.TestFailure("Roaming failed. Look at logs", 481 extras={"Iterations":"%d" %self.stress_count, "Pass":"%d" % 482 (count*2)}) 483 raise signals.TestPass(details="", extras={"Iterations":"%d" % 484 self.stress_count, "Pass":"%d" %((count+1)*2)}) 485 486 @test_tracker_info(uuid="e8ae8cd2-c315-4c08-9eb3-83db65b78a58") 487 def test_stress_network_selector_2G_connection(self): 488 """ 489 1. Add one saved 2G network to DUT. 490 2. Move the DUT in range. 491 3. Verify the DUT is connected to the network. 492 4. Move the DUT out of range 493 5. Repeat step 2-4 494 """ 495 for attenuator in self.attenuators: 496 attenuator.set_atten(95) 497 # add a saved network to DUT 498 networks = [self.reference_networks[0]['2g']] 499 self.add_networks(self.dut, networks) 500 for count in range(self.stress_count): 501 self.connect_and_verify_connected_ssid(self.reference_networks[0]['2g']) 502 # move the DUT out of range 503 self.attenuators[0].set_atten(95) 504 time.sleep(10) 505 wutils.set_attns(self.attenuators, "default") 506 raise signals.TestPass(details="", extras={"Iterations":"%d" % 507 self.stress_count, "Pass":"%d" %(count+1)}) 508 509 @test_tracker_info(uuid="5d5d14cb-3cd1-4b3d-8c04-0d6f4b764b6b") 510 def test_stress_pno_connection_to_2g(self): 511 """Test PNO triggered autoconnect to a network for N times 512 513 Steps: 514 1. Save 2Ghz valid network configuration in the device. 515 2. Screen off DUT 516 3. Attenuate 5Ghz network and wait for a few seconds to trigger PNO. 517 4. Check the device connected to 2Ghz network automatically. 518 5. Repeat step 3-4 519 """ 520 for attenuator in self.attenuators: 521 attenuator.set_atten(95) 522 # add a saved network to DUT 523 networks = [self.reference_networks[0]['2g']] 524 self.add_networks(self.dut, networks) 525 self.dut.droid.wakeLockRelease() 526 self.dut.droid.goToSleepNow() 527 for count in range(self.stress_count): 528 self.connect_and_verify_connected_ssid(self.reference_networks[0]['2g'], is_pno=True) 529 wutils.wifi_forget_network( 530 self.dut, networks[0][WifiEnums.SSID_KEY]) 531 # move the DUT out of range 532 self.attenuators[0].set_atten(95) 533 time.sleep(10) 534 self.add_networks(self.dut, networks) 535 wutils.set_attns(self.attenuators, "default") 536 raise signals.TestPass(details="", extras={"Iterations":"%d" % 537 self.stress_count, "Pass":"%d" %(count+1)}) 538