blob: 5b5c298d77ab5635b1535e114e2e14068f5e21a6 [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
Xianyuan Jia976d4042019-09-30 17:19:47 -070030from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070031from acts.test_utils.wifi import ota_chamber
Omar El Ayach6e518a22019-06-13 13:55:42 -070032from acts.test_utils.wifi import wifi_performance_test_utils as wputils
Omar El Ayach33f80c02018-09-27 15:02:03 -070033from acts.test_utils.wifi import wifi_test_utils as wutils
34from acts.test_utils.wifi import wifi_retail_ap as retail_ap
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070035from functools import partial
Omar El Ayach5fbc1222018-12-07 18:10:05 -080036from WifiRvrTest import WifiRvrTest
37from WifiPingTest import WifiPingTest
Omar El Ayach33f80c02018-09-27 15:02:03 -070038
39
Omar El Ayach5fbc1222018-12-07 18:10:05 -080040class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -070041 """Class to test WiFi sensitivity tests.
42
43 This class implements measures WiFi sensitivity per rate. It heavily
44 leverages the WifiRvrTest class and introduced minor differences to set
45 specific rates and the access point, and implements a different pass/fail
46 check. For an example config file to run this test class see
47 example_connectivity_performance_ap_sta.json.
48 """
49
Omar El Ayacha210d572019-03-14 17:31:38 -070050 RSSI_POLL_INTERVAL = 0.2
Omar El Ayach33f80c02018-09-27 15:02:03 -070051 VALID_TEST_CONFIGS = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070052 1: ['legacy', 'VHT20'],
53 2: ['legacy', 'VHT20'],
54 6: ['legacy', 'VHT20'],
55 10: ['legacy', 'VHT20'],
56 11: ['legacy', 'VHT20'],
57 36: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
58 40: ['legacy', 'VHT20'],
59 44: ['legacy', 'VHT20'],
60 48: ['legacy', 'VHT20'],
61 149: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
62 153: ['legacy', 'VHT20'],
63 157: ['legacy', 'VHT20'],
64 161: ['legacy', 'VHT20']
Omar El Ayach33f80c02018-09-27 15:02:03 -070065 }
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070066 RateTuple = collections.namedtuple(('RateTuple'),
67 ['mcs', 'streams', 'data_rate'])
Omar El Ayacha210d572019-03-14 17:31:38 -070068 #yapf:disable
Omar El Ayach33f80c02018-09-27 15:02:03 -070069 VALID_RATES = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070070 'legacy_2GHz': [
Omar El Ayacha210d572019-03-14 17:31:38 -070071 RateTuple(54, 1, 54), RateTuple(48, 1, 48),
72 RateTuple(36, 1, 36), RateTuple(24, 1, 24),
73 RateTuple(18, 1, 18), RateTuple(12, 1, 12),
74 RateTuple(11, 1, 11), RateTuple(9, 1, 9),
75 RateTuple(6, 1, 6), RateTuple(5.5, 1, 5.5),
76 RateTuple(2, 1, 2), RateTuple(1, 1, 1)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070077 'legacy_5GHz': [
Omar El Ayacha210d572019-03-14 17:31:38 -070078 RateTuple(54, 1, 54), RateTuple(48, 1, 48),
79 RateTuple(36, 1, 36), RateTuple(24, 1, 24),
80 RateTuple(18, 1, 18), RateTuple(12, 1, 12),
81 RateTuple(9, 1, 9), RateTuple(6, 1, 6)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070082 'HT20': [
Omar El Ayacha210d572019-03-14 17:31:38 -070083 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
84 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
85 RateTuple(3, 1, 26), RateTuple(2, 1, 21.7),
86 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
87 RateTuple(15, 2, 144.4), RateTuple(14, 2, 130),
88 RateTuple(13, 2, 115.6), RateTuple(12, 2, 86.7),
89 RateTuple(11, 2, 57.8), RateTuple(10, 2, 43.4),
90 RateTuple(9, 2, 28.9), RateTuple(8, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070091 'VHT20': [
Omar El Ayacha210d572019-03-14 17:31:38 -070092 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
93 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
94 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
95 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
96 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
97 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
98 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
99 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
100 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
101 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700102 'VHT40': [
Omar El Ayach03e40612019-05-01 16:25:39 -0700103 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
104 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
105 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
106 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
107 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
108 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
109 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
110 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
111 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
112 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700113 'VHT80': [
Omar El Ayach03e40612019-05-01 16:25:39 -0700114 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
115 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
116 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
117 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
118 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
119 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
120 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
121 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
122 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
123 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayach33f80c02018-09-27 15:02:03 -0700124 }
Omar El Ayacha210d572019-03-14 17:31:38 -0700125 #yapf:enable
Omar El Ayach33f80c02018-09-27 15:02:03 -0700126
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800127 def __init__(self, controllers):
128 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700129 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700130 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700131 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700132 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700133 self.publish_testcase_metrics = True
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800134
Omar El Ayach33f80c02018-09-27 15:02:03 -0700135 def setup_class(self):
136 """Initializes common test hardware and parameters.
137
138 This function initializes hardwares and compiles parameters that are
139 common to all tests in this class.
140 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700141 self.dut = self.android_devices[-1]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700142 req_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700143 'RetailAccessPoints', 'sensitivity_test_params', 'testbed_params',
144 'RemoteServer'
Omar El Ayach33f80c02018-09-27 15:02:03 -0700145 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700146 opt_params = ['main_network', 'golden_files_list']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700147 self.unpack_userparams(req_params, opt_params)
148 self.testclass_params = self.sensitivity_test_params
149 self.num_atten = self.attenuators[0].instrument.num_atten
Omar El Ayach14416ac2019-01-30 14:58:19 -0800150 self.ping_server = ssh.connection.SshConnection(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700151 ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700152 self.iperf_server = self.iperf_servers[0]
Omar El Ayach14416ac2019-01-30 14:58:19 -0800153 self.iperf_client = self.iperf_clients[0]
Omar El Ayacha210d572019-03-14 17:31:38 -0700154 self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700155 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700156 self.access_point.ap_settings))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700157 self.log_path = os.path.join(logging.log_path, 'results')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700158 utils.create_dir(self.log_path)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700159 if not hasattr(self, 'golden_files_list'):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700160 self.golden_files_list = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700161 os.path.join(self.testbed_params['golden_results_path'], file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700162 for file in os.listdir(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700163 self.testbed_params['golden_results_path'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700164 ]
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700165 if hasattr(self, 'bdf'):
166 self.log.info('Pushing WiFi BDF to DUT.')
167 wputils.push_bdf(self.dut, self.bdf)
168 if hasattr(self, 'firmware'):
169 self.log.info('Pushing WiFi firmware to DUT.')
170 wlanmdsp = [
171 file for file in self.firmware if "wlanmdsp.mbn" in file
172 ][0]
173 data_msc = [file for file in self.firmware
174 if "Data.msc" in file][0]
175 wputils.push_firmware(self.dut, wlanmdsp, data_msc)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800176 self.atten_dut_chain_map = {}
Omar El Ayach33f80c02018-09-27 15:02:03 -0700177 self.testclass_results = []
178
179 # Turn WiFi ON
Omar El Ayachd7109092019-09-29 18:31:33 -0700180 if self.testclass_params.get('airplane_mode', 1):
181 self.log.info('Turning on airplane mode.')
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800182 asserts.assert_true(utils.force_airplane_mode(self.dut, True),
183 "Can not turn on airplane mode.")
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700184 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700185
Omar El Ayach96714c82019-01-28 18:51:46 -0800186 def teardown_class(self):
187 # Turn WiFi OFF
188 for dev in self.android_devices:
189 wutils.wifi_toggle_state(dev, False)
190 self.process_testclass_results()
191
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800192 def pass_fail_check(self, result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700193 """Checks sensitivity against golden results and decides on pass/fail.
194
195 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800196 result: dict containing attenuation, throughput and other meta
Mark De Ruytere54396c2019-08-20 12:54:37 -0700197 data
Omar El Ayach33f80c02018-09-27 15:02:03 -0700198 """
199 try:
200 golden_path = next(file_name
201 for file_name in self.golden_files_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700202 if 'sensitivity_targets' in file_name)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700203 with open(golden_path, 'r') as golden_file:
204 golden_results = json.load(golden_file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700205 golden_sensitivity = golden_results[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700206 self.current_test_name]['sensitivity']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700207 except:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700208 golden_sensitivity = float('nan')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700209
Mark De Ruytere54396c2019-08-20 12:54:37 -0700210 result_string = ('Throughput = {}%, Sensitivity = {}.'
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700211 'Target Sensitivity = {}'.format(
212 result['peak_throughput_pct'],
213 result['sensitivity'], golden_sensitivity))
Omar El Ayache9725962019-09-18 17:30:17 -0700214 if result['peak_throughput_pct'] < 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700215 self.log.warning('Result unreliable. Peak rate unstable')
216 if result['sensitivity'] - golden_sensitivity < self.testclass_params[
217 'sensitivity_tolerance']:
218 asserts.explicit_pass('Test Passed. {}'.format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800219 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700220 asserts.fail('Test Failed. {}'.format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700221
222 def process_testclass_results(self):
223 """Saves and plots test results from all executed test cases."""
Omar El Ayach96714c82019-01-28 18:51:46 -0800224 # write json output
Omar El Ayach33f80c02018-09-27 15:02:03 -0700225 testclass_results_dict = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700226 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
Omar El Ayacha210d572019-03-14 17:31:38 -0700227 channels_tested = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700228 for result in self.testclass_results:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700229 testcase_params = result['testcase_params']
Omar El Ayacha210d572019-03-14 17:31:38 -0700230 test_id = collections.OrderedDict(
231 (key, value) for key, value in testcase_params.items()
232 if key in id_fields)
233 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700234 if test_id not in testclass_results_dict:
235 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700236 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700237 if channel not in channels_tested:
238 channels_tested.append(channel)
Omar El Ayache9725962019-09-18 17:30:17 -0700239 if result['peak_throughput_pct'] >= 95:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700240 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700241 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700242 else:
243 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700244
Omar El Ayach96714c82019-01-28 18:51:46 -0800245 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700246 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700247 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700248 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800249 results_file_path = os.path.join(self.log_path, 'results.csv')
250 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800251 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
252 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700253 for test_id, test_results in testclass_results_dict.items():
254 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700255 if 'legacy' in test_id_dict['mode']:
256 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700257 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700258 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700259 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700260 if rate[:-1] == (test_id_dict['rate'],
261 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700262 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700263 'Mode': test_id_dict['mode'],
264 'MCS': test_id_dict['rate'],
265 'Streams': test_id_dict['num_streams'],
266 'Chain': test_id_dict['chain_mask'],
267 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700268 }
269 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700270 row_value['Ch. ' + str(channel)] = test_results.pop(
271 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700272 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800273
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700274 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800275 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700276
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800277 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700278 """Post processes RvR results to compute sensitivity.
279
280 Takes in the results of the RvR tests and computes the sensitivity of
281 the current rate by looking at the point at which throughput drops
282 below the percentage specified in the config file. The function then
283 calls on its parent class process_test_results to plot the result.
284
285 Args:
286 rvr_result: dict containing attenuation, throughput and other meta
287 data
288 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700289 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
290 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700291 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700292 throughput < rvr_result['peak_throughput'] *
293 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
294 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700295 ]
296 consistency_check = [
297 idx for idx in range(len(throughput_check))
298 if all(throughput_check[idx:])
299 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700300 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700301 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700302 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
303 rvr_result['atten_at_range'])
304 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
305 self.testbed_params['ap_tx_power_offset'][str(
306 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800307 WifiRvrTest.process_test_results(self, rvr_result)
308
309 def process_ping_test_results(self, testcase_params, ping_result):
310 """Post processes RvR results to compute sensitivity.
311
312 Takes in the results of the RvR tests and computes the sensitivity of
313 the current rate by looking at the point at which throughput drops
314 below the percentage specified in the config file. The function then
315 calls on its parent class process_test_results to plot the result.
316
317 Args:
318 rvr_result: dict containing attenuation, throughput and other meta
319 data
320 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800321 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700322 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
323 self.testbed_params['ap_tx_power_offset'][str(
324 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700325
Omar El Ayach03e40612019-05-01 16:25:39 -0700326 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700327 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700328 self.setup_ping_test(testcase_params)
329 self.run_sensitivity_test = self.run_ping_test
330 self.process_sensitivity_test_results = (
331 self.process_ping_test_results)
332 else:
333 self.setup_rvr_test(testcase_params)
334 self.run_sensitivity_test = self.run_rvr_test
335 self.process_sensitivity_test_results = (
336 self.process_rvr_test_results)
337
Omar El Ayach33f80c02018-09-27 15:02:03 -0700338 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800339 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700340
341 Args:
342 testcase_params: dict containing AP and other test params
343 """
344 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700345 testcase_params['channel'])
346 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700347 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700348 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700349 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700350 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700351 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700352 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700353 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700354 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700355 self.access_point.set_region(self.testbed_params['default_region'])
356 self.access_point.set_channel(band, testcase_params['channel'])
357 self.access_point.set_bandwidth(band, testcase_params['mode'])
358 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800359 self.access_point.set_rate(band, testcase_params['mode'],
360 testcase_params['num_streams'],
361 testcase_params['rate'],
362 testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800363 # Set attenuator offsets and set attenuators to initial condition
364 atten_offsets = self.testbed_params['chain_offset'][str(
365 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800366 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800367 if 'AP-Chain-0' in atten.path:
368 atten.offset = atten_offsets[0]
369 elif 'AP-Chain-1' in atten.path:
370 atten.offset = atten_offsets[1]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800371 else:
372 atten.offset = 0
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700373 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700374 self.access_point.ap_settings))
375
Omar El Ayach6e518a22019-06-13 13:55:42 -0700376 def setup_dut(self, testcase_params):
377 """Sets up the DUT in the configuration required by the test.
378
379 Args:
380 testcase_params: dict containing AP and other test params
381 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700382 # Check battery level before test
383 if not wputils.health_check(self.dut, 10):
384 asserts.skip('Battery level too low. Skipping test.')
385 # Turn screen off to preserve battery
386 self.dut.go_to_sleep()
Omar El Ayach6e518a22019-06-13 13:55:42 -0700387 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700388 testcase_params['channel'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700389 current_network = self.dut.droid.wifiGetConnectionInfo()
Omar El Ayachfc74c4d2019-09-27 18:15:48 -0700390 try:
391 connected = wutils.validate_connection(self.dut) is not None
392 except:
393 connected = False
394 if connected and current_network['SSID'] == self.main_network[band][
395 'SSID']:
Omar El Ayach39acf802019-08-02 17:52:39 -0700396 self.log.info('Already connected to desired network')
397 else:
398 wutils.reset_wifi(self.dut)
Girish Moturu2b7afe72019-09-17 20:35:54 +0000399 self.dut.droid.wifiSetCountryCode(
400 self.testclass_params['country_code'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700401 self.main_network[band]['channel'] = testcase_params['channel']
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800402 wutils.wifi_connect(self.dut,
403 self.main_network[band],
404 num_of_tries=5,
405 check_connectivity=False)
Omar El Ayach39acf802019-08-02 17:52:39 -0700406 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800407 self.atten_dut_chain_map.setdefault(
408 testcase_params['channel'],
409 wputils.get_current_atten_dut_chain_map(self.attenuators, self.dut,
410 self.ping_server))
411 self.log.info("Current Attenuator-DUT Chain Map: {}".format(
412 self.atten_dut_chain_map[testcase_params['channel']]))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700413 for idx, atten in enumerate(self.attenuators):
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800414 if self.atten_dut_chain_map[testcase_params['channel']][
415 idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700416 atten.offset = atten.instrument.max_atten
417
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700418 def extract_test_id(self, testcase_params, id_fields):
419 test_id = collections.OrderedDict(
420 (param, testcase_params[param]) for param in id_fields)
421 return test_id
422
423 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700424 """Gets the starting attenuation for this sensitivity test.
425
426 The function gets the starting attenuation by checking whether a test
427 as the next higher MCS has been executed. If so it sets the starting
428 point a configurable number of dBs below the next MCS's sensitivity.
429
430 Returns:
431 start_atten: starting attenuation for current test
432 """
433 # Get the current and reference test config. The reference test is the
434 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700435 current_rate = testcase_params['rate']
436 ref_test_params = self.extract_test_id(
437 testcase_params,
438 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
439 if 'legacy' in testcase_params['mode']:
440 if testcase_params['channel'] <= 13:
441 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700442 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700443 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700444 ref_index = max(
445 0,
446 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
447 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700448 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700449 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700450 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700451
452 # Check if reference test has been run and set attenuation accordingly
453 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700454 self.extract_test_id(
455 result['testcase_params'],
456 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700457 for result in self.testclass_results
458 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700459
Omar El Ayach33f80c02018-09-27 15:02:03 -0700460 try:
461 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700462 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700463 'atten_at_range'] - (
464 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700465 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700466 self.log.warning(
467 'Reference test not found. Starting from {} dB'.format(
468 self.testclass_params['atten_start']))
469 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700470 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700471 return start_atten
472
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700473 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700474 """Function that generates test params based on the test name."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700475 if testcase_params['chain_mask'] in ['0', '1']:
476 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
477 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700478 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700479 # Set attenuated chain to -1. Do not set to None as this will be
480 # compared to RF chain map which may include None
481 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800482
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700483 self.testclass_params[
484 'range_ping_loss_threshold'] = 100 - self.testclass_params[
485 'throughput_pct_at_sensitivity']
486 if self.testclass_params['traffic_type'] == 'UDP':
487 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
488 self.testclass_params['iperf_duration'],
489 self.testclass_params['UDP_rates'][testcase_params['mode']])
490 elif self.testclass_params['traffic_type'] == 'TCP':
491 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
492 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800493
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700494 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700495 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700496 testcase_params['iperf_args'] += ' -R'
497 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800498 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700499 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800500
Omar El Ayach33f80c02018-09-27 15:02:03 -0700501 return testcase_params
502
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700503 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700504 """ Function that gets called for each test case
505
506 The function gets called in each rvr test case. The function customizes
507 the rvr test based on the test name of the test that called it
508 """
509 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700510 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700511 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700512 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800513 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700514 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
515 testcase_params['atten_step'])
516 testcase_params['atten_range'] = [
517 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800518 for x in range(0, num_atten_steps)
519 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700520
521 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700522 self.setup_sensitivity_test(testcase_params)
523 result = self.run_sensitivity_test(testcase_params)
524 self.process_sensitivity_test_results(testcase_params, result)
525
Omar El Ayach33f80c02018-09-27 15:02:03 -0700526 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800527 self.testclass_results.append(result)
528 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700529
Omar El Ayachffb5a462019-09-16 21:05:44 -0700530 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700531 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700532 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700533 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700534 requested_modes = set(modes).intersection(
535 set(self.VALID_TEST_CONFIGS[channel]))
536 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700537 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700538 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700539 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700540 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700541 elif 'legacy' in mode and channel < 14:
542 rates = self.VALID_RATES['legacy_2GHz']
543 elif 'legacy' in mode and channel > 14:
544 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700545 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700546 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800547 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700548 testcase_params = collections.OrderedDict(
549 channel=channel,
550 mode=mode,
551 rate=rate.mcs,
552 num_streams=rate.streams,
553 short_gi=1,
554 chain_mask=chain)
555 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800556 # Do not test 2-stream rates in single chain mode
557 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700558 if 'legacy' in mode:
559 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
560 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700561 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700562 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700563 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700564 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700565 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
566 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700567 channel, mode, rate.mcs,
568 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700569 setattr(self, testcase_name,
570 partial(self._test_sensitivity, testcase_params))
571 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700572 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700573
574
575class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
576 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700577 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700578 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700579 [6, 36, 40, 44, 48, 149, 153, 157, 161],
580 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700581
582
583class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
584 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700585 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800586 self.tests = self.generate_test_cases([6, 36, 149],
587 ['VHT20', 'VHT40', 'VHT80'],
588 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700589
590
591class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
592 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700593 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700594 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
595 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700596
597
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800598class WifiSensitivity_5GHz_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(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700602 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
603 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800604
605
Omar El Ayach33f80c02018-09-27 15:02:03 -0700606class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
607 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700608 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800609 self.tests = self.generate_test_cases([36, 40, 44, 48],
610 ['VHT20', 'VHT40', 'VHT80'],
611 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700612
613
614class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
615 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700616 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700617 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700618 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700619 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700620
621
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700622# Over-the air version of senstivity tests
623class WifiOtaSensitivityTest(WifiSensitivityTest):
624 """Class to test over-the-air senstivity.
625
626 This class implements measures WiFi sensitivity tests in an OTA chamber.
627 It allows setting orientation and other chamber parameters to study
628 performance in varying channel conditions
629 """
Omar El Ayach40099d02019-09-12 15:17:33 -0700630 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700631 base_test.BaseTestClass.__init__(self, controllers)
632 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700633 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700634 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700635 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700636 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700637
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700638 def setup_class(self):
639 WifiSensitivityTest.setup_class(self)
640 self.ota_chamber = ota_chamber.create(
641 self.user_params['OTAChamber'])[0]
642
643 def teardown_class(self):
644 WifiSensitivityTest.teardown_class(self)
645 self.ota_chamber.reset_chamber()
646
647 def setup_sensitivity_test(self, testcase_params):
648 # Setup turntable
649 self.ota_chamber.set_orientation(testcase_params['orientation'])
650 # Continue test setup
651 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
652
653 def process_testclass_results(self):
654 """Saves and plots test results from all executed test cases."""
655 testclass_results_dict = collections.OrderedDict()
656 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
657 plots = []
658 for result in self.testclass_results:
659 test_id = self.extract_test_id(result['testcase_params'],
660 id_fields)
661 test_id = tuple(test_id.items())
662 channel = result['testcase_params']['channel']
663 if test_id not in testclass_results_dict:
664 testclass_results_dict[test_id] = collections.OrderedDict()
665 if channel not in testclass_results_dict[test_id]:
666 testclass_results_dict[test_id][channel] = {
667 'orientation': [],
668 'sensitivity': []
669 }
670 testclass_results_dict[test_id][channel]['orientation'].append(
671 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700672 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700673 testclass_results_dict[test_id][channel]['sensitivity'].append(
674 result['sensitivity'])
675 else:
676 testclass_results_dict[test_id][channel]['sensitivity'].append(
677 float('nan'))
678
679 for test_id, test_data in testclass_results_dict.items():
680 test_id_dict = dict(test_id)
681 if 'legacy' in test_id_dict['mode']:
682 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
683 test_id_dict['mode'], test_id_dict['rate'],
684 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700685 metric_test_config = '{}_{}_ch{}'.format(
686 test_id_dict['mode'], test_id_dict['rate'],
687 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700688 else:
689 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
690 test_id_dict['mode'], test_id_dict['rate'],
691 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700692 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
693 test_id_dict['mode'], test_id_dict['rate'],
694 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700695 curr_plot = wputils.BokehFigure(
696 title=str(test_id_str),
697 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700698 primary_y_label='Sensitivity (dBm)')
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700699 for channel, channel_results in test_data.items():
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800700 curr_plot.add_line(channel_results['orientation'],
701 channel_results['sensitivity'],
702 legend='Channel {}'.format(channel),
703 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700704 metric_tag = 'ota_summary_ch{}_{}'.format(
705 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700706 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700707 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700708 self.testclass_metric_logger.add_metric(
709 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700710 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
711 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700712 current_context = (
713 context.get_current_context().get_full_output_path())
714 output_file_path = os.path.join(current_context,
715 str(test_id_str) + '.html')
716 curr_plot.generate_figure(output_file_path)
717 plots.append(curr_plot)
718 output_file_path = os.path.join(current_context, 'results.html')
719 wputils.BokehFigure.save_figures(plots, output_file_path)
720
721 def get_start_atten(self, testcase_params):
722 """Gets the starting attenuation for this sensitivity test.
723
724 The function gets the starting attenuation by checking whether a test
725 at the same rate configuration has executed. If so it sets the starting
726 point a configurable number of dBs below the reference test.
727
728 Returns:
729 start_atten: starting attenuation for current test
730 """
731 # Get the current and reference test config. The reference test is the
732 # one performed at the current MCS+1
733 ref_test_params = self.extract_test_id(
734 testcase_params,
735 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
736 # Check if reference test has been run and set attenuation accordingly
737 previous_params = [
738 self.extract_test_id(
739 result['testcase_params'],
740 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
741 for result in self.testclass_results
742 ]
743 try:
744 ref_index = previous_params[::-1].index(ref_test_params)
745 ref_index = len(previous_params) - 1 - ref_index
746 start_atten = self.testclass_results[ref_index][
747 'atten_at_range'] - (
748 self.testclass_params['adjacent_mcs_range_gap'])
749 except ValueError:
750 print('Reference test not found. Starting from {} dB'.format(
751 self.testclass_params['atten_start']))
752 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700753 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700754 return start_atten
755
Omar El Ayachffb5a462019-09-16 21:05:44 -0700756 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700757 angles):
758 """Function that auto-generates test cases for a test class."""
759 test_cases = []
760 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700761 requested_modes = set(modes).intersection(
762 set(self.VALID_TEST_CONFIGS[channel]))
763 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700764 if 'VHT' in mode:
765 valid_rates = self.VALID_RATES[mode]
766 elif 'HT' in mode:
767 valid_rates = self.VALID_RATES[mode]
768 elif 'legacy' in mode and channel < 14:
769 valid_rates = self.VALID_RATES['legacy_2GHz']
770 elif 'legacy' in mode and channel > 14:
771 valid_rates = self.VALID_RATES['legacy_5GHz']
772 else:
773 raise ValueError('Invalid test mode.')
774 for chain, rate, angle in itertools.product(
775 chain_mask, valid_rates, angles):
776 testcase_params = collections.OrderedDict(
777 channel=channel,
778 mode=mode,
779 rate=rate.mcs,
780 num_streams=rate.streams,
781 short_gi=1,
782 chain_mask=chain,
783 orientation=angle)
784 if rate not in requested_rates:
785 continue
786 if str(chain) in ['0', '1'] and rate[1] == 2:
787 # Do not test 2-stream rates in single chain mode
788 continue
789 if 'legacy' in mode:
790 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
791 '_ch{}_{}deg'.format(
792 channel, mode,
793 str(rate.mcs).replace('.', 'p'),
794 rate.streams, chain, angle))
795 else:
796 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
797 '_ch{}_{}deg'.format(
798 channel, mode, rate.mcs,
799 rate.streams, chain, angle))
800 setattr(self, testcase_name,
801 partial(self._test_sensitivity, testcase_params))
802 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700803 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700804
805
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700806class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700807 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700808 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700809 requested_channels = [6, 36, 149]
810 requested_rates = [
811 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700812 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700813 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700814 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700815 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800816 self.tests = self.generate_test_cases(requested_channels,
817 ['VHT20', 'VHT80'],
818 requested_rates, ['2x2'],
819 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700820
821
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700822class WifiOtaSensitivity_SingleChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700823 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700824 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700825 requested_channels = [6, 36, 149]
826 requested_rates = [
827 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700828 self.RateTuple(2, 1, 21.7)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700829 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800830 self.tests = self.generate_test_cases(requested_channels,
831 ['VHT20', 'VHT80'],
832 requested_rates, ['2x2'],
833 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700834
835
Omar El Ayachd3850d02019-09-23 16:35:49 -0700836class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
837 def __init__(self, controllers):
838 WifiOtaSensitivityTest.__init__(self, controllers)
839 requested_channels = [6, 36, 149]
840 requested_rates = [
841 self.RateTuple(9, 1, 96),
842 self.RateTuple(8, 1, 86.7),
843 self.RateTuple(7, 1, 72.2),
844 self.RateTuple(4, 1, 43.3),
845 self.RateTuple(2, 1, 21.7),
846 self.RateTuple(0, 1, 7.2),
847 self.RateTuple(9, 2, 192),
848 self.RateTuple(8, 2, 173.3),
849 self.RateTuple(7, 2, 144.4),
850 self.RateTuple(4, 2, 86.7),
851 self.RateTuple(2, 2, 43.3),
852 self.RateTuple(0, 2, 14.4)
853 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800854 self.tests = self.generate_test_cases(requested_channels,
855 ['VHT20', 'VHT80'],
856 requested_rates, ['2x2'],
857 list(range(0, 360, 30)))
Omar El Ayachd3850d02019-09-23 16:35:49 -0700858
859
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700860class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700861 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700862 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700863 requested_rates = [
864 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700865 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700866 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700867 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700868 ]
869 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700870 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
871 requested_rates, ['2x2'], list(range(0, 360, 45)))