blob: 01cdbd296448848728ddb5163d420c737d85e6da [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 Ayach9c56cf32019-09-19 13:07:51 -0700178 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700179
Omar El Ayach96714c82019-01-28 18:51:46 -0800180 def teardown_class(self):
181 # Turn WiFi OFF
182 for dev in self.android_devices:
183 wutils.wifi_toggle_state(dev, False)
184 self.process_testclass_results()
185
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800186 def pass_fail_check(self, result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700187 """Checks sensitivity against golden results and decides on pass/fail.
188
189 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800190 result: dict containing attenuation, throughput and other meta
Omar El Ayach33f80c02018-09-27 15:02:03 -0700191 data
192 """
193 try:
194 golden_path = next(file_name
195 for file_name in self.golden_files_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700196 if 'sensitivity_targets' in file_name)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700197 with open(golden_path, 'r') as golden_file:
198 golden_results = json.load(golden_file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700199 golden_sensitivity = golden_results[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700200 self.current_test_name]['sensitivity']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700201 except:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700202 golden_sensitivity = float('nan')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700203
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700204 result_string = ('Througput = {}%, Sensitivity = {}.'
205 'Target Sensitivity = {}'.format(
206 result['peak_throughput_pct'],
207 result['sensitivity'], golden_sensitivity))
Omar El Ayache9725962019-09-18 17:30:17 -0700208 if result['peak_throughput_pct'] < 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700209 self.log.warning('Result unreliable. Peak rate unstable')
210 if result['sensitivity'] - golden_sensitivity < self.testclass_params[
211 'sensitivity_tolerance']:
212 asserts.explicit_pass('Test Passed. {}'.format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800213 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700214 asserts.fail('Test Failed. {}'.format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700215
216 def process_testclass_results(self):
217 """Saves and plots test results from all executed test cases."""
Omar El Ayach96714c82019-01-28 18:51:46 -0800218 # write json output
Omar El Ayach33f80c02018-09-27 15:02:03 -0700219 testclass_results_dict = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700220 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
Omar El Ayacha210d572019-03-14 17:31:38 -0700221 channels_tested = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700222 for result in self.testclass_results:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700223 testcase_params = result['testcase_params']
Omar El Ayacha210d572019-03-14 17:31:38 -0700224 test_id = collections.OrderedDict(
225 (key, value) for key, value in testcase_params.items()
226 if key in id_fields)
227 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700228 if test_id not in testclass_results_dict:
229 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700230 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700231 if channel not in channels_tested:
232 channels_tested.append(channel)
Omar El Ayache9725962019-09-18 17:30:17 -0700233 if result['peak_throughput_pct'] >= 95:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700234 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700235 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700236 else:
237 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700238
Omar El Ayach96714c82019-01-28 18:51:46 -0800239 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700240 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700241 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700242 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800243 results_file_path = os.path.join(self.log_path, 'results.csv')
244 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800245 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
246 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700247 for test_id, test_results in testclass_results_dict.items():
248 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700249 if 'legacy' in test_id_dict['mode']:
250 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700251 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700252 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700253 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700254 if rate[:-1] == (test_id_dict['rate'],
255 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700256 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700257 'Mode': test_id_dict['mode'],
258 'MCS': test_id_dict['rate'],
259 'Streams': test_id_dict['num_streams'],
260 'Chain': test_id_dict['chain_mask'],
261 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700262 }
263 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700264 row_value['Ch. ' + str(channel)] = test_results.pop(
265 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700266 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800267
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700268 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800269 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700270
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800271 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700272 """Post processes RvR results to compute sensitivity.
273
274 Takes in the results of the RvR tests and computes the sensitivity of
275 the current rate by looking at the point at which throughput drops
276 below the percentage specified in the config file. The function then
277 calls on its parent class process_test_results to plot the result.
278
279 Args:
280 rvr_result: dict containing attenuation, throughput and other meta
281 data
282 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700283 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
284 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700285 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700286 throughput < rvr_result['peak_throughput'] *
287 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
288 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700289 ]
290 consistency_check = [
291 idx for idx in range(len(throughput_check))
292 if all(throughput_check[idx:])
293 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700294 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700295 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700296 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
297 rvr_result['atten_at_range'])
298 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
299 self.testbed_params['ap_tx_power_offset'][str(
300 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800301 WifiRvrTest.process_test_results(self, rvr_result)
302
303 def process_ping_test_results(self, testcase_params, ping_result):
304 """Post processes RvR results to compute sensitivity.
305
306 Takes in the results of the RvR tests and computes the sensitivity of
307 the current rate by looking at the point at which throughput drops
308 below the percentage specified in the config file. The function then
309 calls on its parent class process_test_results to plot the result.
310
311 Args:
312 rvr_result: dict containing attenuation, throughput and other meta
313 data
314 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800315 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700316 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
317 self.testbed_params['ap_tx_power_offset'][str(
318 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700319
Omar El Ayach03e40612019-05-01 16:25:39 -0700320 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700321 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700322 self.setup_ping_test(testcase_params)
323 self.run_sensitivity_test = self.run_ping_test
324 self.process_sensitivity_test_results = (
325 self.process_ping_test_results)
326 else:
327 self.setup_rvr_test(testcase_params)
328 self.run_sensitivity_test = self.run_rvr_test
329 self.process_sensitivity_test_results = (
330 self.process_rvr_test_results)
331
Omar El Ayach33f80c02018-09-27 15:02:03 -0700332 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800333 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700334
335 Args:
336 testcase_params: dict containing AP and other test params
337 """
338 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700339 testcase_params['channel'])
340 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700341 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700342 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700343 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700344 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700345 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700346 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700347 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700348 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700349 self.access_point.set_region(self.testbed_params['default_region'])
350 self.access_point.set_channel(band, testcase_params['channel'])
351 self.access_point.set_bandwidth(band, testcase_params['mode'])
352 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700353 self.access_point.set_rate(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700354 band, testcase_params['mode'], testcase_params['num_streams'],
355 testcase_params['rate'], testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800356 # Set attenuator offsets and set attenuators to initial condition
357 atten_offsets = self.testbed_params['chain_offset'][str(
358 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800359 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800360 if 'AP-Chain-0' in atten.path:
361 atten.offset = atten_offsets[0]
362 elif 'AP-Chain-1' in atten.path:
363 atten.offset = atten_offsets[1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700364 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700365 self.access_point.ap_settings))
366
Omar El Ayach6e518a22019-06-13 13:55:42 -0700367 def setup_dut(self, testcase_params):
368 """Sets up the DUT in the configuration required by the test.
369
370 Args:
371 testcase_params: dict containing AP and other test params
372 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700373 # Check battery level before test
374 if not wputils.health_check(self.dut, 10):
375 asserts.skip('Battery level too low. Skipping test.')
376 # Turn screen off to preserve battery
377 self.dut.go_to_sleep()
Omar El Ayach6e518a22019-06-13 13:55:42 -0700378 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700379 testcase_params['channel'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700380 current_network = self.dut.droid.wifiGetConnectionInfo()
381 valid_connection = wutils.validate_connection(self.dut)
382 if valid_connection and current_network['SSID'] == self.main_network[
383 band]['SSID']:
384 self.log.info('Already connected to desired network')
385 else:
386 wutils.reset_wifi(self.dut)
Girish Moturu2b7afe72019-09-17 20:35:54 +0000387 self.dut.droid.wifiSetCountryCode(
388 self.testclass_params['country_code'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700389 self.main_network[band]['channel'] = testcase_params['channel']
390 wutils.wifi_connect(
391 self.dut,
392 self.main_network[band],
393 num_of_tries=5,
394 check_connectivity=False)
395 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayach189dcbd2019-08-28 17:26:18 -0700396 atten_dut_chain_map = wputils.get_current_atten_dut_chain_map(
397 self.attenuators, self.dut, self.ping_server)
Omar El Ayach49141c02019-09-16 16:43:51 -0700398 self.log.info(
399 "Current Attenuator-DUT Chain Map: {}".format(atten_dut_chain_map))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700400 for idx, atten in enumerate(self.attenuators):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700401 if atten_dut_chain_map[idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700402 atten.offset = atten.instrument.max_atten
403
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700404 def extract_test_id(self, testcase_params, id_fields):
405 test_id = collections.OrderedDict(
406 (param, testcase_params[param]) for param in id_fields)
407 return test_id
408
409 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700410 """Gets the starting attenuation for this sensitivity test.
411
412 The function gets the starting attenuation by checking whether a test
413 as the next higher MCS has been executed. If so it sets the starting
414 point a configurable number of dBs below the next MCS's sensitivity.
415
416 Returns:
417 start_atten: starting attenuation for current test
418 """
419 # Get the current and reference test config. The reference test is the
420 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700421 current_rate = testcase_params['rate']
422 ref_test_params = self.extract_test_id(
423 testcase_params,
424 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
425 if 'legacy' in testcase_params['mode']:
426 if testcase_params['channel'] <= 13:
427 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700428 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700429 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700430 ref_index = max(
431 0,
432 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
433 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700434 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700435 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700436 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700437
438 # Check if reference test has been run and set attenuation accordingly
439 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700440 self.extract_test_id(
441 result['testcase_params'],
442 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700443 for result in self.testclass_results
444 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700445
Omar El Ayach33f80c02018-09-27 15:02:03 -0700446 try:
447 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700448 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700449 'atten_at_range'] - (
450 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700451 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700452 self.log.warning(
453 'Reference test not found. Starting from {} dB'.format(
454 self.testclass_params['atten_start']))
455 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700456 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700457 return start_atten
458
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700459 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700460 """Function that generates test params based on the test name."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700461 if testcase_params['chain_mask'] in ['0', '1']:
462 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
463 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700464 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700465 # Set attenuated chain to -1. Do not set to None as this will be
466 # compared to RF chain map which may include None
467 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800468
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700469 self.testclass_params[
470 'range_ping_loss_threshold'] = 100 - self.testclass_params[
471 'throughput_pct_at_sensitivity']
472 if self.testclass_params['traffic_type'] == 'UDP':
473 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
474 self.testclass_params['iperf_duration'],
475 self.testclass_params['UDP_rates'][testcase_params['mode']])
476 elif self.testclass_params['traffic_type'] == 'TCP':
477 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
478 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800479
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700480 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700481 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700482 testcase_params['iperf_args'] += ' -R'
483 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800484 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700485 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800486
Omar El Ayach33f80c02018-09-27 15:02:03 -0700487 return testcase_params
488
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700489 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700490 """ Function that gets called for each test case
491
492 The function gets called in each rvr test case. The function customizes
493 the rvr test based on the test name of the test that called it
494 """
495 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700496 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700497 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700498 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800499 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700500 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
501 testcase_params['atten_step'])
502 testcase_params['atten_range'] = [
503 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800504 for x in range(0, num_atten_steps)
505 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700506
507 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700508 self.setup_sensitivity_test(testcase_params)
509 result = self.run_sensitivity_test(testcase_params)
510 self.process_sensitivity_test_results(testcase_params, result)
511
Omar El Ayach33f80c02018-09-27 15:02:03 -0700512 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800513 self.testclass_results.append(result)
514 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700515
Omar El Ayachffb5a462019-09-16 21:05:44 -0700516 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700517 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700518 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700519 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700520 requested_modes = set(modes).intersection(
521 set(self.VALID_TEST_CONFIGS[channel]))
522 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700523 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700524 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700525 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700526 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700527 elif 'legacy' in mode and channel < 14:
528 rates = self.VALID_RATES['legacy_2GHz']
529 elif 'legacy' in mode and channel > 14:
530 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700531 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700532 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800533 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700534 testcase_params = collections.OrderedDict(
535 channel=channel,
536 mode=mode,
537 rate=rate.mcs,
538 num_streams=rate.streams,
539 short_gi=1,
540 chain_mask=chain)
541 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800542 # Do not test 2-stream rates in single chain mode
543 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700544 if 'legacy' in mode:
545 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
546 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700547 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700548 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700549 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700550 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700551 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
552 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700553 channel, mode, rate.mcs,
554 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700555 setattr(self, testcase_name,
556 partial(self._test_sensitivity, testcase_params))
557 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700558 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700559
560
561class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
562 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700563 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700564 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700565 [6, 36, 40, 44, 48, 149, 153, 157, 161],
566 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700567
568
569class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
570 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700571 super().__init__(controllers)
Omar El Ayachffb5a462019-09-16 21:05:44 -0700572 self.tests = self.generate_test_cases(
573 [6, 36, 149], ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700574
575
576class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
577 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700578 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700579 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
580 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700581
582
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800583class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
584 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700585 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700586 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700587 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
588 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800589
590
Omar El Ayach33f80c02018-09-27 15:02:03 -0700591class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
592 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700593 super().__init__(controllers)
Omar El Ayachffb5a462019-09-16 21:05:44 -0700594 self.tests = self.generate_test_cases(
595 [36, 40, 44, 48], ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700596
597
598class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
599 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700600 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700601 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700602 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700603 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700604
605
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700606# Over-the air version of senstivity tests
607class WifiOtaSensitivityTest(WifiSensitivityTest):
608 """Class to test over-the-air senstivity.
609
610 This class implements measures WiFi sensitivity tests in an OTA chamber.
611 It allows setting orientation and other chamber parameters to study
612 performance in varying channel conditions
613 """
614
Omar El Ayach40099d02019-09-12 15:17:33 -0700615 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700616 base_test.BaseTestClass.__init__(self, controllers)
617 self.testcase_metric_logger = (
618 wputils.BlackboxMappedMetricLogger.for_test_case())
619 self.testclass_metric_logger = (
Omar El Ayach40099d02019-09-12 15:17:33 -0700620 wputils.BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700621 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700622
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700623 def setup_class(self):
624 WifiSensitivityTest.setup_class(self)
625 self.ota_chamber = ota_chamber.create(
626 self.user_params['OTAChamber'])[0]
627
628 def teardown_class(self):
629 WifiSensitivityTest.teardown_class(self)
630 self.ota_chamber.reset_chamber()
631
632 def setup_sensitivity_test(self, testcase_params):
633 # Setup turntable
634 self.ota_chamber.set_orientation(testcase_params['orientation'])
635 # Continue test setup
636 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
637
638 def process_testclass_results(self):
639 """Saves and plots test results from all executed test cases."""
640 testclass_results_dict = collections.OrderedDict()
641 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
642 plots = []
643 for result in self.testclass_results:
644 test_id = self.extract_test_id(result['testcase_params'],
645 id_fields)
646 test_id = tuple(test_id.items())
647 channel = result['testcase_params']['channel']
648 if test_id not in testclass_results_dict:
649 testclass_results_dict[test_id] = collections.OrderedDict()
650 if channel not in testclass_results_dict[test_id]:
651 testclass_results_dict[test_id][channel] = {
652 'orientation': [],
653 'sensitivity': []
654 }
655 testclass_results_dict[test_id][channel]['orientation'].append(
656 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700657 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700658 testclass_results_dict[test_id][channel]['sensitivity'].append(
659 result['sensitivity'])
660 else:
661 testclass_results_dict[test_id][channel]['sensitivity'].append(
662 float('nan'))
663
664 for test_id, test_data in testclass_results_dict.items():
665 test_id_dict = dict(test_id)
666 if 'legacy' in test_id_dict['mode']:
667 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
668 test_id_dict['mode'], test_id_dict['rate'],
669 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700670 metric_test_config = '{}_{}_ch{}'.format(
671 test_id_dict['mode'], test_id_dict['rate'],
672 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700673 else:
674 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
675 test_id_dict['mode'], test_id_dict['rate'],
676 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700677 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
678 test_id_dict['mode'], test_id_dict['rate'],
679 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700680 curr_plot = wputils.BokehFigure(
681 title=str(test_id_str),
682 x_label='Orientation (deg)',
683 primary_y='Sensitivity (dBm)')
684 for channel, channel_results in test_data.items():
685 curr_plot.add_line(
686 channel_results['orientation'],
687 channel_results['sensitivity'],
Omar El Ayache9725962019-09-18 17:30:17 -0700688 legend='Channel {}'.format(channel),
689 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700690 metric_tag = 'ota_summary_ch{}_{}'.format(
691 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700692 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700693 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700694 self.testclass_metric_logger.add_metric(
695 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700696 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
697 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700698 current_context = (
699 context.get_current_context().get_full_output_path())
700 output_file_path = os.path.join(current_context,
701 str(test_id_str) + '.html')
702 curr_plot.generate_figure(output_file_path)
703 plots.append(curr_plot)
704 output_file_path = os.path.join(current_context, 'results.html')
705 wputils.BokehFigure.save_figures(plots, output_file_path)
706
707 def get_start_atten(self, testcase_params):
708 """Gets the starting attenuation for this sensitivity test.
709
710 The function gets the starting attenuation by checking whether a test
711 at the same rate configuration has executed. If so it sets the starting
712 point a configurable number of dBs below the reference test.
713
714 Returns:
715 start_atten: starting attenuation for current test
716 """
717 # Get the current and reference test config. The reference test is the
718 # one performed at the current MCS+1
719 ref_test_params = self.extract_test_id(
720 testcase_params,
721 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
722 # Check if reference test has been run and set attenuation accordingly
723 previous_params = [
724 self.extract_test_id(
725 result['testcase_params'],
726 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
727 for result in self.testclass_results
728 ]
729 try:
730 ref_index = previous_params[::-1].index(ref_test_params)
731 ref_index = len(previous_params) - 1 - ref_index
732 start_atten = self.testclass_results[ref_index][
733 'atten_at_range'] - (
734 self.testclass_params['adjacent_mcs_range_gap'])
735 except ValueError:
736 print('Reference test not found. Starting from {} dB'.format(
737 self.testclass_params['atten_start']))
738 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700739 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700740 return start_atten
741
Omar El Ayachffb5a462019-09-16 21:05:44 -0700742 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700743 angles):
744 """Function that auto-generates test cases for a test class."""
745 test_cases = []
746 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700747 requested_modes = set(modes).intersection(
748 set(self.VALID_TEST_CONFIGS[channel]))
749 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700750 if 'VHT' in mode:
751 valid_rates = self.VALID_RATES[mode]
752 elif 'HT' in mode:
753 valid_rates = self.VALID_RATES[mode]
754 elif 'legacy' in mode and channel < 14:
755 valid_rates = self.VALID_RATES['legacy_2GHz']
756 elif 'legacy' in mode and channel > 14:
757 valid_rates = self.VALID_RATES['legacy_5GHz']
758 else:
759 raise ValueError('Invalid test mode.')
760 for chain, rate, angle in itertools.product(
761 chain_mask, valid_rates, angles):
762 testcase_params = collections.OrderedDict(
763 channel=channel,
764 mode=mode,
765 rate=rate.mcs,
766 num_streams=rate.streams,
767 short_gi=1,
768 chain_mask=chain,
769 orientation=angle)
770 if rate not in requested_rates:
771 continue
772 if str(chain) in ['0', '1'] and rate[1] == 2:
773 # Do not test 2-stream rates in single chain mode
774 continue
775 if 'legacy' in mode:
776 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
777 '_ch{}_{}deg'.format(
778 channel, mode,
779 str(rate.mcs).replace('.', 'p'),
780 rate.streams, chain, angle))
781 else:
782 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
783 '_ch{}_{}deg'.format(
784 channel, mode, rate.mcs,
785 rate.streams, chain, angle))
786 setattr(self, testcase_name,
787 partial(self._test_sensitivity, testcase_params))
788 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700789 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700790
791
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700792class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700793 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700794 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700795 requested_channels = [6, 36, 149]
796 requested_rates = [
797 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700798 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700799 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700800 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700801 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700802 self.tests = self.generate_test_cases(
803 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
804 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700805
806
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700807class WifiOtaSensitivity_SingleChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700808 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700809 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700810 requested_channels = [6, 36, 149]
811 requested_rates = [
812 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700813 self.RateTuple(2, 1, 21.7)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700814 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700815 self.tests = self.generate_test_cases(
816 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
817 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700818
819
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700820class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700821 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700822 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700823 requested_rates = [
824 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700825 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700826 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700827 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700828 ]
829 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700830 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
831 requested_rates, ['2x2'], list(range(0, 360, 45)))