blob: a982cc6972cb60d52854a87c46034f47e5062181 [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 Ayach33f80c02018-09-27 15:02:03 -0700176 self.testclass_results = []
177
178 # Turn WiFi ON
Omar El Ayachd7109092019-09-29 18:31:33 -0700179 if self.testclass_params.get('airplane_mode', 1):
180 self.log.info('Turning on airplane mode.')
181 asserts.assert_true(
182 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 Ayach33f80c02018-09-27 15:02:03 -0700359 self.access_point.set_rate(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700360 band, testcase_params['mode'], testcase_params['num_streams'],
361 testcase_params['rate'], testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800362 # Set attenuator offsets and set attenuators to initial condition
363 atten_offsets = self.testbed_params['chain_offset'][str(
364 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800365 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800366 if 'AP-Chain-0' in atten.path:
367 atten.offset = atten_offsets[0]
368 elif 'AP-Chain-1' in atten.path:
369 atten.offset = atten_offsets[1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700370 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700371 self.access_point.ap_settings))
372
Omar El Ayach6e518a22019-06-13 13:55:42 -0700373 def setup_dut(self, testcase_params):
374 """Sets up the DUT in the configuration required by the test.
375
376 Args:
377 testcase_params: dict containing AP and other test params
378 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700379 # Check battery level before test
380 if not wputils.health_check(self.dut, 10):
381 asserts.skip('Battery level too low. Skipping test.')
382 # Turn screen off to preserve battery
383 self.dut.go_to_sleep()
Omar El Ayach6e518a22019-06-13 13:55:42 -0700384 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700385 testcase_params['channel'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700386 current_network = self.dut.droid.wifiGetConnectionInfo()
Omar El Ayachfc74c4d2019-09-27 18:15:48 -0700387 try:
388 connected = wutils.validate_connection(self.dut) is not None
389 except:
390 connected = False
391 if connected and current_network['SSID'] == self.main_network[band][
392 'SSID']:
Omar El Ayach39acf802019-08-02 17:52:39 -0700393 self.log.info('Already connected to desired network')
394 else:
395 wutils.reset_wifi(self.dut)
Girish Moturu2b7afe72019-09-17 20:35:54 +0000396 self.dut.droid.wifiSetCountryCode(
397 self.testclass_params['country_code'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700398 self.main_network[band]['channel'] = testcase_params['channel']
399 wutils.wifi_connect(
400 self.dut,
401 self.main_network[band],
402 num_of_tries=5,
403 check_connectivity=False)
404 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayach189dcbd2019-08-28 17:26:18 -0700405 atten_dut_chain_map = wputils.get_current_atten_dut_chain_map(
406 self.attenuators, self.dut, self.ping_server)
Omar El Ayach49141c02019-09-16 16:43:51 -0700407 self.log.info(
408 "Current Attenuator-DUT Chain Map: {}".format(atten_dut_chain_map))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700409 for idx, atten in enumerate(self.attenuators):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700410 if atten_dut_chain_map[idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700411 atten.offset = atten.instrument.max_atten
412
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700413 def extract_test_id(self, testcase_params, id_fields):
414 test_id = collections.OrderedDict(
415 (param, testcase_params[param]) for param in id_fields)
416 return test_id
417
418 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700419 """Gets the starting attenuation for this sensitivity test.
420
421 The function gets the starting attenuation by checking whether a test
422 as the next higher MCS has been executed. If so it sets the starting
423 point a configurable number of dBs below the next MCS's sensitivity.
424
425 Returns:
426 start_atten: starting attenuation for current test
427 """
428 # Get the current and reference test config. The reference test is the
429 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700430 current_rate = testcase_params['rate']
431 ref_test_params = self.extract_test_id(
432 testcase_params,
433 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
434 if 'legacy' in testcase_params['mode']:
435 if testcase_params['channel'] <= 13:
436 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700437 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700438 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700439 ref_index = max(
440 0,
441 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
442 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700443 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700444 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700445 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700446
447 # Check if reference test has been run and set attenuation accordingly
448 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700449 self.extract_test_id(
450 result['testcase_params'],
451 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700452 for result in self.testclass_results
453 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700454
Omar El Ayach33f80c02018-09-27 15:02:03 -0700455 try:
456 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700457 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700458 'atten_at_range'] - (
459 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700460 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700461 self.log.warning(
462 'Reference test not found. Starting from {} dB'.format(
463 self.testclass_params['atten_start']))
464 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700465 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700466 return start_atten
467
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700468 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700469 """Function that generates test params based on the test name."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700470 if testcase_params['chain_mask'] in ['0', '1']:
471 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
472 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700473 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700474 # Set attenuated chain to -1. Do not set to None as this will be
475 # compared to RF chain map which may include None
476 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800477
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700478 self.testclass_params[
479 'range_ping_loss_threshold'] = 100 - self.testclass_params[
480 'throughput_pct_at_sensitivity']
481 if self.testclass_params['traffic_type'] == 'UDP':
482 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
483 self.testclass_params['iperf_duration'],
484 self.testclass_params['UDP_rates'][testcase_params['mode']])
485 elif self.testclass_params['traffic_type'] == 'TCP':
486 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
487 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800488
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700489 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700490 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700491 testcase_params['iperf_args'] += ' -R'
492 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800493 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700494 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800495
Omar El Ayach33f80c02018-09-27 15:02:03 -0700496 return testcase_params
497
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700498 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700499 """ Function that gets called for each test case
500
501 The function gets called in each rvr test case. The function customizes
502 the rvr test based on the test name of the test that called it
503 """
504 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700505 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700506 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700507 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800508 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700509 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
510 testcase_params['atten_step'])
511 testcase_params['atten_range'] = [
512 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800513 for x in range(0, num_atten_steps)
514 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700515
516 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700517 self.setup_sensitivity_test(testcase_params)
518 result = self.run_sensitivity_test(testcase_params)
519 self.process_sensitivity_test_results(testcase_params, result)
520
Omar El Ayach33f80c02018-09-27 15:02:03 -0700521 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800522 self.testclass_results.append(result)
523 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700524
Omar El Ayachffb5a462019-09-16 21:05:44 -0700525 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700526 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700527 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700528 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700529 requested_modes = set(modes).intersection(
530 set(self.VALID_TEST_CONFIGS[channel]))
531 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700532 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700533 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700534 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700535 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700536 elif 'legacy' in mode and channel < 14:
537 rates = self.VALID_RATES['legacy_2GHz']
538 elif 'legacy' in mode and channel > 14:
539 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700540 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700541 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800542 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700543 testcase_params = collections.OrderedDict(
544 channel=channel,
545 mode=mode,
546 rate=rate.mcs,
547 num_streams=rate.streams,
548 short_gi=1,
549 chain_mask=chain)
550 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800551 # Do not test 2-stream rates in single chain mode
552 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700553 if 'legacy' in mode:
554 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
555 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700556 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700557 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700558 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700559 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700560 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
561 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700562 channel, mode, rate.mcs,
563 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700564 setattr(self, testcase_name,
565 partial(self._test_sensitivity, testcase_params))
566 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700567 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700568
569
570class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
571 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700572 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700573 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700574 [6, 36, 40, 44, 48, 149, 153, 157, 161],
575 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700576
577
578class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
579 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700580 super().__init__(controllers)
Omar El Ayachffb5a462019-09-16 21:05:44 -0700581 self.tests = self.generate_test_cases(
582 [6, 36, 149], ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700583
584
585class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
586 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700587 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700588 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
589 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700590
591
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800592class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
593 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700594 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700595 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700596 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
597 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800598
599
Omar El Ayach33f80c02018-09-27 15:02:03 -0700600class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
601 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700602 super().__init__(controllers)
Omar El Ayachffb5a462019-09-16 21:05:44 -0700603 self.tests = self.generate_test_cases(
604 [36, 40, 44, 48], ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700605
606
607class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
608 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700609 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700610 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700611 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700612 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700613
614
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700615# Over-the air version of senstivity tests
616class WifiOtaSensitivityTest(WifiSensitivityTest):
617 """Class to test over-the-air senstivity.
618
619 This class implements measures WiFi sensitivity tests in an OTA chamber.
620 It allows setting orientation and other chamber parameters to study
621 performance in varying channel conditions
622 """
623
Omar El Ayach40099d02019-09-12 15:17:33 -0700624 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700625 base_test.BaseTestClass.__init__(self, controllers)
626 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700627 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700628 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700629 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700630 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700631
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700632 def setup_class(self):
633 WifiSensitivityTest.setup_class(self)
634 self.ota_chamber = ota_chamber.create(
635 self.user_params['OTAChamber'])[0]
636
637 def teardown_class(self):
638 WifiSensitivityTest.teardown_class(self)
639 self.ota_chamber.reset_chamber()
640
641 def setup_sensitivity_test(self, testcase_params):
642 # Setup turntable
643 self.ota_chamber.set_orientation(testcase_params['orientation'])
644 # Continue test setup
645 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
646
647 def process_testclass_results(self):
648 """Saves and plots test results from all executed test cases."""
649 testclass_results_dict = collections.OrderedDict()
650 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
651 plots = []
652 for result in self.testclass_results:
653 test_id = self.extract_test_id(result['testcase_params'],
654 id_fields)
655 test_id = tuple(test_id.items())
656 channel = result['testcase_params']['channel']
657 if test_id not in testclass_results_dict:
658 testclass_results_dict[test_id] = collections.OrderedDict()
659 if channel not in testclass_results_dict[test_id]:
660 testclass_results_dict[test_id][channel] = {
661 'orientation': [],
662 'sensitivity': []
663 }
664 testclass_results_dict[test_id][channel]['orientation'].append(
665 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700666 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700667 testclass_results_dict[test_id][channel]['sensitivity'].append(
668 result['sensitivity'])
669 else:
670 testclass_results_dict[test_id][channel]['sensitivity'].append(
671 float('nan'))
672
673 for test_id, test_data in testclass_results_dict.items():
674 test_id_dict = dict(test_id)
675 if 'legacy' in test_id_dict['mode']:
676 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
677 test_id_dict['mode'], test_id_dict['rate'],
678 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700679 metric_test_config = '{}_{}_ch{}'.format(
680 test_id_dict['mode'], test_id_dict['rate'],
681 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700682 else:
683 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
684 test_id_dict['mode'], test_id_dict['rate'],
685 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700686 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
687 test_id_dict['mode'], test_id_dict['rate'],
688 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700689 curr_plot = wputils.BokehFigure(
690 title=str(test_id_str),
691 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700692 primary_y_label='Sensitivity (dBm)')
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700693 for channel, channel_results in test_data.items():
694 curr_plot.add_line(
695 channel_results['orientation'],
696 channel_results['sensitivity'],
Omar El Ayache9725962019-09-18 17:30:17 -0700697 legend='Channel {}'.format(channel),
698 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700699 metric_tag = 'ota_summary_ch{}_{}'.format(
700 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700701 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700702 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700703 self.testclass_metric_logger.add_metric(
704 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700705 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
706 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700707 current_context = (
708 context.get_current_context().get_full_output_path())
709 output_file_path = os.path.join(current_context,
710 str(test_id_str) + '.html')
711 curr_plot.generate_figure(output_file_path)
712 plots.append(curr_plot)
713 output_file_path = os.path.join(current_context, 'results.html')
714 wputils.BokehFigure.save_figures(plots, output_file_path)
715
716 def get_start_atten(self, testcase_params):
717 """Gets the starting attenuation for this sensitivity test.
718
719 The function gets the starting attenuation by checking whether a test
720 at the same rate configuration has executed. If so it sets the starting
721 point a configurable number of dBs below the reference test.
722
723 Returns:
724 start_atten: starting attenuation for current test
725 """
726 # Get the current and reference test config. The reference test is the
727 # one performed at the current MCS+1
728 ref_test_params = self.extract_test_id(
729 testcase_params,
730 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
731 # Check if reference test has been run and set attenuation accordingly
732 previous_params = [
733 self.extract_test_id(
734 result['testcase_params'],
735 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
736 for result in self.testclass_results
737 ]
738 try:
739 ref_index = previous_params[::-1].index(ref_test_params)
740 ref_index = len(previous_params) - 1 - ref_index
741 start_atten = self.testclass_results[ref_index][
742 'atten_at_range'] - (
743 self.testclass_params['adjacent_mcs_range_gap'])
744 except ValueError:
745 print('Reference test not found. Starting from {} dB'.format(
746 self.testclass_params['atten_start']))
747 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700748 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700749 return start_atten
750
Omar El Ayachffb5a462019-09-16 21:05:44 -0700751 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700752 angles):
753 """Function that auto-generates test cases for a test class."""
754 test_cases = []
755 for channel in channels:
Omar El Ayachffb5a462019-09-16 21:05:44 -0700756 requested_modes = set(modes).intersection(
757 set(self.VALID_TEST_CONFIGS[channel]))
758 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700759 if 'VHT' in mode:
760 valid_rates = self.VALID_RATES[mode]
761 elif 'HT' in mode:
762 valid_rates = self.VALID_RATES[mode]
763 elif 'legacy' in mode and channel < 14:
764 valid_rates = self.VALID_RATES['legacy_2GHz']
765 elif 'legacy' in mode and channel > 14:
766 valid_rates = self.VALID_RATES['legacy_5GHz']
767 else:
768 raise ValueError('Invalid test mode.')
769 for chain, rate, angle in itertools.product(
770 chain_mask, valid_rates, angles):
771 testcase_params = collections.OrderedDict(
772 channel=channel,
773 mode=mode,
774 rate=rate.mcs,
775 num_streams=rate.streams,
776 short_gi=1,
777 chain_mask=chain,
778 orientation=angle)
779 if rate not in requested_rates:
780 continue
781 if str(chain) in ['0', '1'] and rate[1] == 2:
782 # Do not test 2-stream rates in single chain mode
783 continue
784 if 'legacy' in mode:
785 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
786 '_ch{}_{}deg'.format(
787 channel, mode,
788 str(rate.mcs).replace('.', 'p'),
789 rate.streams, chain, angle))
790 else:
791 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
792 '_ch{}_{}deg'.format(
793 channel, mode, rate.mcs,
794 rate.streams, chain, angle))
795 setattr(self, testcase_name,
796 partial(self._test_sensitivity, testcase_params))
797 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700798 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700799
800
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700801class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700802 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700803 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700804 requested_channels = [6, 36, 149]
805 requested_rates = [
806 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700807 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700808 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700809 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700810 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700811 self.tests = self.generate_test_cases(
812 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
813 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700814
815
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700816class WifiOtaSensitivity_SingleChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700817 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700818 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700819 requested_channels = [6, 36, 149]
820 requested_rates = [
821 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700822 self.RateTuple(2, 1, 21.7)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700823 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700824 self.tests = self.generate_test_cases(
825 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
826 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700827
828
Omar El Ayachd3850d02019-09-23 16:35:49 -0700829class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
830 def __init__(self, controllers):
831 WifiOtaSensitivityTest.__init__(self, controllers)
832 requested_channels = [6, 36, 149]
833 requested_rates = [
834 self.RateTuple(9, 1, 96),
835 self.RateTuple(8, 1, 86.7),
836 self.RateTuple(7, 1, 72.2),
837 self.RateTuple(4, 1, 43.3),
838 self.RateTuple(2, 1, 21.7),
839 self.RateTuple(0, 1, 7.2),
840 self.RateTuple(9, 2, 192),
841 self.RateTuple(8, 2, 173.3),
842 self.RateTuple(7, 2, 144.4),
843 self.RateTuple(4, 2, 86.7),
844 self.RateTuple(2, 2, 43.3),
845 self.RateTuple(0, 2, 14.4)
846 ]
847 self.tests = self.generate_test_cases(
848 requested_channels, ['VHT20', 'VHT80'], requested_rates, ['2x2'],
849 list(range(0, 360, 30)))
850
851
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700852class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700853 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700854 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700855 requested_rates = [
856 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700857 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700858 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700859 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700860 ]
861 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700862 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
863 requested_rates, ['2x2'], list(range(0, 360, 45)))