blob: 2eea8436151134609f72b779537d5cd52c04f100 [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 Ayach39acf802019-08-02 17:52:39 -0700387 current_network = self.dut.droid.wifiGetConnectionInfo()
Omar El Ayachfc74c4d2019-09-27 18:15:48 -0700388 try:
389 connected = wutils.validate_connection(self.dut) is not None
390 except:
391 connected = False
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800392 if connected and current_network['SSID'] == testcase_params[
393 'test_network']['SSID']:
Omar El Ayach39acf802019-08-02 17:52:39 -0700394 self.log.info('Already connected to desired network')
395 else:
396 wutils.reset_wifi(self.dut)
Girish Moturu2b7afe72019-09-17 20:35:54 +0000397 self.dut.droid.wifiSetCountryCode(
398 self.testclass_params['country_code'])
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800399 testcase_params['test_network']['channel'] = testcase_params[
400 'channel']
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800401 wutils.wifi_connect(self.dut,
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800402 testcase_params['test_network'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800403 num_of_tries=5,
404 check_connectivity=False)
Omar El Ayach39acf802019-08-02 17:52:39 -0700405 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayach16f5f342019-11-23 12:27:56 -0800406 if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
407 self.atten_dut_chain_map[testcase_params[
408 'channel']] = wputils.get_current_atten_dut_chain_map(
409 self.attenuators, self.dut, self.ping_server)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800410 self.log.info("Current Attenuator-DUT Chain Map: {}".format(
411 self.atten_dut_chain_map[testcase_params['channel']]))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700412 for idx, atten in enumerate(self.attenuators):
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800413 if self.atten_dut_chain_map[testcase_params['channel']][
414 idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700415 atten.offset = atten.instrument.max_atten
416
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700417 def extract_test_id(self, testcase_params, id_fields):
418 test_id = collections.OrderedDict(
419 (param, testcase_params[param]) for param in id_fields)
420 return test_id
421
422 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700423 """Gets the starting attenuation for this sensitivity test.
424
425 The function gets the starting attenuation by checking whether a test
426 as the next higher MCS has been executed. If so it sets the starting
427 point a configurable number of dBs below the next MCS's sensitivity.
428
429 Returns:
430 start_atten: starting attenuation for current test
431 """
432 # Get the current and reference test config. The reference test is the
433 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700434 current_rate = testcase_params['rate']
435 ref_test_params = self.extract_test_id(
436 testcase_params,
437 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
438 if 'legacy' in testcase_params['mode']:
439 if testcase_params['channel'] <= 13:
440 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700441 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700442 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700443 ref_index = max(
444 0,
445 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
446 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700447 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700448 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700449 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700450
451 # Check if reference test has been run and set attenuation accordingly
452 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700453 self.extract_test_id(
454 result['testcase_params'],
455 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700456 for result in self.testclass_results
457 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700458
Omar El Ayach33f80c02018-09-27 15:02:03 -0700459 try:
460 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700461 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700462 'atten_at_range'] - (
463 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700464 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700465 self.log.warning(
466 'Reference test not found. Starting from {} dB'.format(
467 self.testclass_params['atten_start']))
468 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700469 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700470 return start_atten
471
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700472 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700473 """Function that generates test params based on the test name."""
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800474 band = self.access_point.band_lookup_by_channel(
475 testcase_params['channel'])
476 testcase_params['test_network'] = self.main_network[band]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700477 if testcase_params['chain_mask'] in ['0', '1']:
478 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
479 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700480 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700481 # Set attenuated chain to -1. Do not set to None as this will be
482 # compared to RF chain map which may include None
483 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800484
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700485 self.testclass_params[
486 'range_ping_loss_threshold'] = 100 - self.testclass_params[
487 'throughput_pct_at_sensitivity']
488 if self.testclass_params['traffic_type'] == 'UDP':
489 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
490 self.testclass_params['iperf_duration'],
491 self.testclass_params['UDP_rates'][testcase_params['mode']])
492 elif self.testclass_params['traffic_type'] == 'TCP':
493 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
494 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800495
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700496 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700497 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700498 testcase_params['iperf_args'] += ' -R'
499 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800500 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700501 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800502
Omar El Ayach33f80c02018-09-27 15:02:03 -0700503 return testcase_params
504
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700505 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700506 """ Function that gets called for each test case
507
508 The function gets called in each rvr test case. The function customizes
509 the rvr test based on the test name of the test that called it
510 """
511 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700512 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700513 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700514 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800515 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700516 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
517 testcase_params['atten_step'])
518 testcase_params['atten_range'] = [
519 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800520 for x in range(0, num_atten_steps)
521 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700522
523 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700524 self.setup_sensitivity_test(testcase_params)
525 result = self.run_sensitivity_test(testcase_params)
526 self.process_sensitivity_test_results(testcase_params, result)
527
Omar El Ayach33f80c02018-09-27 15:02:03 -0700528 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800529 self.testclass_results.append(result)
530 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700531
Omar El Ayachffb5a462019-09-16 21:05:44 -0700532 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700533 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700534 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700535 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700536 requested_modes = set(modes).intersection(
537 set(self.VALID_TEST_CONFIGS[channel]))
538 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700539 if 'VHT' 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 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700542 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700543 elif 'legacy' in mode and channel < 14:
544 rates = self.VALID_RATES['legacy_2GHz']
545 elif 'legacy' in mode and channel > 14:
546 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700547 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700548 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800549 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700550 testcase_params = collections.OrderedDict(
551 channel=channel,
552 mode=mode,
553 rate=rate.mcs,
554 num_streams=rate.streams,
555 short_gi=1,
556 chain_mask=chain)
557 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800558 # Do not test 2-stream rates in single chain mode
559 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700560 if 'legacy' in mode:
561 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
562 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700563 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700564 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700565 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700566 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700567 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
568 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700569 channel, mode, rate.mcs,
570 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700571 setattr(self, testcase_name,
572 partial(self._test_sensitivity, testcase_params))
573 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700574 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700575
576
577class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
578 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700579 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700580 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700581 [6, 36, 40, 44, 48, 149, 153, 157, 161],
582 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700583
584
585class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
586 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700587 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800588 self.tests = self.generate_test_cases([6, 36, 149],
589 ['VHT20', 'VHT40', 'VHT80'],
590 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700591
592
593class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
594 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700595 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700596 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
597 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700598
599
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800600class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
601 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700602 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700603 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700604 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
605 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800606
607
Omar El Ayach33f80c02018-09-27 15:02:03 -0700608class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
609 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700610 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800611 self.tests = self.generate_test_cases([36, 40, 44, 48],
612 ['VHT20', 'VHT40', 'VHT80'],
613 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700614
615
616class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
617 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700618 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700619 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700620 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700621 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700622
623
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700624# Over-the air version of senstivity tests
625class WifiOtaSensitivityTest(WifiSensitivityTest):
626 """Class to test over-the-air senstivity.
627
628 This class implements measures WiFi sensitivity tests in an OTA chamber.
629 It allows setting orientation and other chamber parameters to study
630 performance in varying channel conditions
631 """
Omar El Ayach40099d02019-09-12 15:17:33 -0700632 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700633 base_test.BaseTestClass.__init__(self, controllers)
634 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700635 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700636 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700637 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700638 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700639
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700640 def setup_class(self):
641 WifiSensitivityTest.setup_class(self)
642 self.ota_chamber = ota_chamber.create(
643 self.user_params['OTAChamber'])[0]
644
645 def teardown_class(self):
646 WifiSensitivityTest.teardown_class(self)
647 self.ota_chamber.reset_chamber()
648
649 def setup_sensitivity_test(self, testcase_params):
650 # Setup turntable
651 self.ota_chamber.set_orientation(testcase_params['orientation'])
652 # Continue test setup
653 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
654
655 def process_testclass_results(self):
656 """Saves and plots test results from all executed test cases."""
657 testclass_results_dict = collections.OrderedDict()
658 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
659 plots = []
660 for result in self.testclass_results:
661 test_id = self.extract_test_id(result['testcase_params'],
662 id_fields)
663 test_id = tuple(test_id.items())
664 channel = result['testcase_params']['channel']
665 if test_id not in testclass_results_dict:
666 testclass_results_dict[test_id] = collections.OrderedDict()
667 if channel not in testclass_results_dict[test_id]:
668 testclass_results_dict[test_id][channel] = {
669 'orientation': [],
670 'sensitivity': []
671 }
672 testclass_results_dict[test_id][channel]['orientation'].append(
673 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700674 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700675 testclass_results_dict[test_id][channel]['sensitivity'].append(
676 result['sensitivity'])
677 else:
678 testclass_results_dict[test_id][channel]['sensitivity'].append(
679 float('nan'))
680
681 for test_id, test_data in testclass_results_dict.items():
682 test_id_dict = dict(test_id)
683 if 'legacy' in test_id_dict['mode']:
684 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
685 test_id_dict['mode'], test_id_dict['rate'],
686 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700687 metric_test_config = '{}_{}_ch{}'.format(
688 test_id_dict['mode'], test_id_dict['rate'],
689 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700690 else:
691 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
692 test_id_dict['mode'], test_id_dict['rate'],
693 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700694 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
695 test_id_dict['mode'], test_id_dict['rate'],
696 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700697 curr_plot = wputils.BokehFigure(
698 title=str(test_id_str),
699 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700700 primary_y_label='Sensitivity (dBm)')
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700701 for channel, channel_results in test_data.items():
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800702 curr_plot.add_line(channel_results['orientation'],
703 channel_results['sensitivity'],
704 legend='Channel {}'.format(channel),
705 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700706 metric_tag = 'ota_summary_ch{}_{}'.format(
707 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700708 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700709 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700710 self.testclass_metric_logger.add_metric(
711 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700712 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
713 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700714 current_context = (
715 context.get_current_context().get_full_output_path())
716 output_file_path = os.path.join(current_context,
717 str(test_id_str) + '.html')
718 curr_plot.generate_figure(output_file_path)
719 plots.append(curr_plot)
720 output_file_path = os.path.join(current_context, 'results.html')
721 wputils.BokehFigure.save_figures(plots, output_file_path)
722
723 def get_start_atten(self, testcase_params):
724 """Gets the starting attenuation for this sensitivity test.
725
726 The function gets the starting attenuation by checking whether a test
727 at the same rate configuration has executed. If so it sets the starting
728 point a configurable number of dBs below the reference test.
729
730 Returns:
731 start_atten: starting attenuation for current test
732 """
733 # Get the current and reference test config. The reference test is the
734 # one performed at the current MCS+1
735 ref_test_params = self.extract_test_id(
736 testcase_params,
737 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
738 # Check if reference test has been run and set attenuation accordingly
739 previous_params = [
740 self.extract_test_id(
741 result['testcase_params'],
742 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
743 for result in self.testclass_results
744 ]
745 try:
746 ref_index = previous_params[::-1].index(ref_test_params)
747 ref_index = len(previous_params) - 1 - ref_index
748 start_atten = self.testclass_results[ref_index][
749 'atten_at_range'] - (
750 self.testclass_params['adjacent_mcs_range_gap'])
751 except ValueError:
752 print('Reference test not found. Starting from {} dB'.format(
753 self.testclass_params['atten_start']))
754 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700755 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700756 return start_atten
757
Omar El Ayachffb5a462019-09-16 21:05:44 -0700758 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700759 angles):
760 """Function that auto-generates test cases for a test class."""
761 test_cases = []
762 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700763 requested_modes = set(modes).intersection(
764 set(self.VALID_TEST_CONFIGS[channel]))
765 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700766 if 'VHT' in mode:
767 valid_rates = self.VALID_RATES[mode]
768 elif 'HT' in mode:
769 valid_rates = self.VALID_RATES[mode]
770 elif 'legacy' in mode and channel < 14:
771 valid_rates = self.VALID_RATES['legacy_2GHz']
772 elif 'legacy' in mode and channel > 14:
773 valid_rates = self.VALID_RATES['legacy_5GHz']
774 else:
775 raise ValueError('Invalid test mode.')
776 for chain, rate, angle in itertools.product(
777 chain_mask, valid_rates, angles):
778 testcase_params = collections.OrderedDict(
779 channel=channel,
780 mode=mode,
781 rate=rate.mcs,
782 num_streams=rate.streams,
783 short_gi=1,
784 chain_mask=chain,
785 orientation=angle)
786 if rate not in requested_rates:
787 continue
788 if str(chain) in ['0', '1'] and rate[1] == 2:
789 # Do not test 2-stream rates in single chain mode
790 continue
791 if 'legacy' in mode:
792 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
793 '_ch{}_{}deg'.format(
794 channel, mode,
795 str(rate.mcs).replace('.', 'p'),
796 rate.streams, chain, angle))
797 else:
798 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
799 '_ch{}_{}deg'.format(
800 channel, mode, rate.mcs,
801 rate.streams, chain, angle))
802 setattr(self, testcase_name,
803 partial(self._test_sensitivity, testcase_params))
804 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700805 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700806
807
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700808class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700809 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700810 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700811 requested_channels = [6, 36, 149]
812 requested_rates = [
813 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700814 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700815 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700816 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700817 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800818 self.tests = self.generate_test_cases(requested_channels,
819 ['VHT20', 'VHT80'],
820 requested_rates, ['2x2'],
821 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700822
823
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700824class WifiOtaSensitivity_SingleChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700825 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700826 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700827 requested_channels = [6, 36, 149]
828 requested_rates = [
829 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700830 self.RateTuple(2, 1, 21.7)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700831 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800832 self.tests = self.generate_test_cases(requested_channels,
833 ['VHT20', 'VHT80'],
834 requested_rates, ['2x2'],
835 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700836
837
Omar El Ayachd3850d02019-09-23 16:35:49 -0700838class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
839 def __init__(self, controllers):
840 WifiOtaSensitivityTest.__init__(self, controllers)
841 requested_channels = [6, 36, 149]
842 requested_rates = [
843 self.RateTuple(9, 1, 96),
844 self.RateTuple(8, 1, 86.7),
845 self.RateTuple(7, 1, 72.2),
846 self.RateTuple(4, 1, 43.3),
847 self.RateTuple(2, 1, 21.7),
848 self.RateTuple(0, 1, 7.2),
849 self.RateTuple(9, 2, 192),
850 self.RateTuple(8, 2, 173.3),
851 self.RateTuple(7, 2, 144.4),
852 self.RateTuple(4, 2, 86.7),
853 self.RateTuple(2, 2, 43.3),
854 self.RateTuple(0, 2, 14.4)
855 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800856 self.tests = self.generate_test_cases(requested_channels,
857 ['VHT20', 'VHT80'],
858 requested_rates, ['2x2'],
859 list(range(0, 360, 30)))
Omar El Ayachd3850d02019-09-23 16:35:49 -0700860
861
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700862class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700863 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700864 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700865 requested_rates = [
866 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700867 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700868 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700869 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700870 ]
871 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700872 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
873 requested_rates, ['2x2'], list(range(0, 360, 45)))