blob: 66eb0b53222eae11acd343a0d26a59fa09af5d90 [file] [log] [blame]
Omar El Ayach33f80c02018-09-27 15:02:03 -07001#!/usr/bin/env python3.4
2#
3# Copyright 2017 - 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
19import logging
20import os
Omar El Ayach33f80c02018-09-27 15:02:03 -070021from acts import asserts
22from acts import base_test
23from acts import utils
Omar El Ayach5fbc1222018-12-07 18:10:05 -080024from acts.metrics.loggers.blackbox import BlackboxMetricLogger
Omar El Ayach33f80c02018-09-27 15:02:03 -070025from acts.test_utils.wifi import wifi_test_utils as wutils
26from acts.test_utils.wifi import wifi_retail_ap as retail_ap
Omar El Ayach5fbc1222018-12-07 18:10:05 -080027from WifiRvrTest import WifiRvrTest
28from WifiPingTest import WifiPingTest
Omar El Ayach33f80c02018-09-27 15:02:03 -070029
30
Omar El Ayach5fbc1222018-12-07 18:10:05 -080031class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -070032 """Class to test WiFi sensitivity tests.
33
34 This class implements measures WiFi sensitivity per rate. It heavily
35 leverages the WifiRvrTest class and introduced minor differences to set
36 specific rates and the access point, and implements a different pass/fail
37 check. For an example config file to run this test class see
38 example_connectivity_performance_ap_sta.json.
39 """
40
41 VALID_TEST_CONFIGS = {
42 1: ["legacy", "VHT20"],
43 2: ["legacy", "VHT20"],
44 6: ["legacy", "VHT20"],
45 10: ["legacy", "VHT20"],
46 11: ["legacy", "VHT20"],
47 36: ["legacy", "VHT20"],
48 40: ["legacy", "VHT20"],
49 44: ["legacy", "VHT20"],
50 48: ["legacy", "VHT20"],
51 149: ["legacy", "VHT20"],
52 153: ["legacy", "VHT20"],
53 157: ["legacy", "VHT20"],
54 161: ["legacy", "VHT20"]
55 }
56 VALID_RATES = {
57 "legacy_2GHz": [[54, 1], [48, 1], [36, 1], [24, 1], [18, 1], [12, 1],
58 [11, 1], [9, 1], [6, 1], [5.5, 1], [2, 1], [1, 1]],
59 "legacy_5GHz": [[54, 1], [48, 1], [36, 1], [24, 1], [18, 1], [12, 1],
60 [9, 1], [6, 1]],
61 "HT": [[8, 1], [7, 1], [6, 1], [5, 1], [4, 1], [3, 1], [2, 1], [1, 1],
62 [0, 1], [15, 2], [14, 2], [13, 2], [12, 2], [11, 2], [10, 2],
63 [9, 2], [8, 2]],
64 "VHT": [[9, 1], [8, 1], [7, 1], [6, 1], [5, 1], [4, 1], [3, 1], [2, 1],
65 [1, 1], [0, 1], [9, 2], [8, 2], [7, 2], [6, 2], [5, 2], [4, 2],
66 [3, 2], [2, 2], [1, 2], [0, 2]]
67 }
68
Omar El Ayach5fbc1222018-12-07 18:10:05 -080069 def __init__(self, controllers):
70 base_test.BaseTestClass.__init__(self, controllers)
71 self.failure_count_metric = BlackboxMetricLogger.for_test_case(
72 metric_name='sensitivity')
73
Omar El Ayach33f80c02018-09-27 15:02:03 -070074 def setup_class(self):
75 """Initializes common test hardware and parameters.
76
77 This function initializes hardwares and compiles parameters that are
78 common to all tests in this class.
79 """
80 self.client_dut = self.android_devices[-1]
81 req_params = [
82 "RetailAccessPoints", "sensitivity_test_params", "testbed_params"
83 ]
84 opt_params = ["main_network", "golden_files_list"]
85 self.unpack_userparams(req_params, opt_params)
86 self.testclass_params = self.sensitivity_test_params
87 self.num_atten = self.attenuators[0].instrument.num_atten
88 self.iperf_server = self.iperf_servers[0]
89 self.access_points = retail_ap.create(self.RetailAccessPoints)
90 self.access_point = self.access_points[0]
91 self.log.info("Access Point Configuration: {}".format(
92 self.access_point.ap_settings))
93 self.log_path = os.path.join(logging.log_path, "results")
94 utils.create_dir(self.log_path)
95 if not hasattr(self, "golden_files_list"):
96 self.golden_files_list = [
97 os.path.join(self.testbed_params["golden_results_path"],
98 file) for file in os.listdir(
99 self.testbed_params["golden_results_path"])
100 ]
101 self.testclass_results = []
102
103 # Turn WiFi ON
104 for dev in self.android_devices:
105 wutils.wifi_toggle_state(dev, True)
106
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800107 def pass_fail_check(self, result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700108 """Checks sensitivity against golden results and decides on pass/fail.
109
110 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800111 result: dict containing attenuation, throughput and other meta
Omar El Ayach33f80c02018-09-27 15:02:03 -0700112 data
113 """
114 try:
115 golden_path = next(file_name
116 for file_name in self.golden_files_list
117 if "sensitivity_targets" in file_name)
118 with open(golden_path, 'r') as golden_file:
119 golden_results = json.load(golden_file)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800120 golden_sensitivity = golden_results[self.current_test_name][
121 "sensitivity"]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700122 except:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800123 golden_sensitivity = float("nan")
Omar El Ayach33f80c02018-09-27 15:02:03 -0700124
Omar El Ayach33f80c02018-09-27 15:02:03 -0700125 result_string = "Througput = {}, Sensitivity = {}. Target Sensitivity = {}".format(
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800126 result["peak_throughput"], result["sensitivity"],
Omar El Ayach33f80c02018-09-27 15:02:03 -0700127 golden_sensitivity)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800128 if result["sensitivity"] - golden_sensitivity < self.testclass_params["sensitivity_tolerance"]:
Omar El Ayach33f80c02018-09-27 15:02:03 -0700129 asserts.explicit_pass("Test Passed. {}".format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800130 else:
131 asserts.fail("Test Failed. {}".format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700132
133 def process_testclass_results(self):
134 """Saves and plots test results from all executed test cases."""
135 testclass_results_dict = collections.OrderedDict()
136 for result in self.testclass_results:
137 testclass_results_dict[result["test_name"]] = {
138 "peak_throughput": result["peak_throughput"],
139 "range": result["range"],
140 "sensitivity": result["sensitivity"]
141 }
142 results_file_path = os.path.join(self.log_path, 'results.json')
143 with open(results_file_path, 'w') as results_file:
144 json.dump(testclass_results_dict, results_file, indent=4)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800145 if not self.testclass_params["traffic_type"].lower() == "ping":
146 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700147
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800148 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700149 """Post processes RvR results to compute sensitivity.
150
151 Takes in the results of the RvR tests and computes the sensitivity of
152 the current rate by looking at the point at which throughput drops
153 below the percentage specified in the config file. The function then
154 calls on its parent class process_test_results to plot the result.
155
156 Args:
157 rvr_result: dict containing attenuation, throughput and other meta
158 data
159 """
160 rvr_result["peak_throughput"] = max(rvr_result["throughput_receive"])
161 throughput_check = [
162 throughput < rvr_result["peak_throughput"] *
163 (self.testclass_params["throughput_pct_at_sensitivity"] / 100)
164 for throughput in rvr_result["throughput_receive"]
165 ]
166 consistency_check = [
167 idx for idx in range(len(throughput_check))
168 if all(throughput_check[idx:])
169 ]
170 rvr_result["atten_at_range"] = rvr_result["attenuation"][
171 consistency_check[0] - 1]
172 rvr_result["range"] = rvr_result["fixed_attenuation"] + (
173 rvr_result["atten_at_range"])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800174 rvr_result["sensitivity"] = self.testclass_params["ap_tx_power"] + (
175 self.testbed_params["ap_tx_power_offset"][str(
176 testcase_params["channel"])] - rvr_result["range"])
177 WifiRvrTest.process_test_results(self, rvr_result)
178
179 def process_ping_test_results(self, testcase_params, ping_result):
180 """Post processes RvR results to compute sensitivity.
181
182 Takes in the results of the RvR tests and computes the sensitivity of
183 the current rate by looking at the point at which throughput drops
184 below the percentage specified in the config file. The function then
185 calls on its parent class process_test_results to plot the result.
186
187 Args:
188 rvr_result: dict containing attenuation, throughput and other meta
189 data
190 """
191 testcase_params[
192 "range_ping_loss_threshold"] = 100 - testcase_params["throughput_pct_at_sensitivity"]
193 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
194 ping_result["sensitivity"] = self.testclass_params["ap_tx_power"] + (
195 self.testbed_params["ap_tx_power_offset"][str(
196 testcase_params["channel"])] - ping_result["range"])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700197
198 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800199 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700200
201 Args:
202 testcase_params: dict containing AP and other test params
203 """
204 band = self.access_point.band_lookup_by_channel(
205 testcase_params["channel"])
206 if "2G" in band:
207 frequency = wutils.WifiEnums.channel_2G_to_freq[testcase_params[
208 "channel"]]
209 else:
210 frequency = wutils.WifiEnums.channel_5G_to_freq[testcase_params[
211 "channel"]]
212 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
213 self.access_point.set_region(self.testbed_params["DFS_region"])
214 else:
215 self.access_point.set_region(self.testbed_params["default_region"])
216 self.access_point.set_channel(band, testcase_params["channel"])
217 self.access_point.set_bandwidth(band, testcase_params["mode"])
218 self.access_point.set_power(band, testcase_params["ap_tx_power"])
219 self.access_point.set_rate(
220 band, testcase_params["mode"], testcase_params["num_streams"],
221 testcase_params["rate"], testcase_params["short_gi"])
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800222 for atten in self.attenuators:
223 if atten.path == "Chain-0":
224 atten.offset = self.testbed_params["chain_offset"][str(
225 testcase_params["channel"])][0]
226 elif atten.path == "Chain-1":
227 atten.offset = self.testbed_params["chain_offset"][str(
228 testcase_params["channel"])][1]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700229 self.log.info("Access Point Configuration: {}".format(
230 self.access_point.ap_settings))
231
232 def get_start_atten(self):
233 """Gets the starting attenuation for this sensitivity test.
234
235 The function gets the starting attenuation by checking whether a test
236 as the next higher MCS has been executed. If so it sets the starting
237 point a configurable number of dBs below the next MCS's sensitivity.
238
239 Returns:
240 start_atten: starting attenuation for current test
241 """
242 # Get the current and reference test config. The reference test is the
243 # one performed at the current MCS+1
244 current_test_params = self.parse_test_params(self.current_test_name)
245 ref_test_params = current_test_params.copy()
246 if "legacy" in current_test_params["mode"] and current_test_params["rate"] < 54:
247 if current_test_params["channel"] <= 13:
248 ref_index = self.VALID_RATES["legacy_2GHz"].index(
249 [current_test_params["rate"], 1]) - 1
250 ref_test_params["rate"] = self.VALID_RATES["legacy_2GHz"][
251 ref_index][0]
252 else:
253 ref_index = self.VALID_RATES["legacy_5GHz"].index(
254 [current_test_params["rate"], 1]) - 1
255 ref_test_params["rate"] = self.VALID_RATES["legacy_5GHz"][
256 ref_index][0]
257 else:
258 ref_test_params["rate"] = ref_test_params["rate"] + 1
259
260 # Check if reference test has been run and set attenuation accordingly
261 previous_params = [
262 self.parse_test_params(result["test_name"])
263 for result in self.testclass_results
264 ]
265 try:
266 ref_index = previous_params.index(ref_test_params)
267 start_atten = self.testclass_results[ref_index]["atten_at_range"] - (
268 self.testclass_params["adjacent_mcs_range_gap"])
269 except:
270 print("Reference test not found. Starting from {} dB".format(
271 self.testclass_params["atten_start"]))
272 start_atten = self.testclass_params["atten_start"]
273 return start_atten
274
275 def parse_test_params(self, test_name):
276 """Function that generates test params based on the test name."""
277 test_name_params = test_name.split("_")
278 testcase_params = collections.OrderedDict()
279 testcase_params["channel"] = int(test_name_params[2][2:])
280 testcase_params["mode"] = test_name_params[3]
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800281
Omar El Ayach33f80c02018-09-27 15:02:03 -0700282 if "legacy" in testcase_params["mode"].lower():
283 testcase_params["rate"] = float(
284 str(test_name_params[4]).replace("p", "."))
285 else:
286 testcase_params["rate"] = int(test_name_params[4][3:])
287 testcase_params["num_streams"] = int(test_name_params[5][3:])
288 testcase_params["short_gi"] = 0
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800289
Omar El Ayach33f80c02018-09-27 15:02:03 -0700290 if self.testclass_params["traffic_type"] == "UDP":
291 testcase_params["iperf_args"] = '-i 1 -t {} -J -u -b {} -R'.format(
292 self.testclass_params["iperf_duration"],
293 self.testclass_params["UDP_rates"][testcase_params["mode"]])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800294 testcase_params["use_client_output"] = True
295 elif self.testclass_params["traffic_type"] == "TCP":
Omar El Ayach33f80c02018-09-27 15:02:03 -0700296 testcase_params["iperf_args"] = '-i 1 -t {} -J -R'.format(
297 self.testclass_params["iperf_duration"])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800298 testcase_params["use_client_output"] = True
299
Omar El Ayach33f80c02018-09-27 15:02:03 -0700300 return testcase_params
301
302 def _test_sensitivity(self):
303 """ Function that gets called for each test case
304
305 The function gets called in each rvr test case. The function customizes
306 the rvr test based on the test name of the test that called it
307 """
308 # Compile test parameters from config and test name
309 testcase_params = self.parse_test_params(self.current_test_name)
310 testcase_params.update(self.testclass_params)
311 testcase_params["atten_start"] = self.get_start_atten()
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800312 num_atten_steps = int(
313 (testcase_params["atten_stop"] - testcase_params["atten_start"]) /
314 testcase_params["atten_step"])
315 testcase_params["atten_range"] = [
316 testcase_params["atten_start"] + x * testcase_params["atten_step"]
317 for x in range(0, num_atten_steps)
318 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700319
320 # Prepare devices and run test
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800321 if testcase_params["traffic_type"].lower() == "ping":
322 self.setup_ping_test(testcase_params)
323 result = self.run_ping_test(testcase_params)
324 self.process_ping_test_results(testcase_params, result)
325 else:
326 self.setup_rvr_test(testcase_params)
327 result = self.run_rvr_test(testcase_params)
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800328 self.process_rvr_test_results(testcase_params, result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700329 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800330 self.testclass_results.append(result)
331 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700332
333 def generate_test_cases(self, channels):
334 """Function that auto-generates test cases for a test class."""
335 testcase_wrapper = self._test_sensitivity
336 for channel in channels:
337 for mode in self.VALID_TEST_CONFIGS[channel]:
338 if "VHT" in mode:
339 rates = self.VALID_RATES["VHT"]
340 elif "HT" in mode:
341 rates = self.VALID_RATES["HT"]
342 elif "legacy" in mode and channel < 14:
343 rates = self.VALID_RATES["legacy_2GHz"]
344 elif "legacy" in mode and channel > 14:
345 rates = self.VALID_RATES["legacy_5GHz"]
346 else:
347 raise ValueError("Invalid test mode.")
348 for rate in rates:
349 if "legacy" in mode:
350 testcase_name = "test_sensitivity_ch{}_{}_{}_nss{}".format(
351 channel, mode,
352 str(rate[0]).replace(".", "p"), rate[1])
353 else:
354 testcase_name = "test_sensitivity_ch{}_{}_mcs{}_nss{}".format(
355 channel, mode, rate[0], rate[1])
356 setattr(self, testcase_name, testcase_wrapper)
357 self.tests.append(testcase_name)
358
359
360class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
361 def __init__(self, controllers):
362 base_test.BaseTestClass.__init__(self, controllers)
363 self.generate_test_cases(
364 [1, 2, 6, 10, 11, 36, 40, 44, 48, 149, 153, 157, 161])
365
366
367class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
368 def __init__(self, controllers):
369 base_test.BaseTestClass.__init__(self, controllers)
370 self.generate_test_cases([1, 2, 6, 10, 11])
371
372
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800373class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
374 def __init__(self, controllers):
375 base_test.BaseTestClass.__init__(self, controllers)
376 self.generate_test_cases([36, 40, 44, 48, 149, 153, 157, 161])
377
378
Omar El Ayach33f80c02018-09-27 15:02:03 -0700379class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
380 def __init__(self, controllers):
381 base_test.BaseTestClass.__init__(self, controllers)
382 self.generate_test_cases([36, 40, 44, 48])
383
384
385class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
386 def __init__(self, controllers):
387 base_test.BaseTestClass.__init__(self, controllers)
388 self.generate_test_cases([149, 153, 157, 161])
389
390
391class WifiSensitivity_ch1_Test(WifiSensitivityTest):
392 def __init__(self, controllers):
393 base_test.BaseTestClass.__init__(self, controllers)
394 self.generate_test_cases([1])
395
396
397class WifiSensitivity_ch2_Test(WifiSensitivityTest):
398 def __init__(self, controllers):
399 base_test.BaseTestClass.__init__(self, controllers)
400 self.generate_test_cases([2])
401
402
403class WifiSensitivity_ch6_Test(WifiSensitivityTest):
404 def __init__(self, controllers):
405 base_test.BaseTestClass.__init__(self, controllers)
406 self.generate_test_cases([6])
407
408
409class WifiSensitivity_ch10_Test(WifiSensitivityTest):
410 def __init__(self, controllers):
411 base_test.BaseTestClass.__init__(self, controllers)
412 self.generate_test_cases([10])
413
414
415class WifiSensitivity_ch11_Test(WifiSensitivityTest):
416 def __init__(self, controllers):
417 base_test.BaseTestClass.__init__(self, controllers)
418 self.generate_test_cases([11])
419
420
421class WifiSensitivity_ch36_Test(WifiSensitivityTest):
422 def __init__(self, controllers):
423 base_test.BaseTestClass.__init__(self, controllers)
424 self.generate_test_cases([36])
425
426
427class WifiSensitivity_ch40_Test(WifiSensitivityTest):
428 def __init__(self, controllers):
429 base_test.BaseTestClass.__init__(self, controllers)
430 self.generate_test_cases([40])
431
432
433class WifiSensitivity_ch44_Test(WifiSensitivityTest):
434 def __init__(self, controllers):
435 base_test.BaseTestClass.__init__(self, controllers)
436 self.generate_test_cases([44])
437
438
439class WifiSensitivity_ch48_Test(WifiSensitivityTest):
440 def __init__(self, controllers):
441 base_test.BaseTestClass.__init__(self, controllers)
442 self.generate_test_cases([48])
443
444
445class WifiSensitivity_ch149_Test(WifiSensitivityTest):
446 def __init__(self, controllers):
447 base_test.BaseTestClass.__init__(self, controllers)
448 self.generate_test_cases([149])
449
450
451class WifiSensitivity_ch153_Test(WifiSensitivityTest):
452 def __init__(self, controllers):
453 base_test.BaseTestClass.__init__(self, controllers)
454 self.generate_test_cases([153])
455
456
457class WifiSensitivity_ch157_Test(WifiSensitivityTest):
458 def __init__(self, controllers):
459 base_test.BaseTestClass.__init__(self, controllers)
460 self.generate_test_cases([157])
461
462
463class WifiSensitivity_ch161_Test(WifiSensitivityTest):
464 def __init__(self, controllers):
465 base_test.BaseTestClass.__init__(self, controllers)
466 self.generate_test_cases([161])