blob: ea06bef20e5fe012408dc2b81a3886e23856fc72 [file] [log] [blame]
Omar El Ayachb503ae82019-02-13 09:36:36 -08001#!/usr/bin/env python3.4
2#
3# Copyright 2019 - 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 collections
18import json
Omar El Ayachb503ae82019-02-13 09:36:36 -080019import math
20import os
21import time
22from acts import asserts
23from acts import base_test
24from acts import context
Omar El Ayachd7109092019-09-29 18:31:33 -070025from acts import utils
Omar El Ayachb503ae82019-02-13 09:36:36 -080026from acts.controllers import iperf_server as ipf
27from acts.controllers.utils_lib import ssh
Omar El Ayachd30d3982020-04-06 14:07:01 -070028from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
Omar El Ayachb503ae82019-02-13 09:36:36 -080029from acts.test_utils.wifi import wifi_performance_test_utils as wputils
30from acts.test_utils.wifi import wifi_retail_ap as retail_ap
31from acts.test_utils.wifi import wifi_test_utils as wutils
32
33SHORT_SLEEP = 1
34MED_SLEEP = 5
35TRAFFIC_GAP_THRESH = 0.5
36IPERF_INTERVAL = 0.25
37
38
39class WifiRoamingPerformanceTest(base_test.BaseTestClass):
40 """Class for ping-based Wifi performance tests.
41
42 This class implements WiFi ping performance tests such as range and RTT.
43 The class setups up the AP in the desired configurations, configures
44 and connects the phone to the AP, and runs For an example config file to
45 run this test class see example_connectivity_performance_ap_sta.json.
46 """
Omar El Ayachd30d3982020-04-06 14:07:01 -070047 def __init__(self, controllers):
48 base_test.BaseTestClass.__init__(self, controllers)
49 self.testcase_metric_logger = (
50 BlackboxMappedMetricLogger.for_test_case())
51 self.testclass_metric_logger = (
52 BlackboxMappedMetricLogger.for_test_class())
53 self.publish_testcase_metrics = True
Omar El Ayachb503ae82019-02-13 09:36:36 -080054
55 def setup_class(self):
56 """Initializes common test hardware and parameters.
57
58 This function initializes hardwares and compiles parameters that are
59 common to all tests in this class.
60 """
Omar El Ayach74f78dc2019-07-30 15:15:34 -070061 self.dut = self.android_devices[-1]
Omar El Ayachb503ae82019-02-13 09:36:36 -080062 req_params = [
63 'RetailAccessPoints', 'roaming_test_params', 'testbed_params'
64 ]
65 opt_params = ['main_network', 'RemoteServer']
66 self.unpack_userparams(req_params, opt_params)
67 self.testclass_params = self.roaming_test_params
68 self.num_atten = self.attenuators[0].instrument.num_atten
69 self.remote_server = ssh.connection.SshConnection(
70 ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
71 self.remote_server.setup_master_ssh()
72 self.iperf_server = self.iperf_servers[0]
73 self.iperf_client = self.iperf_clients[0]
74 self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
75 self.log.info('Access Point Configuration: {}'.format(
76 self.access_point.ap_settings))
Omar El Ayach73477702019-08-07 10:12:33 -070077
Omar El Ayach9c56cf32019-09-19 13:07:51 -070078 if hasattr(self, 'bdf'):
79 self.log.info('Pushing WiFi BDF to DUT.')
80 wputils.push_bdf(self.dut, self.bdf)
81 if hasattr(self, 'firmware'):
82 self.log.info('Pushing WiFi firmware to DUT.')
83 wlanmdsp = [
84 file for file in self.firmware if "wlanmdsp.mbn" in file
85 ][0]
86 data_msc = [file for file in self.firmware
87 if "Data.msc" in file][0]
88 wputils.push_firmware(self.dut, wlanmdsp, data_msc)
Omar El Ayach73477702019-08-07 10:12:33 -070089 # Get RF connection map
90 self.log.info("Getting RF connection map.")
Omar El Ayach98be2382019-09-04 15:11:50 -070091 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayach73477702019-08-07 10:12:33 -070092 self.rf_map_by_network, self.rf_map_by_atten = (
93 wputils.get_full_rf_connection_map(self.attenuators, self.dut,
94 self.remote_server,
95 self.main_network))
Omar El Ayachab047c02019-09-03 11:38:30 -070096 self.log.info("RF Map (by Network): {}".format(self.rf_map_by_network))
97 self.log.info("RF Map (by Atten): {}".format(self.rf_map_by_atten))
Omar El Ayachb503ae82019-02-13 09:36:36 -080098
99 #Turn WiFi ON
Omar El Ayachd7109092019-09-29 18:31:33 -0700100 if self.testclass_params.get('airplane_mode', 1):
101 self.log.info('Turning on airplane mode.')
Omar El Ayachd30d3982020-04-06 14:07:01 -0700102 asserts.assert_true(utils.force_airplane_mode(self.dut, True),
103 "Can not turn on airplane mode.")
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700104 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800105
106 def pass_fail_traffic_continuity(self, result):
107 """Pass fail check for traffic continuity
108
109 Currently, the function only reports test results and implicitly passes
110 the test. A pass fail criterion is current being researched.
111
112 Args:
113 result: dict containing test results
114 """
115 self.log.info('Detected {} roam transitions:'.format(
116 len(result['roam_transitions'])))
117 for event in result['roam_transitions']:
118 self.log.info('Roam: {} -> {})'.format(event[0], event[1]))
119 self.log.info('Roam transition statistics: {}'.format(
120 result['roam_counts']))
121
122 formatted_traffic_gaps = [
123 round(gap, 2) for gap in result['traffic_disruption']
124 ]
125 self.log.info('Detected {} traffic gaps of duration: {}'.format(
126 len(result['traffic_disruption']), formatted_traffic_gaps))
127
Omar El Ayachd30d3982020-04-06 14:07:01 -0700128 if result['total_roams'] > 0:
129 disruption_percentage = (len(result['traffic_disruption']) /
130 result['total_roams']) * 100
131 max_disruption = max(result['traffic_disruption'])
Omar El Ayach73477702019-08-07 10:12:33 -0700132 else:
Omar El Ayachd30d3982020-04-06 14:07:01 -0700133 disruption_percentage = 0
134 max_disruption = 0
135 self.testcase_metric_logger.add_metric('disruption_percentage',
136 disruption_percentage)
137 self.testcase_metric_logger.add_metric('max_disruption',
138 max_disruption)
139
140 if disruption_percentage == 0:
141 asserts.explicit_pass('Test passed. No traffic disruptions found.')
142 elif max_disruption > self.testclass_params[
143 'traffic_disruption_threshold']:
144 asserts.fail('Test failed. Disruption Percentage = {}%. '
145 'Max traffic disruption: {}s.'.format(
146 disruption_percentage, max_disruption))
147 else:
148 asserts.explicit_pass('Test failed. Disruption Percentage = {}%. '
149 'Max traffic disruption: {}s.'.format(
150 disruption_percentage, max_disruption))
Omar El Ayachb503ae82019-02-13 09:36:36 -0800151
152 def pass_fail_roaming_consistency(self, results_dict):
153 """Function to evaluate roaming consistency results.
154
155 The function looks for the roams recorded in multiple runs of the same
156 attenuation waveform and checks that the DUT reliably roams to the
157 same network
158
159 Args:
160 results_dict: dict containing consistency test results
161 """
162 test_fail = False
163 for secondary_atten, roam_stats in results_dict['roam_stats'].items():
164 total_roams = sum(list(roam_stats.values()))
165 common_roam = max(roam_stats.keys(), key=(lambda k: roam_stats[k]))
166 common_roam_frequency = roam_stats[common_roam] / total_roams
167 self.log.info(
168 '{}dB secondary atten. Most common roam: {}. Frequency: {}'.
169 format(secondary_atten, common_roam, common_roam_frequency))
170 if common_roam_frequency < self.testclass_params[
171 'consistency_threshold']:
172 test_fail = True
173 self.log.info('Unstable Roams at {}dB secondary att'.format(
174 secondary_atten))
Omar El Ayachd30d3982020-04-06 14:07:01 -0700175 self.testcase_metric_logger.add_metric('common_roam_frequency',
176 common_roam_frequency)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800177 if test_fail:
178 asserts.fail('Incosistent roaming detected.')
179 else:
180 asserts.explicit_pass('Consistent roaming at all levels.')
181
182 def process_traffic_continuity_results(self, testcase_params, result):
183 """Function to process traffic results.
184
185 The function looks for traffic gaps during a roaming test
186
187 Args:
188 testcase_params: dict containing all test results and meta data
189 results_dict: dict containing consistency test results
190 """
191 self.detect_roam_events(result)
Omar El Ayach68395c42019-03-01 11:25:47 -0800192 current_context = context.get_current_context().get_full_output_path()
193 plot_file_path = os.path.join(current_context,
Omar El Ayachb503ae82019-02-13 09:36:36 -0800194 self.current_test_name + '.html')
195
196 if 'ping' in self.current_test_name:
197 self.detect_ping_gaps(result)
Omar El Ayachd30d3982020-04-06 14:07:01 -0700198 self.plot_ping_result(testcase_params,
199 result,
200 output_file_path=plot_file_path)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800201 elif 'iperf' in self.current_test_name:
202 self.detect_iperf_gaps(result)
Omar El Ayachd30d3982020-04-06 14:07:01 -0700203 self.plot_iperf_result(testcase_params,
204 result,
205 output_file_path=plot_file_path)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800206
Omar El Ayach68395c42019-03-01 11:25:47 -0800207 results_file_path = os.path.join(current_context,
Omar El Ayachb503ae82019-02-13 09:36:36 -0800208 self.current_test_name + '.json')
209 with open(results_file_path, 'w') as results_file:
Omar El Ayach73477702019-08-07 10:12:33 -0700210 json.dump(wputils.serialize_dict(result), results_file, indent=4)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800211
212 def process_consistency_results(self, testcase_params, results_dict):
213 """Function to process roaming consistency results.
214
215 The function looks compiles the test of roams recorded in consistency
216 tests and plots results for easy visualization.
217
218 Args:
219 testcase_params: dict containing all test results and meta data
220 results_dict: dict containing consistency test results
221 """
222 # make figure placeholder and get relevant functions
223 if 'ping' in self.current_test_name:
224 detect_gaps = self.detect_ping_gaps
225 plot_result = self.plot_ping_result
226 primary_y_axis = 'RTT (ms)'
227 elif 'iperf' in self.current_test_name:
228 detect_gaps = self.detect_iperf_gaps
229 plot_result = self.plot_iperf_result
230 primary_y_axis = 'Throughput (Mbps)'
231 # loop over results
232 roam_stats = collections.OrderedDict()
Omar El Ayach68395c42019-03-01 11:25:47 -0800233 current_context = context.get_current_context().get_full_output_path()
Omar El Ayachb503ae82019-02-13 09:36:36 -0800234 for secondary_atten, results_list in results_dict.items():
Omar El Ayachd30d3982020-04-06 14:07:01 -0700235 figure = wputils.BokehFigure(title=self.current_test_name,
236 x_label='Time (ms)',
237 primary_y_label=primary_y_axis,
238 secondary_y_label='RSSI (dBm)')
Omar El Ayachb503ae82019-02-13 09:36:36 -0800239 roam_stats[secondary_atten] = collections.OrderedDict()
240 for result in results_list:
241 self.detect_roam_events(result)
242 for roam_transition, count in result['roam_counts'].items():
243 roam_stats[secondary_atten][
244 roam_transition] = roam_stats[secondary_atten].get(
245 roam_transition, 0) + count
246 detect_gaps(result)
247 plot_result(testcase_params, result, figure=figure)
248 # save plot
Omar El Ayachd30d3982020-04-06 14:07:01 -0700249 plot_file_name = (self.current_test_name + '_' +
250 str(secondary_atten) + '.html')
Omar El Ayach68395c42019-03-01 11:25:47 -0800251
252 plot_file_path = os.path.join(current_context, plot_file_name)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800253 figure.save_figure(plot_file_path)
254 results_dict['roam_stats'] = roam_stats
255
Omar El Ayach68395c42019-03-01 11:25:47 -0800256 results_file_path = os.path.join(current_context,
Omar El Ayachb503ae82019-02-13 09:36:36 -0800257 self.current_test_name + '.json')
258 with open(results_file_path, 'w') as results_file:
Omar El Ayach73477702019-08-07 10:12:33 -0700259 json.dump(wputils.serialize_dict(result), results_file, indent=4)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800260
261 def detect_roam_events(self, result):
262 """Function to process roaming results.
263
264 The function detects roams by looking at changes in BSSID and compiles
265 meta data about each roam, e.g., RSSI before and after a roam. The
266 function then calls the relevant method to process traffic results and
267 report traffic disruptions.
268
269 Args:
270 testcase_params: dict containing AP and other test params
271 result: dict containing test results
272 """
273 roam_events = [
274 (idx, idx + 1)
275 for idx in range(len(result['rssi_result']['bssid']) - 1)
276 if result['rssi_result']['bssid'][idx] != result['rssi_result']
277 ['bssid'][idx + 1]
278 ]
279
280 def ignore_entry(vals):
281 for val in vals:
282 if val in {0} or math.isnan(val):
283 return True
284 return False
285
286 for roam_idx, roam_event in enumerate(roam_events):
287 # Find true roam start by scanning earlier samples for valid data
288 while ignore_entry([
289 result['rssi_result']['frequency'][roam_event[0]],
290 result['rssi_result']['signal_poll_rssi']['data'][
291 roam_event[0]]
292 ]):
293 roam_event = (roam_event[0] - 1, roam_event[1])
294 roam_events[roam_idx] = roam_event
295 # Find true roam end by scanning later samples for valid data
296 while ignore_entry([
297 result['rssi_result']['frequency'][roam_event[1]],
298 result['rssi_result']['signal_poll_rssi']['data'][
299 roam_event[1]]
300 ]):
301 roam_event = (roam_event[0], roam_event[1] + 1)
302 roam_events[roam_idx] = roam_event
303
304 roam_events = list(set(roam_events))
305 roam_events.sort(key=lambda event_tuple: event_tuple[1])
306 roam_transitions = []
307 roam_counts = {}
Omar El Ayachd30d3982020-04-06 14:07:01 -0700308 total_roams = 0
Omar El Ayachb503ae82019-02-13 09:36:36 -0800309 for event in roam_events:
310 from_bssid = next(
311 key for key, value in self.main_network.items()
312 if value['BSSID'] == result['rssi_result']['bssid'][event[0]])
313 to_bssid = next(
314 key for key, value in self.main_network.items()
315 if value['BSSID'] == result['rssi_result']['bssid'][event[1]])
316 curr_bssid_transition = (from_bssid, to_bssid)
317 curr_roam_transition = (
318 (from_bssid,
319 result['rssi_result']['signal_poll_rssi']['data'][event[0]]),
320 (to_bssid,
321 result['rssi_result']['signal_poll_rssi']['data'][event[1]]))
322 roam_transitions.append(curr_roam_transition)
323 roam_counts[curr_bssid_transition] = roam_counts.get(
324 curr_bssid_transition, 0) + 1
Omar El Ayachd30d3982020-04-06 14:07:01 -0700325 total_roams = total_roams + 1
Omar El Ayachb503ae82019-02-13 09:36:36 -0800326 result['roam_events'] = roam_events
327 result['roam_transitions'] = roam_transitions
328 result['roam_counts'] = roam_counts
Omar El Ayachd30d3982020-04-06 14:07:01 -0700329 result['total_roams'] = total_roams
Omar El Ayachb503ae82019-02-13 09:36:36 -0800330
331 def detect_ping_gaps(self, result):
332 """Function to process ping results.
333
334 The function looks for gaps in iperf traffic and reports them as
335 disruptions due to roams.
336
337 Args:
338 result: dict containing test results
339 """
340 traffic_disruption = [
341 x for x in result['ping_result']['ping_interarrivals']
342 if x > TRAFFIC_GAP_THRESH
343 ]
344 result['traffic_disruption'] = traffic_disruption
345
346 def detect_iperf_gaps(self, result):
347 """Function to process iperf results.
348
349 The function looks for gaps in iperf traffic and reports them as
350 disruptions due to roams.
351
352 Args:
353 result: dict containing test results
354 """
355 tput_thresholding = [tput < 1 for tput in result['throughput']]
356 window_size = int(TRAFFIC_GAP_THRESH / IPERF_INTERVAL)
357 tput_thresholding = [
358 any(tput_thresholding[max(0, idx - window_size):idx])
359 for idx in range(1,
360 len(tput_thresholding) + 1)
361 ]
362
363 traffic_disruption = []
364 current_disruption = 1 - window_size
365 for tput_low in tput_thresholding:
366 if tput_low:
367 current_disruption += 1
368 elif current_disruption > window_size:
369 traffic_disruption.append(current_disruption * IPERF_INTERVAL)
370 current_disruption = 1 - window_size
371 else:
372 current_disruption = 1 - window_size
373 result['traffic_disruption'] = traffic_disruption
374
375 def plot_ping_result(self,
376 testcase_params,
377 result,
378 figure=None,
379 output_file_path=None):
380 """Function to plot ping results.
381
382 The function plots ping RTTs along with RSSI over time during a roaming
383 test.
384
385 Args:
386 testcase_params: dict containing all test params
387 result: dict containing test results
388 figure: optional bokeh figure object to add current plot to
389 output_file_path: optional path to output file
390 """
391 if not figure:
Omar El Ayachd30d3982020-04-06 14:07:01 -0700392 figure = wputils.BokehFigure(title=self.current_test_name,
393 x_label='Time (ms)',
394 primary_y_label='RTT (ms)',
395 secondary_y_label='RSSI (dBm)')
396 figure.add_line(x_data=result['ping_result']['time_stamp'],
397 y_data=result['ping_result']['rtt'],
398 legend='Ping RTT',
399 width=1)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800400 figure.add_line(
Omar El Ayach73477702019-08-07 10:12:33 -0700401 x_data=result['rssi_result']['time_stamp'],
402 y_data=result['rssi_result']['signal_poll_rssi']['data'],
403 legend='RSSI',
Omar El Ayachb503ae82019-02-13 09:36:36 -0800404 y_axis='secondary')
405 figure.generate_figure(output_file_path)
406
407 def plot_iperf_result(self,
408 testcase_params,
409 result,
410 figure=None,
411 output_file_path=None):
412 """Function to plot iperf results.
413
414 The function plots iperf throughput and RSSI over time during a roaming
415 test.
416
417 Args:
418 testcase_params: dict containing all test params
419 result: dict containing test results
420 figure: optional bokeh figure object to add current plot to
421 output_file_path: optional path to output file
422 """
423 if not figure:
Omar El Ayachd30d3982020-04-06 14:07:01 -0700424 figure = wputils.BokehFigure(title=self.current_test_name,
425 x_label='Time (s)',
426 primary_y_label='Throughput (Mbps)',
427 secondary_y_label='RSSI (dBm)')
Omar El Ayachb503ae82019-02-13 09:36:36 -0800428 iperf_time_stamps = [
429 idx * IPERF_INTERVAL for idx in range(len(result['throughput']))
430 ]
Omar El Ayachd30d3982020-04-06 14:07:01 -0700431 figure.add_line(iperf_time_stamps,
432 result['throughput'],
433 'Throughput',
434 width=1)
435 figure.add_line(result['rssi_result']['time_stamp'],
436 result['rssi_result']['signal_poll_rssi']['data'],
437 'RSSI',
438 y_axis='secondary')
Omar El Ayachb503ae82019-02-13 09:36:36 -0800439
440 figure.generate_figure(output_file_path)
441
442 def setup_ap(self, testcase_params):
443 """Sets up the AP and attenuator to the test configuration.
444
445 Args:
446 testcase_params: dict containing AP and other test params
447 """
448 (primary_net_id,
449 primary_net_config) = next(net for net in self.main_network.items()
450 if net[1]['roaming_label'] == 'primary')
Omar El Ayach98be2382019-09-04 15:11:50 -0700451 for idx, atten in enumerate(self.attenuators):
452 nets_on_port = [
453 item["network"] for item in self.rf_map_by_atten[idx]
454 ]
455 if primary_net_id in nets_on_port:
Omar El Ayachb503ae82019-02-13 09:36:36 -0800456 atten.set_atten(0)
457 else:
458 atten.set_atten(atten.instrument.max_atten)
459
460 def setup_dut(self, testcase_params):
461 """Sets up the DUT in the configuration required by the test.
462
463 Args:
464 testcase_params: dict containing AP and other test params
465 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700466 # Check battery level before test
467 if not wputils.health_check(self.dut, 10):
468 asserts.skip('Battery level too low. Skipping test.')
Omar El Ayach74f78dc2019-07-30 15:15:34 -0700469 wutils.reset_wifi(self.dut)
Roshan Pius5b19a122019-09-13 08:07:30 -0700470 wutils.set_wifi_country_code(self.dut,
Omar El Ayachd30d3982020-04-06 14:07:01 -0700471 self.testclass_params['country_code'])
Omar El Ayachb503ae82019-02-13 09:36:36 -0800472 (primary_net_id,
473 primary_net_config) = next(net for net in self.main_network.items()
474 if net[1]['roaming_label'] == 'primary')
475 network = primary_net_config.copy()
476 network.pop('BSSID', None)
Omar El Ayach74f78dc2019-07-30 15:15:34 -0700477 self.dut.droid.wifiSetEnableAutoJoinWhenAssociated(1)
Omar El Ayachd30d3982020-04-06 14:07:01 -0700478 wutils.wifi_connect(self.dut,
479 network,
480 num_of_tries=5,
481 check_connectivity=False)
Omar El Ayach74f78dc2019-07-30 15:15:34 -0700482 self.dut.droid.wifiSetEnableAutoJoinWhenAssociated(1)
483 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayachb503ae82019-02-13 09:36:36 -0800484 if testcase_params['screen_on']:
Omar El Ayach74f78dc2019-07-30 15:15:34 -0700485 self.dut.wakeup_screen()
486 self.dut.droid.wakeLockAcquireBright()
Omar El Ayachb503ae82019-02-13 09:36:36 -0800487 time.sleep(MED_SLEEP)
488
489 def setup_roaming_test(self, testcase_params):
490 """Function to set up roaming test."""
491 self.setup_ap(testcase_params)
492 self.setup_dut(testcase_params)
493
494 def run_ping_test(self, testcase_params):
495 """Main function for ping roaming tests.
496
497 Args:
498 testcase_params: dict including all test params encoded in test
499 name
500 Returns:
501 dict containing all test results and meta data
502 """
503 self.log.info('Starting ping test.')
504 ping_future = wputils.get_ping_stats_nb(
505 self.remote_server, self.dut_ip,
506 testcase_params['atten_waveforms']['length'],
507 testcase_params['ping_interval'], 64)
508 rssi_future = wputils.get_connected_rssi_nb(
Omar El Ayach74f78dc2019-07-30 15:15:34 -0700509 self.dut,
Omar El Ayachb503ae82019-02-13 09:36:36 -0800510 int(testcase_params['atten_waveforms']['length'] /
511 testcase_params['rssi_polling_frequency']),
512 testcase_params['rssi_polling_frequency'])
513 self.run_attenuation_waveform(testcase_params)
514 return {
Omar El Ayach73477702019-08-07 10:12:33 -0700515 'ping_result': ping_future.result().as_dict(),
Omar El Ayachb503ae82019-02-13 09:36:36 -0800516 'rssi_result': rssi_future.result(),
517 'ap_settings': self.access_point.ap_settings,
518 }
519
520 def run_iperf_test(self, testcase_params):
521 """Main function for iperf roaming tests.
522
523 Args:
524 testcase_params: dict including all test params encoded in test
525 name
526 Returns:
527 result: dict containing all test results and meta data
528 """
529 self.log.info('Starting iperf test.')
530 self.iperf_server.start(extra_args='-i {}'.format(IPERF_INTERVAL))
Omar El Ayach73477702019-08-07 10:12:33 -0700531 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayachb503ae82019-02-13 09:36:36 -0800532 if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
Omar El Ayach73477702019-08-07 10:12:33 -0700533 iperf_server_address = self.dut_ip
Omar El Ayachb503ae82019-02-13 09:36:36 -0800534 else:
Omar El Ayach73477702019-08-07 10:12:33 -0700535 iperf_server_address = wputils.get_server_address(
536 self.remote_server, self.dut_ip, '255.255.255.0')
Omar El Ayachb503ae82019-02-13 09:36:36 -0800537 iperf_args = '-i {} -t {} -J'.format(
538 IPERF_INTERVAL, testcase_params['atten_waveforms']['length'])
539 if not isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
540 iperf_args = iperf_args + ' -R'
541 iperf_future = wputils.start_iperf_client_nb(
542 self.iperf_client, iperf_server_address, iperf_args, 0,
543 testcase_params['atten_waveforms']['length'] + MED_SLEEP)
544 rssi_future = wputils.get_connected_rssi_nb(
Omar El Ayach74f78dc2019-07-30 15:15:34 -0700545 self.dut,
Omar El Ayachb503ae82019-02-13 09:36:36 -0800546 int(testcase_params['atten_waveforms']['length'] /
547 testcase_params['rssi_polling_frequency']),
548 testcase_params['rssi_polling_frequency'])
549 self.run_attenuation_waveform(testcase_params)
550 client_output_path = iperf_future.result()
551 server_output_path = self.iperf_server.stop()
552 if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
553 iperf_file = server_output_path
554 else:
555 iperf_file = client_output_path
556 iperf_result = ipf.IPerfResult(iperf_file)
Omar El Ayach98be2382019-09-04 15:11:50 -0700557 instantaneous_rates = [
558 rate * 8 * (1.024**2) for rate in iperf_result.instantaneous_rates
559 ]
Omar El Ayachb503ae82019-02-13 09:36:36 -0800560 return {
Omar El Ayach98be2382019-09-04 15:11:50 -0700561 'throughput': instantaneous_rates,
Omar El Ayachb503ae82019-02-13 09:36:36 -0800562 'rssi_result': rssi_future.result(),
563 'ap_settings': self.access_point.ap_settings,
564 }
565
566 def run_attenuation_waveform(self, testcase_params, step_duration=1):
567 """Function that generates test params based on the test name.
568
569 Args:
570 testcase_params: dict including all test params encoded in test
571 name
572 step_duration: int representing number of seconds to dwell on each
573 atten level
574 """
575 atten_waveforms = testcase_params['atten_waveforms']
576 for atten_idx in range(atten_waveforms['length']):
577 start_time = time.time()
578 for network, atten_waveform in atten_waveforms.items():
Omar El Ayach73477702019-08-07 10:12:33 -0700579 for idx, atten in enumerate(self.attenuators):
580 nets_on_port = [
581 item["network"] for item in self.rf_map_by_atten[idx]
582 ]
583 if network in nets_on_port:
Omar El Ayachb503ae82019-02-13 09:36:36 -0800584 atten.set_atten(atten_waveform[atten_idx])
585 measure_time = time.time() - start_time
586 time.sleep(step_duration - measure_time)
587
588 def compile_atten_waveforms(self, waveform_params):
589 """Function to compile all attenuation waveforms for roaming test.
590
591 Args:
592 waveform_params: list of dicts representing waveforms to generate
593 """
594 atten_waveforms = {}
595 for network in list(waveform_params[0]):
596 atten_waveforms[network] = []
597
598 for waveform in waveform_params:
599 for network, network_waveform in waveform.items():
600 waveform_vector = self.gen_single_atten_waveform(
601 network_waveform)
602 atten_waveforms[network] += waveform_vector
603
604 waveform_lengths = {
605 len(atten_waveforms[network])
606 for network in atten_waveforms.keys()
607 }
608 if len(waveform_lengths) != 1:
609 raise ValueError(
610 'Attenuation waveform length should be equal for all networks.'
611 )
612 else:
613 atten_waveforms['length'] = waveform_lengths.pop()
614 return atten_waveforms
615
616 def gen_single_atten_waveform(self, waveform_params):
617 """Function to generate a single attenuation waveform for roaming test.
618
619 Args:
620 waveform_params: dict representing waveform to generate
621 """
622 waveform_vector = []
623 for section in range(len(waveform_params['atten_levels']) - 1):
624 section_limits = waveform_params['atten_levels'][section:section +
625 2]
626 up_down = (1 - 2 * (section_limits[1] < section_limits[0]))
627 temp_section = list(
628 range(section_limits[0], section_limits[1] + up_down,
629 up_down * waveform_params['step_size']))
630 temp_section = [
631 val for val in temp_section
632 for _ in range(waveform_params['step_duration'])
633 ]
634 waveform_vector += temp_section
635 waveform_vector *= waveform_params['repetitions']
636 return waveform_vector
637
Omar El Ayach73477702019-08-07 10:12:33 -0700638 def parse_test_params(self, testcase_params):
Omar El Ayachb503ae82019-02-13 09:36:36 -0800639 """Function that generates test params based on the test name.
640
641 Args:
642 test_name: current test name
643 Returns:
644 testcase_params: dict including all test params encoded in test
645 name
646 """
Omar El Ayach73477702019-08-07 10:12:33 -0700647 if testcase_params["waveform_type"] == 'smooth':
Omar El Ayachb503ae82019-02-13 09:36:36 -0800648 testcase_params[
649 'roaming_waveforms_params'] = self.testclass_params[
650 'smooth_roaming_waveforms']
Omar El Ayach73477702019-08-07 10:12:33 -0700651 elif testcase_params["waveform_type"] == 'failover':
Omar El Ayachb503ae82019-02-13 09:36:36 -0800652 testcase_params[
653 'roaming_waveforms_params'] = self.testclass_params[
654 'failover_roaming_waveforms']
Omar El Ayach73477702019-08-07 10:12:33 -0700655 elif testcase_params["waveform_type"] == 'consistency':
Omar El Ayachb503ae82019-02-13 09:36:36 -0800656 testcase_params[
657 'roaming_waveforms_params'] = self.testclass_params[
658 'consistency_waveforms']
Omar El Ayachb503ae82019-02-13 09:36:36 -0800659 return testcase_params
660
Omar El Ayach73477702019-08-07 10:12:33 -0700661 def _test_traffic_continuity(self, testcase_params):
Omar El Ayachb503ae82019-02-13 09:36:36 -0800662 """Test function for traffic continuity"""
663 # Compile test parameters from config and test name
Omar El Ayach73477702019-08-07 10:12:33 -0700664 testcase_params = self.parse_test_params(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800665 testcase_params.update(self.testclass_params)
666 testcase_params['atten_waveforms'] = self.compile_atten_waveforms(
667 testcase_params['roaming_waveforms_params'])
668 # Run traffic test
669 self.setup_roaming_test(testcase_params)
670 if testcase_params['traffic_type'] == 'iperf':
671 result = self.run_iperf_test(testcase_params)
672 elif testcase_params['traffic_type'] == 'ping':
673 result = self.run_ping_test(testcase_params)
674 # Postprocess results
675 self.process_traffic_continuity_results(testcase_params, result)
676 self.pass_fail_traffic_continuity(result)
677
Omar El Ayach73477702019-08-07 10:12:33 -0700678 def _test_roam_consistency(self, testcase_params):
Omar El Ayachb503ae82019-02-13 09:36:36 -0800679 """Test function for roaming consistency"""
Omar El Ayach73477702019-08-07 10:12:33 -0700680 testcase_params = self.parse_test_params(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800681 testcase_params.update(self.testclass_params)
682 # Run traffic test
683 secondary_attens = range(
684 self.testclass_params['consistency_waveforms']['secondary_loop']
685 ['atten_levels'][0], self.testclass_params['consistency_waveforms']
686 ['secondary_loop']['atten_levels'][1],
687 self.testclass_params['consistency_waveforms']['secondary_loop']
688 ['step_size'])
689 results = collections.OrderedDict()
690 for secondary_atten in secondary_attens:
691 primary_waveform = self.gen_single_atten_waveform(
692 testcase_params['roaming_waveforms_params']['primary_sweep'])
693 secondary_waveform_params = {
694 'atten_levels': [secondary_atten, secondary_atten],
695 'step_size': 1,
696 'step_duration': len(primary_waveform),
697 'repetitions': 1
698 }
699 secondary_waveform = self.gen_single_atten_waveform(
700 secondary_waveform_params)
701 testcase_params['atten_waveforms'] = {
702 'length': len(primary_waveform)
703 }
704 for network_key, network_info in self.main_network.items():
705 if 'primary' in network_info['roaming_label']:
706 testcase_params['atten_waveforms'][
707 network_key] = primary_waveform
708 else:
709 testcase_params['atten_waveforms'][
710 network_key] = secondary_waveform
711 results[secondary_atten] = []
712 for run in range(self.testclass_params['consistency_num_runs']):
713 self.setup_roaming_test(testcase_params)
714 results[secondary_atten].append(
715 self.run_ping_test(testcase_params))
716 # Postprocess results
717 self.process_consistency_results(testcase_params, results)
718 self.pass_fail_roaming_consistency(results)
719
720 def test_consistency_roaming_screen_on_ping(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700721 testcase_params = {
722 "waveform_type": "consistency",
723 "screen_on": 1,
724 "traffic_type": "ping"
725 }
726 self._test_roam_consistency(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800727
728 def test_smooth_roaming_screen_on_ping_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700729 testcase_params = {
730 "waveform_type": "smooth",
731 "screen_on": 1,
732 "traffic_type": "ping"
733 }
734 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800735
736 def test_smooth_roaming_screen_on_iperf_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700737 testcase_params = {
738 "waveform_type": "smooth",
739 "screen_on": 1,
740 "traffic_type": "iperf"
741 }
742 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800743
744 def test_failover_roaming_screen_on_ping_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700745 testcase_params = {
746 "waveform_type": "failover",
747 "screen_on": 1,
748 "traffic_type": "ping"
749 }
750 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800751
752 def test_failover_roaming_screen_on_iperf_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700753 testcase_params = {
754 "waveform_type": "failover",
755 "screen_on": 1,
756 "traffic_type": "iperf"
757 }
758 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800759
760 def test_smooth_roaming_screen_off_ping_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700761 testcase_params = {
762 "waveform_type": "smooth",
763 "screen_on": 0,
764 "traffic_type": "ping"
765 }
766 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800767
768 def test_smooth_roaming_screen_off_iperf_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700769 testcase_params = {
770 "waveform_type": "smooth",
771 "screen_on": 0,
772 "traffic_type": "iperf"
773 }
774 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800775
776 def test_failover_roaming_screen_off_ping_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700777 testcase_params = {
778 "waveform_type": "failover",
779 "screen_on": 0,
780 "traffic_type": "ping"
781 }
782 self._test_traffic_continuity(testcase_params)
Omar El Ayachb503ae82019-02-13 09:36:36 -0800783
784 def test_failover_roaming_screen_off_iperf_continuity(self):
Omar El Ayach73477702019-08-07 10:12:33 -0700785 testcase_params = {
786 "waveform_type": "failover",
787 "screen_on": 0,
788 "traffic_type": "iperf"
789 }
790 self._test_traffic_continuity(testcase_params)