blob: c340756571172ec7b92cfa28d22025d0b1718a9e [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 Ayach55f51b52019-12-06 17:56:23 -0800230 test_id = self.extract_test_id(testcase_params, id_fields)
Omar El Ayacha210d572019-03-14 17:31:38 -0700231 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700232 if test_id not in testclass_results_dict:
233 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700234 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700235 if channel not in channels_tested:
236 channels_tested.append(channel)
Omar El Ayache9725962019-09-18 17:30:17 -0700237 if result['peak_throughput_pct'] >= 95:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700238 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700239 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700240 else:
241 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700242
Omar El Ayach55f51b52019-12-06 17:56:23 -0800243 # calculate average metrics
244 metrics_dict = collections.OrderedDict()
245 id_fields = ['channel', 'mode', 'num_streams', 'chain_mask']
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800246 for test_id in testclass_results_dict.keys():
247 for channel in testclass_results_dict[test_id].keys():
248 metric_tag = collections.OrderedDict(test_id, channel=channel)
249 metric_tag = self.extract_test_id(metric_tag, id_fields)
250 metric_tag = tuple(metric_tag.items())
251 metrics_dict.setdefault(metric_tag, [])
252 sensitivity_result = testclass_results_dict[test_id][channel]
253 if sensitivity_result != '':
254 metrics_dict[metric_tag].append(sensitivity_result)
Omar El Ayach55f51b52019-12-06 17:56:23 -0800255 for metric_tag_tuple, metric_data in metrics_dict.items():
256 metric_tag_dict = collections.OrderedDict(metric_tag_tuple)
257 metric_tag = 'ch{}_{}_nss{}_chain{}'.format(
258 metric_tag_dict['channel'], metric_tag_dict['mode'],
259 metric_tag_dict['num_streams'], metric_tag_dict['chain_mask'])
260 metric_key = "{}.avg_sensitivity".format(metric_tag)
261 metric_value = numpy.nanmean(metric_data)
262 self.testclass_metric_logger.add_metric(metric_key, metric_value)
263
Omar El Ayach96714c82019-01-28 18:51:46 -0800264 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700265 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700266 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700267 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800268 results_file_path = os.path.join(self.log_path, 'results.csv')
269 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800270 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
271 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700272 for test_id, test_results in testclass_results_dict.items():
273 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700274 if 'legacy' in test_id_dict['mode']:
275 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700276 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700277 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700278 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700279 if rate[:-1] == (test_id_dict['rate'],
280 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700281 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700282 'Mode': test_id_dict['mode'],
283 'MCS': test_id_dict['rate'],
284 'Streams': test_id_dict['num_streams'],
285 'Chain': test_id_dict['chain_mask'],
286 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700287 }
288 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700289 row_value['Ch. ' + str(channel)] = test_results.pop(
290 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700291 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800292
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700293 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800294 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700295
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800296 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700297 """Post processes RvR results to compute sensitivity.
298
299 Takes in the results of the RvR tests and computes the sensitivity of
300 the current rate by looking at the point at which throughput drops
301 below the percentage specified in the config file. The function then
302 calls on its parent class process_test_results to plot the result.
303
304 Args:
305 rvr_result: dict containing attenuation, throughput and other meta
306 data
307 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700308 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
309 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700310 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700311 throughput < rvr_result['peak_throughput'] *
312 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
313 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700314 ]
315 consistency_check = [
316 idx for idx in range(len(throughput_check))
317 if all(throughput_check[idx:])
318 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700319 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700320 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700321 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
322 rvr_result['atten_at_range'])
323 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
324 self.testbed_params['ap_tx_power_offset'][str(
325 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800326 WifiRvrTest.process_test_results(self, rvr_result)
327
328 def process_ping_test_results(self, testcase_params, ping_result):
329 """Post processes RvR results to compute sensitivity.
330
331 Takes in the results of the RvR tests and computes the sensitivity of
332 the current rate by looking at the point at which throughput drops
333 below the percentage specified in the config file. The function then
334 calls on its parent class process_test_results to plot the result.
335
336 Args:
337 rvr_result: dict containing attenuation, throughput and other meta
338 data
339 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800340 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700341 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
342 self.testbed_params['ap_tx_power_offset'][str(
343 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700344
Omar El Ayach03e40612019-05-01 16:25:39 -0700345 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700346 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700347 self.setup_ping_test(testcase_params)
348 self.run_sensitivity_test = self.run_ping_test
349 self.process_sensitivity_test_results = (
350 self.process_ping_test_results)
351 else:
352 self.setup_rvr_test(testcase_params)
353 self.run_sensitivity_test = self.run_rvr_test
354 self.process_sensitivity_test_results = (
355 self.process_rvr_test_results)
356
Omar El Ayach33f80c02018-09-27 15:02:03 -0700357 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800358 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700359
360 Args:
361 testcase_params: dict containing AP and other test params
362 """
363 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700364 testcase_params['channel'])
365 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700366 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700367 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700368 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700369 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700370 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700371 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700372 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700373 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700374 self.access_point.set_region(self.testbed_params['default_region'])
375 self.access_point.set_channel(band, testcase_params['channel'])
376 self.access_point.set_bandwidth(band, testcase_params['mode'])
377 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800378 self.access_point.set_rate(band, testcase_params['mode'],
379 testcase_params['num_streams'],
380 testcase_params['rate'],
381 testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800382 # Set attenuator offsets and set attenuators to initial condition
383 atten_offsets = self.testbed_params['chain_offset'][str(
384 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800385 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800386 if 'AP-Chain-0' in atten.path:
387 atten.offset = atten_offsets[0]
388 elif 'AP-Chain-1' in atten.path:
389 atten.offset = atten_offsets[1]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800390 else:
391 atten.offset = 0
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700392 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700393 self.access_point.ap_settings))
394
Omar El Ayach6e518a22019-06-13 13:55:42 -0700395 def setup_dut(self, testcase_params):
396 """Sets up the DUT in the configuration required by the test.
397
398 Args:
399 testcase_params: dict containing AP and other test params
400 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700401 # Check battery level before test
402 if not wputils.health_check(self.dut, 10):
403 asserts.skip('Battery level too low. Skipping test.')
404 # Turn screen off to preserve battery
405 self.dut.go_to_sleep()
Omar El Ayach35bf3b82019-12-06 19:29:19 -0800406 if wputils.validate_network(self.dut,
407 testcase_params['test_network']['SSID']):
Omar El Ayach39acf802019-08-02 17:52:39 -0700408 self.log.info('Already connected to desired network')
409 else:
410 wutils.reset_wifi(self.dut)
Roshan Pius5b19a122019-09-13 08:07:30 -0700411 wutils.set_wifi_country_code(self.dut,
Girish Moturu2b7afe72019-09-17 20:35:54 +0000412 self.testclass_params['country_code'])
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800413 testcase_params['test_network']['channel'] = testcase_params[
414 'channel']
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800415 wutils.wifi_connect(self.dut,
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800416 testcase_params['test_network'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800417 num_of_tries=5,
418 check_connectivity=False)
Omar El Ayach39acf802019-08-02 17:52:39 -0700419 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayach16f5f342019-11-23 12:27:56 -0800420 if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
421 self.atten_dut_chain_map[testcase_params[
422 'channel']] = wputils.get_current_atten_dut_chain_map(
423 self.attenuators, self.dut, self.ping_server)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800424 self.log.info("Current Attenuator-DUT Chain Map: {}".format(
425 self.atten_dut_chain_map[testcase_params['channel']]))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700426 for idx, atten in enumerate(self.attenuators):
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800427 if self.atten_dut_chain_map[testcase_params['channel']][
428 idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700429 atten.offset = atten.instrument.max_atten
430
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700431 def extract_test_id(self, testcase_params, id_fields):
432 test_id = collections.OrderedDict(
433 (param, testcase_params[param]) for param in id_fields)
434 return test_id
435
436 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700437 """Gets the starting attenuation for this sensitivity test.
438
439 The function gets the starting attenuation by checking whether a test
440 as the next higher MCS has been executed. If so it sets the starting
441 point a configurable number of dBs below the next MCS's sensitivity.
442
443 Returns:
444 start_atten: starting attenuation for current test
445 """
446 # Get the current and reference test config. The reference test is the
447 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700448 current_rate = testcase_params['rate']
449 ref_test_params = self.extract_test_id(
450 testcase_params,
451 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
452 if 'legacy' in testcase_params['mode']:
453 if testcase_params['channel'] <= 13:
454 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700455 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700456 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700457 ref_index = max(
458 0,
459 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
460 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700461 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700462 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700463 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700464
465 # Check if reference test has been run and set attenuation accordingly
466 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700467 self.extract_test_id(
468 result['testcase_params'],
469 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700470 for result in self.testclass_results
471 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700472
Omar El Ayach33f80c02018-09-27 15:02:03 -0700473 try:
474 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700475 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700476 'atten_at_range'] - (
477 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700478 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700479 self.log.warning(
480 'Reference test not found. Starting from {} dB'.format(
481 self.testclass_params['atten_start']))
482 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700483 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700484 return start_atten
485
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700486 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700487 """Function that generates test params based on the test name."""
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800488 band = self.access_point.band_lookup_by_channel(
489 testcase_params['channel'])
490 testcase_params['test_network'] = self.main_network[band]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700491 if testcase_params['chain_mask'] in ['0', '1']:
492 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
493 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700494 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700495 # Set attenuated chain to -1. Do not set to None as this will be
496 # compared to RF chain map which may include None
497 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800498
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700499 self.testclass_params[
500 'range_ping_loss_threshold'] = 100 - self.testclass_params[
501 'throughput_pct_at_sensitivity']
502 if self.testclass_params['traffic_type'] == 'UDP':
503 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
504 self.testclass_params['iperf_duration'],
505 self.testclass_params['UDP_rates'][testcase_params['mode']])
506 elif self.testclass_params['traffic_type'] == 'TCP':
507 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
508 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800509
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700510 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700511 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700512 testcase_params['iperf_args'] += ' -R'
513 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800514 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700515 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800516
Omar El Ayach33f80c02018-09-27 15:02:03 -0700517 return testcase_params
518
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700519 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700520 """ Function that gets called for each test case
521
522 The function gets called in each rvr test case. The function customizes
523 the rvr test based on the test name of the test that called it
524 """
525 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700526 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700527 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700528 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800529 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700530 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
531 testcase_params['atten_step'])
532 testcase_params['atten_range'] = [
533 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800534 for x in range(0, num_atten_steps)
535 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700536
537 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700538 self.setup_sensitivity_test(testcase_params)
539 result = self.run_sensitivity_test(testcase_params)
540 self.process_sensitivity_test_results(testcase_params, result)
541
Omar El Ayach33f80c02018-09-27 15:02:03 -0700542 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800543 self.testclass_results.append(result)
544 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700545
Omar El Ayachffb5a462019-09-16 21:05:44 -0700546 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700547 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700548 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700549 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800550 requested_modes = [
551 mode for mode in modes
552 if mode in self.VALID_TEST_CONFIGS[channel]
553 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700554 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700555 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700556 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700557 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700558 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700559 elif 'legacy' in mode and channel < 14:
560 rates = self.VALID_RATES['legacy_2GHz']
561 elif 'legacy' in mode and channel > 14:
562 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700563 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700564 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800565 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700566 testcase_params = collections.OrderedDict(
567 channel=channel,
568 mode=mode,
569 rate=rate.mcs,
570 num_streams=rate.streams,
571 short_gi=1,
572 chain_mask=chain)
573 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800574 # Do not test 2-stream rates in single chain mode
575 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700576 if 'legacy' in mode:
577 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
578 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700579 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700580 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700581 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700582 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700583 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
584 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700585 channel, mode, rate.mcs,
586 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700587 setattr(self, testcase_name,
588 partial(self._test_sensitivity, testcase_params))
589 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700590 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700591
592
593class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
594 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700595 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700596 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700597 [6, 36, 40, 44, 48, 149, 153, 157, 161],
598 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700599
600
601class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
602 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700603 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800604 self.tests = self.generate_test_cases([6, 36, 149],
605 ['VHT20', 'VHT40', 'VHT80'],
606 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700607
608
609class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
610 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700611 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700612 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
613 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700614
615
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800616class WifiSensitivity_5GHz_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(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700620 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
621 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800622
623
Omar El Ayach33f80c02018-09-27 15:02:03 -0700624class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
625 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700626 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800627 self.tests = self.generate_test_cases([36, 40, 44, 48],
628 ['VHT20', 'VHT40', 'VHT80'],
629 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700630
631
632class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
633 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700634 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700635 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700636 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700637 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700638
639
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700640# Over-the air version of senstivity tests
641class WifiOtaSensitivityTest(WifiSensitivityTest):
642 """Class to test over-the-air senstivity.
643
644 This class implements measures WiFi sensitivity tests in an OTA chamber.
645 It allows setting orientation and other chamber parameters to study
646 performance in varying channel conditions
647 """
Omar El Ayach40099d02019-09-12 15:17:33 -0700648 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700649 base_test.BaseTestClass.__init__(self, controllers)
650 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700651 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700652 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700653 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700654 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700655
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700656 def setup_class(self):
657 WifiSensitivityTest.setup_class(self)
658 self.ota_chamber = ota_chamber.create(
659 self.user_params['OTAChamber'])[0]
660
661 def teardown_class(self):
662 WifiSensitivityTest.teardown_class(self)
663 self.ota_chamber.reset_chamber()
664
665 def setup_sensitivity_test(self, testcase_params):
666 # Setup turntable
667 self.ota_chamber.set_orientation(testcase_params['orientation'])
668 # Continue test setup
669 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
670
671 def process_testclass_results(self):
672 """Saves and plots test results from all executed test cases."""
673 testclass_results_dict = collections.OrderedDict()
674 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
675 plots = []
676 for result in self.testclass_results:
677 test_id = self.extract_test_id(result['testcase_params'],
678 id_fields)
679 test_id = tuple(test_id.items())
680 channel = result['testcase_params']['channel']
681 if test_id not in testclass_results_dict:
682 testclass_results_dict[test_id] = collections.OrderedDict()
683 if channel not in testclass_results_dict[test_id]:
684 testclass_results_dict[test_id][channel] = {
685 'orientation': [],
686 'sensitivity': []
687 }
688 testclass_results_dict[test_id][channel]['orientation'].append(
689 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700690 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700691 testclass_results_dict[test_id][channel]['sensitivity'].append(
692 result['sensitivity'])
693 else:
694 testclass_results_dict[test_id][channel]['sensitivity'].append(
695 float('nan'))
696
697 for test_id, test_data in testclass_results_dict.items():
698 test_id_dict = dict(test_id)
699 if 'legacy' in test_id_dict['mode']:
700 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
701 test_id_dict['mode'], test_id_dict['rate'],
702 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700703 metric_test_config = '{}_{}_ch{}'.format(
704 test_id_dict['mode'], test_id_dict['rate'],
705 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700706 else:
707 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
708 test_id_dict['mode'], test_id_dict['rate'],
709 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700710 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
711 test_id_dict['mode'], test_id_dict['rate'],
712 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700713 curr_plot = wputils.BokehFigure(
714 title=str(test_id_str),
715 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700716 primary_y_label='Sensitivity (dBm)')
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700717 for channel, channel_results in test_data.items():
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800718 curr_plot.add_line(channel_results['orientation'],
719 channel_results['sensitivity'],
720 legend='Channel {}'.format(channel),
721 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700722 metric_tag = 'ota_summary_ch{}_{}'.format(
723 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700724 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700725 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700726 self.testclass_metric_logger.add_metric(
727 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700728 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
729 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700730 current_context = (
731 context.get_current_context().get_full_output_path())
732 output_file_path = os.path.join(current_context,
733 str(test_id_str) + '.html')
734 curr_plot.generate_figure(output_file_path)
735 plots.append(curr_plot)
736 output_file_path = os.path.join(current_context, 'results.html')
737 wputils.BokehFigure.save_figures(plots, output_file_path)
738
739 def get_start_atten(self, testcase_params):
740 """Gets the starting attenuation for this sensitivity test.
741
742 The function gets the starting attenuation by checking whether a test
743 at the same rate configuration has executed. If so it sets the starting
744 point a configurable number of dBs below the reference test.
745
746 Returns:
747 start_atten: starting attenuation for current test
748 """
749 # Get the current and reference test config. The reference test is the
750 # one performed at the current MCS+1
751 ref_test_params = self.extract_test_id(
752 testcase_params,
753 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
754 # Check if reference test has been run and set attenuation accordingly
755 previous_params = [
756 self.extract_test_id(
757 result['testcase_params'],
758 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
759 for result in self.testclass_results
760 ]
761 try:
762 ref_index = previous_params[::-1].index(ref_test_params)
763 ref_index = len(previous_params) - 1 - ref_index
764 start_atten = self.testclass_results[ref_index][
765 'atten_at_range'] - (
766 self.testclass_params['adjacent_mcs_range_gap'])
767 except ValueError:
768 print('Reference test not found. Starting from {} dB'.format(
769 self.testclass_params['atten_start']))
770 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700771 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700772 return start_atten
773
Omar El Ayachffb5a462019-09-16 21:05:44 -0700774 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700775 angles):
776 """Function that auto-generates test cases for a test class."""
777 test_cases = []
778 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800779 requested_modes = [
780 mode for mode in modes
781 if mode in self.VALID_TEST_CONFIGS[channel]
782 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700783 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700784 if 'VHT' in mode:
785 valid_rates = self.VALID_RATES[mode]
786 elif 'HT' in mode:
787 valid_rates = self.VALID_RATES[mode]
788 elif 'legacy' in mode and channel < 14:
789 valid_rates = self.VALID_RATES['legacy_2GHz']
790 elif 'legacy' in mode and channel > 14:
791 valid_rates = self.VALID_RATES['legacy_5GHz']
792 else:
793 raise ValueError('Invalid test mode.')
794 for chain, rate, angle in itertools.product(
795 chain_mask, valid_rates, angles):
796 testcase_params = collections.OrderedDict(
797 channel=channel,
798 mode=mode,
799 rate=rate.mcs,
800 num_streams=rate.streams,
801 short_gi=1,
802 chain_mask=chain,
803 orientation=angle)
804 if rate not in requested_rates:
805 continue
806 if str(chain) in ['0', '1'] and rate[1] == 2:
807 # Do not test 2-stream rates in single chain mode
808 continue
809 if 'legacy' in mode:
810 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
811 '_ch{}_{}deg'.format(
812 channel, mode,
813 str(rate.mcs).replace('.', 'p'),
814 rate.streams, chain, angle))
815 else:
816 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
817 '_ch{}_{}deg'.format(
818 channel, mode, rate.mcs,
819 rate.streams, chain, angle))
820 setattr(self, testcase_name,
821 partial(self._test_sensitivity, testcase_params))
822 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700823 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700824
825
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700826class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700827 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700828 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700829 requested_channels = [6, 36, 149]
830 requested_rates = [
831 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700832 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700833 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700834 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700835 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800836 self.tests = self.generate_test_cases(requested_channels,
837 ['VHT20', 'VHT80'],
838 requested_rates, ['2x2'],
839 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700840
841
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700842class WifiOtaSensitivity_SingleChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700843 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700844 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700845 requested_channels = [6, 36, 149]
846 requested_rates = [
847 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700848 self.RateTuple(2, 1, 21.7)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700849 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800850 self.tests = self.generate_test_cases(requested_channels,
851 ['VHT20', 'VHT80'],
852 requested_rates, ['2x2'],
853 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700854
855
Omar El Ayachd3850d02019-09-23 16:35:49 -0700856class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
857 def __init__(self, controllers):
858 WifiOtaSensitivityTest.__init__(self, controllers)
859 requested_channels = [6, 36, 149]
860 requested_rates = [
861 self.RateTuple(9, 1, 96),
862 self.RateTuple(8, 1, 86.7),
863 self.RateTuple(7, 1, 72.2),
864 self.RateTuple(4, 1, 43.3),
865 self.RateTuple(2, 1, 21.7),
866 self.RateTuple(0, 1, 7.2),
867 self.RateTuple(9, 2, 192),
868 self.RateTuple(8, 2, 173.3),
869 self.RateTuple(7, 2, 144.4),
870 self.RateTuple(4, 2, 86.7),
871 self.RateTuple(2, 2, 43.3),
872 self.RateTuple(0, 2, 14.4)
873 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800874 self.tests = self.generate_test_cases(requested_channels,
875 ['VHT20', 'VHT80'],
876 requested_rates, ['2x2'],
877 list(range(0, 360, 30)))
Omar El Ayachd3850d02019-09-23 16:35:49 -0700878
879
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700880class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700881 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700882 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700883 requested_rates = [
884 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700885 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700886 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700887 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700888 ]
889 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700890 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
891 requested_rates, ['2x2'], list(range(0, 360, 45)))