blob: 9d6ba59e7e657e6f68f67316c07f914e0efc47e4 [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#
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -07005# Licensed under the Apache License, Version 2.0 (the 'License');
Omar El Ayach33f80c02018-09-27 15:02:03 -07006# 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
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070012# distributed under the License is distributed on an 'AS IS' BASIS,
Omar El Ayach33f80c02018-09-27 15:02:03 -070013# 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
Omar El Ayach96714c82019-01-28 18:51:46 -080018import csv
19import itertools
Omar El Ayach33f80c02018-09-27 15:02:03 -070020import json
21import logging
Omar El Ayache9725962019-09-18 17:30:17 -070022import numpy
Omar El Ayach33f80c02018-09-27 15:02:03 -070023import os
Omar El Ayach33f80c02018-09-27 15:02:03 -070024from acts import asserts
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070025from acts import context
Omar El Ayach33f80c02018-09-27 15:02:03 -070026from acts import base_test
27from acts import utils
Omar El Ayacha210d572019-03-14 17:31:38 -070028from acts.controllers import iperf_client
Omar El Ayach14416ac2019-01-30 14:58:19 -080029from acts.controllers.utils_lib import ssh
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070030from acts.test_utils.wifi import ota_chamber
Omar El Ayach6e518a22019-06-13 13:55:42 -070031from acts.test_utils.wifi import wifi_performance_test_utils as wputils
Omar El Ayach33f80c02018-09-27 15:02:03 -070032from acts.test_utils.wifi import wifi_test_utils as wutils
33from acts.test_utils.wifi import wifi_retail_ap as retail_ap
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070034from functools import partial
Omar El Ayach5fbc1222018-12-07 18:10:05 -080035from WifiRvrTest import WifiRvrTest
36from WifiPingTest import WifiPingTest
Omar El Ayach33f80c02018-09-27 15:02:03 -070037
38
Omar El Ayach5fbc1222018-12-07 18:10:05 -080039class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -070040 """Class to test WiFi sensitivity tests.
41
42 This class implements measures WiFi sensitivity per rate. It heavily
43 leverages the WifiRvrTest class and introduced minor differences to set
44 specific rates and the access point, and implements a different pass/fail
45 check. For an example config file to run this test class see
46 example_connectivity_performance_ap_sta.json.
47 """
48
Omar El Ayacha210d572019-03-14 17:31:38 -070049 RSSI_POLL_INTERVAL = 0.2
Omar El Ayach33f80c02018-09-27 15:02:03 -070050 VALID_TEST_CONFIGS = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070051 1: ['legacy', 'VHT20'],
52 2: ['legacy', 'VHT20'],
53 6: ['legacy', 'VHT20'],
54 10: ['legacy', 'VHT20'],
55 11: ['legacy', 'VHT20'],
56 36: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
57 40: ['legacy', 'VHT20'],
58 44: ['legacy', 'VHT20'],
59 48: ['legacy', 'VHT20'],
60 149: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
61 153: ['legacy', 'VHT20'],
62 157: ['legacy', 'VHT20'],
63 161: ['legacy', 'VHT20']
Omar El Ayach33f80c02018-09-27 15:02:03 -070064 }
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070065 RateTuple = collections.namedtuple(('RateTuple'),
66 ['mcs', 'streams', 'data_rate'])
Omar El Ayacha210d572019-03-14 17:31:38 -070067 #yapf:disable
Omar El Ayach33f80c02018-09-27 15:02:03 -070068 VALID_RATES = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070069 'legacy_2GHz': [
Omar El Ayacha210d572019-03-14 17:31:38 -070070 RateTuple(54, 1, 54), RateTuple(48, 1, 48),
71 RateTuple(36, 1, 36), RateTuple(24, 1, 24),
72 RateTuple(18, 1, 18), RateTuple(12, 1, 12),
73 RateTuple(11, 1, 11), RateTuple(9, 1, 9),
74 RateTuple(6, 1, 6), RateTuple(5.5, 1, 5.5),
75 RateTuple(2, 1, 2), RateTuple(1, 1, 1)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070076 'legacy_5GHz': [
Omar El Ayacha210d572019-03-14 17:31:38 -070077 RateTuple(54, 1, 54), RateTuple(48, 1, 48),
78 RateTuple(36, 1, 36), RateTuple(24, 1, 24),
79 RateTuple(18, 1, 18), RateTuple(12, 1, 12),
80 RateTuple(9, 1, 9), RateTuple(6, 1, 6)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070081 'HT20': [
Omar El Ayacha210d572019-03-14 17:31:38 -070082 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
83 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
84 RateTuple(3, 1, 26), RateTuple(2, 1, 21.7),
85 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
86 RateTuple(15, 2, 144.4), RateTuple(14, 2, 130),
87 RateTuple(13, 2, 115.6), RateTuple(12, 2, 86.7),
88 RateTuple(11, 2, 57.8), RateTuple(10, 2, 43.4),
89 RateTuple(9, 2, 28.9), RateTuple(8, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070090 'VHT20': [
Omar El Ayacha210d572019-03-14 17:31:38 -070091 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
92 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
93 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
94 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
95 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
96 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
97 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
98 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
99 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
100 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700101 'VHT40': [
Omar El Ayach03e40612019-05-01 16:25:39 -0700102 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
103 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
104 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
105 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
106 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
107 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
108 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
109 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
110 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
111 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700112 'VHT80': [
Omar El Ayach03e40612019-05-01 16:25:39 -0700113 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
114 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
115 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
116 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
117 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
118 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
119 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
120 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
121 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
122 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayach33f80c02018-09-27 15:02:03 -0700123 }
Omar El Ayacha210d572019-03-14 17:31:38 -0700124 #yapf:enable
Omar El Ayach33f80c02018-09-27 15:02:03 -0700125
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800126 def __init__(self, controllers):
127 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700128 self.testcase_metric_logger = (
129 wputils.BlackboxMappedMetricLogger.for_test_case())
130 self.testclass_metric_logger = (
131 wputils.BlackboxMappedMetricLogger.for_test_class())
132 self.publish_testcase_metrics = True
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800133
Omar El Ayach33f80c02018-09-27 15:02:03 -0700134 def setup_class(self):
135 """Initializes common test hardware and parameters.
136
137 This function initializes hardwares and compiles parameters that are
138 common to all tests in this class.
139 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700140 self.dut = self.android_devices[-1]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700141 req_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700142 'RetailAccessPoints', 'sensitivity_test_params', 'testbed_params',
143 'RemoteServer'
Omar El Ayach33f80c02018-09-27 15:02:03 -0700144 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700145 opt_params = ['main_network', 'golden_files_list']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700146 self.unpack_userparams(req_params, opt_params)
147 self.testclass_params = self.sensitivity_test_params
148 self.num_atten = self.attenuators[0].instrument.num_atten
Omar El Ayach14416ac2019-01-30 14:58:19 -0800149 self.ping_server = ssh.connection.SshConnection(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700150 ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700151 self.iperf_server = self.iperf_servers[0]
Omar El Ayach14416ac2019-01-30 14:58:19 -0800152 self.iperf_client = self.iperf_clients[0]
Omar El Ayacha210d572019-03-14 17:31:38 -0700153 self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700154 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700155 self.access_point.ap_settings))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700156 self.log_path = os.path.join(logging.log_path, 'results')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700157 utils.create_dir(self.log_path)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700158 if not hasattr(self, 'golden_files_list'):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700159 self.golden_files_list = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700160 os.path.join(self.testbed_params['golden_results_path'], file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700161 for file in os.listdir(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700162 self.testbed_params['golden_results_path'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700163 ]
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700164 if hasattr(self, 'bdf'):
165 self.log.info('Pushing WiFi BDF to DUT.')
166 wputils.push_bdf(self.dut, self.bdf)
167 if hasattr(self, 'firmware'):
168 self.log.info('Pushing WiFi firmware to DUT.')
169 wlanmdsp = [
170 file for file in self.firmware if "wlanmdsp.mbn" in file
171 ][0]
172 data_msc = [file for file in self.firmware
173 if "Data.msc" in file][0]
174 wputils.push_firmware(self.dut, wlanmdsp, data_msc)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700175 self.testclass_results = []
176
177 # Turn WiFi ON
Omar El Ayachd7109092019-09-29 18:31:33 -0700178 if self.testclass_params.get('airplane_mode', 1):
179 self.log.info('Turning on airplane mode.')
180 asserts.assert_true(
181 utils.force_airplane_mode(self.dut, True),
182 "Can not turn on airplane mode.")
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700183 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700184
Omar El Ayach96714c82019-01-28 18:51:46 -0800185 def teardown_class(self):
186 # Turn WiFi OFF
187 for dev in self.android_devices:
188 wutils.wifi_toggle_state(dev, False)
189 self.process_testclass_results()
190
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800191 def pass_fail_check(self, result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700192 """Checks sensitivity against golden results and decides on pass/fail.
193
194 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800195 result: dict containing attenuation, throughput and other meta
Omar El Ayach33f80c02018-09-27 15:02:03 -0700196 data
197 """
198 try:
199 golden_path = next(file_name
200 for file_name in self.golden_files_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700201 if 'sensitivity_targets' in file_name)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700202 with open(golden_path, 'r') as golden_file:
203 golden_results = json.load(golden_file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700204 golden_sensitivity = golden_results[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700205 self.current_test_name]['sensitivity']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700206 except:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700207 golden_sensitivity = float('nan')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700208
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700209 result_string = ('Througput = {}%, Sensitivity = {}.'
210 'Target Sensitivity = {}'.format(
211 result['peak_throughput_pct'],
212 result['sensitivity'], golden_sensitivity))
Omar El Ayache9725962019-09-18 17:30:17 -0700213 if result['peak_throughput_pct'] < 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700214 self.log.warning('Result unreliable. Peak rate unstable')
215 if result['sensitivity'] - golden_sensitivity < self.testclass_params[
216 'sensitivity_tolerance']:
217 asserts.explicit_pass('Test Passed. {}'.format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800218 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700219 asserts.fail('Test Failed. {}'.format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700220
221 def process_testclass_results(self):
222 """Saves and plots test results from all executed test cases."""
Omar El Ayach96714c82019-01-28 18:51:46 -0800223 # write json output
Omar El Ayach33f80c02018-09-27 15:02:03 -0700224 testclass_results_dict = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700225 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
Omar El Ayacha210d572019-03-14 17:31:38 -0700226 channels_tested = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700227 for result in self.testclass_results:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700228 testcase_params = result['testcase_params']
Omar El Ayacha210d572019-03-14 17:31:38 -0700229 test_id = collections.OrderedDict(
230 (key, value) for key, value in testcase_params.items()
231 if key in id_fields)
232 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700233 if test_id not in testclass_results_dict:
234 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700235 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700236 if channel not in channels_tested:
237 channels_tested.append(channel)
Omar El Ayache9725962019-09-18 17:30:17 -0700238 if result['peak_throughput_pct'] >= 95:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700239 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700240 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700241 else:
242 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700243
Omar El Ayach96714c82019-01-28 18:51:46 -0800244 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700245 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700246 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700247 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800248 results_file_path = os.path.join(self.log_path, 'results.csv')
249 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800250 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
251 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700252 for test_id, test_results in testclass_results_dict.items():
253 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700254 if 'legacy' in test_id_dict['mode']:
255 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700256 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700257 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700258 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700259 if rate[:-1] == (test_id_dict['rate'],
260 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700261 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700262 'Mode': test_id_dict['mode'],
263 'MCS': test_id_dict['rate'],
264 'Streams': test_id_dict['num_streams'],
265 'Chain': test_id_dict['chain_mask'],
266 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700267 }
268 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700269 row_value['Ch. ' + str(channel)] = test_results.pop(
270 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700271 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800272
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700273 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800274 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700275
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800276 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700277 """Post processes RvR results to compute sensitivity.
278
279 Takes in the results of the RvR tests and computes the sensitivity of
280 the current rate by looking at the point at which throughput drops
281 below the percentage specified in the config file. The function then
282 calls on its parent class process_test_results to plot the result.
283
284 Args:
285 rvr_result: dict containing attenuation, throughput and other meta
286 data
287 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700288 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
289 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700290 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700291 throughput < rvr_result['peak_throughput'] *
292 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
293 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700294 ]
295 consistency_check = [
296 idx for idx in range(len(throughput_check))
297 if all(throughput_check[idx:])
298 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700299 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700300 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700301 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
302 rvr_result['atten_at_range'])
303 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
304 self.testbed_params['ap_tx_power_offset'][str(
305 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800306 WifiRvrTest.process_test_results(self, rvr_result)
307
308 def process_ping_test_results(self, testcase_params, ping_result):
309 """Post processes RvR results to compute sensitivity.
310
311 Takes in the results of the RvR tests and computes the sensitivity of
312 the current rate by looking at the point at which throughput drops
313 below the percentage specified in the config file. The function then
314 calls on its parent class process_test_results to plot the result.
315
316 Args:
317 rvr_result: dict containing attenuation, throughput and other meta
318 data
319 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800320 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700321 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
322 self.testbed_params['ap_tx_power_offset'][str(
323 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700324
Omar El Ayach03e40612019-05-01 16:25:39 -0700325 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700326 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700327 self.setup_ping_test(testcase_params)
328 self.run_sensitivity_test = self.run_ping_test
329 self.process_sensitivity_test_results = (
330 self.process_ping_test_results)
331 else:
332 self.setup_rvr_test(testcase_params)
333 self.run_sensitivity_test = self.run_rvr_test
334 self.process_sensitivity_test_results = (
335 self.process_rvr_test_results)
336
Omar El Ayach33f80c02018-09-27 15:02:03 -0700337 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800338 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700339
340 Args:
341 testcase_params: dict containing AP and other test params
342 """
343 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700344 testcase_params['channel'])
345 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700346 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700347 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700348 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700349 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700350 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700351 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700352 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700353 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700354 self.access_point.set_region(self.testbed_params['default_region'])
355 self.access_point.set_channel(band, testcase_params['channel'])
356 self.access_point.set_bandwidth(band, testcase_params['mode'])
357 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700358 self.access_point.set_rate(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700359 band, testcase_params['mode'], testcase_params['num_streams'],
360 testcase_params['rate'], testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800361 # Set attenuator offsets and set attenuators to initial condition
362 atten_offsets = self.testbed_params['chain_offset'][str(
363 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800364 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800365 if 'AP-Chain-0' in atten.path:
366 atten.offset = atten_offsets[0]
367 elif 'AP-Chain-1' in atten.path:
368 atten.offset = atten_offsets[1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700369 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700370 self.access_point.ap_settings))
371
Omar El Ayach6e518a22019-06-13 13:55:42 -0700372 def setup_dut(self, testcase_params):
373 """Sets up the DUT in the configuration required by the test.
374
375 Args:
376 testcase_params: dict containing AP and other test params
377 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700378 # Check battery level before test
379 if not wputils.health_check(self.dut, 10):
380 asserts.skip('Battery level too low. Skipping test.')
381 # Turn screen off to preserve battery
382 self.dut.go_to_sleep()
Omar El Ayach6e518a22019-06-13 13:55:42 -0700383 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700384 testcase_params['channel'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700385 current_network = self.dut.droid.wifiGetConnectionInfo()
Omar El Ayachfc74c4d2019-09-27 18:15:48 -0700386 try:
387 connected = wutils.validate_connection(self.dut) is not None
388 except:
389 connected = False
390 if connected and current_network['SSID'] == self.main_network[band][
391 'SSID']:
Omar El Ayach39acf802019-08-02 17:52:39 -0700392 self.log.info('Already connected to desired network')
393 else:
394 wutils.reset_wifi(self.dut)
Girish Moturu2b7afe72019-09-17 20:35:54 +0000395 self.dut.droid.wifiSetCountryCode(
396 self.testclass_params['country_code'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700397 self.main_network[band]['channel'] = testcase_params['channel']
398 wutils.wifi_connect(
399 self.dut,
400 self.main_network[band],
401 num_of_tries=5,
402 check_connectivity=False)
403 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayach189dcbd2019-08-28 17:26:18 -0700404 atten_dut_chain_map = wputils.get_current_atten_dut_chain_map(
405 self.attenuators, self.dut, self.ping_server)
Omar El Ayach49141c02019-09-16 16:43:51 -0700406 self.log.info(
407 "Current Attenuator-DUT Chain Map: {}".format(atten_dut_chain_map))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700408 for idx, atten in enumerate(self.attenuators):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700409 if atten_dut_chain_map[idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700410 atten.offset = atten.instrument.max_atten
411
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700412 def extract_test_id(self, testcase_params, id_fields):
413 test_id = collections.OrderedDict(
414 (param, testcase_params[param]) for param in id_fields)
415 return test_id
416
417 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700418 """Gets the starting attenuation for this sensitivity test.
419
420 The function gets the starting attenuation by checking whether a test
421 as the next higher MCS has been executed. If so it sets the starting
422 point a configurable number of dBs below the next MCS's sensitivity.
423
424 Returns:
425 start_atten: starting attenuation for current test
426 """
427 # Get the current and reference test config. The reference test is the
428 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700429 current_rate = testcase_params['rate']
430 ref_test_params = self.extract_test_id(
431 testcase_params,
432 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
433 if 'legacy' in testcase_params['mode']:
434 if testcase_params['channel'] <= 13:
435 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700436 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700437 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700438 ref_index = max(
439 0,
440 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
441 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700442 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700443 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700444 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700445
446 # Check if reference test has been run and set attenuation accordingly
447 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700448 self.extract_test_id(
449 result['testcase_params'],
450 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700451 for result in self.testclass_results
452 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700453
Omar El Ayach33f80c02018-09-27 15:02:03 -0700454 try:
455 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700456 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700457 'atten_at_range'] - (
458 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700459 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700460 self.log.warning(
461 'Reference test not found. Starting from {} dB'.format(
462 self.testclass_params['atten_start']))
463 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700464 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700465 return start_atten
466
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700467 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700468 """Function that generates test params based on the test name."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700469 if testcase_params['chain_mask'] in ['0', '1']:
470 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
471 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700472 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700473 # Set attenuated chain to -1. Do not set to None as this will be
474 # compared to RF chain map which may include None
475 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800476
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700477 self.testclass_params[
478 'range_ping_loss_threshold'] = 100 - self.testclass_params[
479 'throughput_pct_at_sensitivity']
480 if self.testclass_params['traffic_type'] == 'UDP':
481 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
482 self.testclass_params['iperf_duration'],
483 self.testclass_params['UDP_rates'][testcase_params['mode']])
484 elif self.testclass_params['traffic_type'] == 'TCP':
485 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
486 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800487
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700488 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700489 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700490 testcase_params['iperf_args'] += ' -R'
491 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800492 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700493 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800494
Omar El Ayach33f80c02018-09-27 15:02:03 -0700495 return testcase_params
496
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700497 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700498 """ Function that gets called for each test case
499
500 The function gets called in each rvr test case. The function customizes
501 the rvr test based on the test name of the test that called it
502 """
503 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700504 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700505 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700506 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800507 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700508 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
509 testcase_params['atten_step'])
510 testcase_params['atten_range'] = [
511 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800512 for x in range(0, num_atten_steps)
513 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700514
515 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700516 self.setup_sensitivity_test(testcase_params)
517 result = self.run_sensitivity_test(testcase_params)
518 self.process_sensitivity_test_results(testcase_params, result)
519
Omar El Ayach33f80c02018-09-27 15:02:03 -0700520 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800521 self.testclass_results.append(result)
522 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700523
Omar El Ayachffb5a462019-09-16 21:05:44 -0700524 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700525 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700526 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700527 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700528 requested_modes = set(modes).intersection(
529 set(self.VALID_TEST_CONFIGS[channel]))
530 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700531 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700532 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700533 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700534 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700535 elif 'legacy' in mode and channel < 14:
536 rates = self.VALID_RATES['legacy_2GHz']
537 elif 'legacy' in mode and channel > 14:
538 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700539 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700540 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800541 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700542 testcase_params = collections.OrderedDict(
543 channel=channel,
544 mode=mode,
545 rate=rate.mcs,
546 num_streams=rate.streams,
547 short_gi=1,
548 chain_mask=chain)
549 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800550 # Do not test 2-stream rates in single chain mode
551 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700552 if 'legacy' in mode:
553 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
554 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700555 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700556 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700557 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700558 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700559 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
560 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700561 channel, mode, rate.mcs,
562 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700563 setattr(self, testcase_name,
564 partial(self._test_sensitivity, testcase_params))
565 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700566 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700567
568
569class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
570 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700571 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700572 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700573 [6, 36, 40, 44, 48, 149, 153, 157, 161],
574 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700575
576
577class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
578 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700579 super().__init__(controllers)
Omar El Ayachffb5a462019-09-16 21:05:44 -0700580 self.tests = self.generate_test_cases(
581 [6, 36, 149], ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700582
583
584class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
585 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700586 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700587 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
588 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700589
590
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800591class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
592 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700593 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700594 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700595 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
596 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800597
598
Omar El Ayach33f80c02018-09-27 15:02:03 -0700599class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
600 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700601 super().__init__(controllers)
Omar El Ayachffb5a462019-09-16 21:05:44 -0700602 self.tests = self.generate_test_cases(
603 [36, 40, 44, 48], ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700604
605
606class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
607 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700608 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700609 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700610 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700611 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700612
613
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700614# Over-the air version of senstivity tests
615class WifiOtaSensitivityTest(WifiSensitivityTest):
616 """Class to test over-the-air senstivity.
617
618 This class implements measures WiFi sensitivity tests in an OTA chamber.
619 It allows setting orientation and other chamber parameters to study
620 performance in varying channel conditions
621 """
622
Omar El Ayach40099d02019-09-12 15:17:33 -0700623 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700624 base_test.BaseTestClass.__init__(self, controllers)
625 self.testcase_metric_logger = (
626 wputils.BlackboxMappedMetricLogger.for_test_case())
627 self.testclass_metric_logger = (
Omar El Ayach40099d02019-09-12 15:17:33 -0700628 wputils.BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700629 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700630
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700631 def setup_class(self):
632 WifiSensitivityTest.setup_class(self)
633 self.ota_chamber = ota_chamber.create(
634 self.user_params['OTAChamber'])[0]
635
636 def teardown_class(self):
637 WifiSensitivityTest.teardown_class(self)
638 self.ota_chamber.reset_chamber()
639
640 def setup_sensitivity_test(self, testcase_params):
641 # Setup turntable
642 self.ota_chamber.set_orientation(testcase_params['orientation'])
643 # Continue test setup
644 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
645
646 def process_testclass_results(self):
647 """Saves and plots test results from all executed test cases."""
648 testclass_results_dict = collections.OrderedDict()
649 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
650 plots = []
651 for result in self.testclass_results:
652 test_id = self.extract_test_id(result['testcase_params'],
653 id_fields)
654 test_id = tuple(test_id.items())
655 channel = result['testcase_params']['channel']
656 if test_id not in testclass_results_dict:
657 testclass_results_dict[test_id] = collections.OrderedDict()
658 if channel not in testclass_results_dict[test_id]:
659 testclass_results_dict[test_id][channel] = {
660 'orientation': [],
661 'sensitivity': []
662 }
663 testclass_results_dict[test_id][channel]['orientation'].append(
664 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700665 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700666 testclass_results_dict[test_id][channel]['sensitivity'].append(
667 result['sensitivity'])
668 else:
669 testclass_results_dict[test_id][channel]['sensitivity'].append(
670 float('nan'))
671
672 for test_id, test_data in testclass_results_dict.items():
673 test_id_dict = dict(test_id)
674 if 'legacy' in test_id_dict['mode']:
675 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
676 test_id_dict['mode'], test_id_dict['rate'],
677 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700678 metric_test_config = '{}_{}_ch{}'.format(
679 test_id_dict['mode'], test_id_dict['rate'],
680 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700681 else:
682 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
683 test_id_dict['mode'], test_id_dict['rate'],
684 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700685 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
686 test_id_dict['mode'], test_id_dict['rate'],
687 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700688 curr_plot = wputils.BokehFigure(
689 title=str(test_id_str),
690 x_label='Orientation (deg)',
691 primary_y='Sensitivity (dBm)')
692 for channel, channel_results in test_data.items():
693 curr_plot.add_line(
694 channel_results['orientation'],
695 channel_results['sensitivity'],
Omar El Ayache9725962019-09-18 17:30:17 -0700696 legend='Channel {}'.format(channel),
697 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700698 metric_tag = 'ota_summary_ch{}_{}'.format(
699 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700700 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700701 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700702 self.testclass_metric_logger.add_metric(
703 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700704 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
705 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700706 current_context = (
707 context.get_current_context().get_full_output_path())
708 output_file_path = os.path.join(current_context,
709 str(test_id_str) + '.html')
710 curr_plot.generate_figure(output_file_path)
711 plots.append(curr_plot)
712 output_file_path = os.path.join(current_context, 'results.html')
713 wputils.BokehFigure.save_figures(plots, output_file_path)
714
715 def get_start_atten(self, testcase_params):
716 """Gets the starting attenuation for this sensitivity test.
717
718 The function gets the starting attenuation by checking whether a test
719 at the same rate configuration has executed. If so it sets the starting
720 point a configurable number of dBs below the reference test.
721
722 Returns:
723 start_atten: starting attenuation for current test
724 """
725 # Get the current and reference test config. The reference test is the
726 # one performed at the current MCS+1
727 ref_test_params = self.extract_test_id(
728 testcase_params,
729 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
730 # Check if reference test has been run and set attenuation accordingly
731 previous_params = [
732 self.extract_test_id(
733 result['testcase_params'],
734 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
735 for result in self.testclass_results
736 ]
737 try:
738 ref_index = previous_params[::-1].index(ref_test_params)
739 ref_index = len(previous_params) - 1 - ref_index
740 start_atten = self.testclass_results[ref_index][
741 'atten_at_range'] - (
742 self.testclass_params['adjacent_mcs_range_gap'])
743 except ValueError:
744 print('Reference test not found. Starting from {} dB'.format(
745 self.testclass_params['atten_start']))
746 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700747 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700748 return start_atten
749
Omar El Ayachffb5a462019-09-16 21:05:44 -0700750 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700751 angles):
752 """Function that auto-generates test cases for a test class."""
753 test_cases = []
754 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700755 requested_modes = set(modes).intersection(
756 set(self.VALID_TEST_CONFIGS[channel]))
757 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700758 if 'VHT' in mode:
759 valid_rates = self.VALID_RATES[mode]
760 elif 'HT' in mode:
761 valid_rates = self.VALID_RATES[mode]
762 elif 'legacy' in mode and channel < 14:
763 valid_rates = self.VALID_RATES['legacy_2GHz']
764 elif 'legacy' in mode and channel > 14:
765 valid_rates = self.VALID_RATES['legacy_5GHz']
766 else:
767 raise ValueError('Invalid test mode.')
768 for chain, rate, angle in itertools.product(
769 chain_mask, valid_rates, angles):
770 testcase_params = collections.OrderedDict(
771 channel=channel,
772 mode=mode,
773 rate=rate.mcs,
774 num_streams=rate.streams,
775 short_gi=1,
776 chain_mask=chain,
777 orientation=angle)
778 if rate not in requested_rates:
779 continue
780 if str(chain) in ['0', '1'] and rate[1] == 2:
781 # Do not test 2-stream rates in single chain mode
782 continue
783 if 'legacy' in mode:
784 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
785 '_ch{}_{}deg'.format(
786 channel, mode,
787 str(rate.mcs).replace('.', 'p'),
788 rate.streams, chain, angle))
789 else:
790 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
791 '_ch{}_{}deg'.format(
792 channel, mode, rate.mcs,
793 rate.streams, chain, angle))
794 setattr(self, testcase_name,
795 partial(self._test_sensitivity, testcase_params))
796 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700797 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700798
799
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700800class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700801 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700802 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700803 requested_channels = [6, 36, 149]
804 requested_rates = [
805 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700806 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700807 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700808 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700809 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700810 self.tests = self.generate_test_cases(
811 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
812 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700813
814
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700815class WifiOtaSensitivity_SingleChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700816 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700817 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700818 requested_channels = [6, 36, 149]
819 requested_rates = [
820 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700821 self.RateTuple(2, 1, 21.7)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700822 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700823 self.tests = self.generate_test_cases(
824 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
825 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700826
827
Omar El Ayachd3850d02019-09-23 16:35:49 -0700828class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
829 def __init__(self, controllers):
830 WifiOtaSensitivityTest.__init__(self, controllers)
831 requested_channels = [6, 36, 149]
832 requested_rates = [
833 self.RateTuple(9, 1, 96),
834 self.RateTuple(8, 1, 86.7),
835 self.RateTuple(7, 1, 72.2),
836 self.RateTuple(4, 1, 43.3),
837 self.RateTuple(2, 1, 21.7),
838 self.RateTuple(0, 1, 7.2),
839 self.RateTuple(9, 2, 192),
840 self.RateTuple(8, 2, 173.3),
841 self.RateTuple(7, 2, 144.4),
842 self.RateTuple(4, 2, 86.7),
843 self.RateTuple(2, 2, 43.3),
844 self.RateTuple(0, 2, 14.4)
845 ]
846 self.tests = self.generate_test_cases(
847 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
848 list(range(0, 360, 30)))
849
850
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700851class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700852 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700853 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700854 requested_rates = [
855 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700856 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700857 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700858 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700859 ]
860 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700861 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
862 requested_rates, ['2x2'], list(range(0, 360, 45)))