blob: 005b05486326d0724627058e1a0e51ff8e721749 [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,
Omar El Ayache5b8be12019-10-01 16:27:12 -0700412 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 Ayache5b8be12019-10-01 16:27:12 -0700420 # Activate/attenuate the correct chains
Omar El Ayach16f5f342019-11-23 12:27:56 -0800421 if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
422 self.atten_dut_chain_map[testcase_params[
423 'channel']] = wputils.get_current_atten_dut_chain_map(
424 self.attenuators, self.dut, self.ping_server)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800425 self.log.info("Current Attenuator-DUT Chain Map: {}".format(
426 self.atten_dut_chain_map[testcase_params['channel']]))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700427 for idx, atten in enumerate(self.attenuators):
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800428 if self.atten_dut_chain_map[testcase_params['channel']][
429 idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700430 atten.offset = atten.instrument.max_atten
431
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700432 def extract_test_id(self, testcase_params, id_fields):
433 test_id = collections.OrderedDict(
434 (param, testcase_params[param]) for param in id_fields)
435 return test_id
436
437 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700438 """Gets the starting attenuation for this sensitivity test.
439
440 The function gets the starting attenuation by checking whether a test
441 as the next higher MCS has been executed. If so it sets the starting
442 point a configurable number of dBs below the next MCS's sensitivity.
443
444 Returns:
445 start_atten: starting attenuation for current test
446 """
447 # Get the current and reference test config. The reference test is the
448 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700449 current_rate = testcase_params['rate']
450 ref_test_params = self.extract_test_id(
451 testcase_params,
452 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
453 if 'legacy' in testcase_params['mode']:
454 if testcase_params['channel'] <= 13:
455 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700456 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700457 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700458 ref_index = max(
459 0,
460 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
461 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700462 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700463 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700464 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700465
466 # Check if reference test has been run and set attenuation accordingly
467 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700468 self.extract_test_id(
469 result['testcase_params'],
470 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700471 for result in self.testclass_results
472 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700473
Omar El Ayach33f80c02018-09-27 15:02:03 -0700474 try:
475 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700476 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700477 'atten_at_range'] - (
478 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700479 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700480 self.log.warning(
481 'Reference test not found. Starting from {} dB'.format(
482 self.testclass_params['atten_start']))
483 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700484 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700485 return start_atten
486
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700487 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700488 """Function that generates test params based on the test name."""
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800489 band = self.access_point.band_lookup_by_channel(
490 testcase_params['channel'])
491 testcase_params['test_network'] = self.main_network[band]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700492 if testcase_params['chain_mask'] in ['0', '1']:
493 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
494 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700495 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700496 # Set attenuated chain to -1. Do not set to None as this will be
497 # compared to RF chain map which may include None
498 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800499
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700500 self.testclass_params[
501 'range_ping_loss_threshold'] = 100 - self.testclass_params[
502 'throughput_pct_at_sensitivity']
503 if self.testclass_params['traffic_type'] == 'UDP':
504 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
505 self.testclass_params['iperf_duration'],
506 self.testclass_params['UDP_rates'][testcase_params['mode']])
507 elif self.testclass_params['traffic_type'] == 'TCP':
508 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
509 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800510
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700511 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700512 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700513 testcase_params['iperf_args'] += ' -R'
514 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800515 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700516 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800517
Omar El Ayach33f80c02018-09-27 15:02:03 -0700518 return testcase_params
519
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700520 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700521 """ Function that gets called for each test case
522
523 The function gets called in each rvr test case. The function customizes
524 the rvr test based on the test name of the test that called it
525 """
526 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700527 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700528 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700529 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800530 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700531 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
532 testcase_params['atten_step'])
533 testcase_params['atten_range'] = [
534 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800535 for x in range(0, num_atten_steps)
536 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700537
538 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700539 self.setup_sensitivity_test(testcase_params)
540 result = self.run_sensitivity_test(testcase_params)
541 self.process_sensitivity_test_results(testcase_params, result)
542
Omar El Ayach33f80c02018-09-27 15:02:03 -0700543 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800544 self.testclass_results.append(result)
545 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700546
Omar El Ayachffb5a462019-09-16 21:05:44 -0700547 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700548 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700549 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700550 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800551 requested_modes = [
552 mode for mode in modes
553 if mode in self.VALID_TEST_CONFIGS[channel]
554 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700555 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700556 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700557 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700558 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700559 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700560 elif 'legacy' in mode and channel < 14:
561 rates = self.VALID_RATES['legacy_2GHz']
562 elif 'legacy' in mode and channel > 14:
563 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700564 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700565 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800566 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700567 testcase_params = collections.OrderedDict(
568 channel=channel,
569 mode=mode,
570 rate=rate.mcs,
571 num_streams=rate.streams,
572 short_gi=1,
573 chain_mask=chain)
574 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800575 # Do not test 2-stream rates in single chain mode
576 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700577 if 'legacy' in mode:
578 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
579 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700580 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700581 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700582 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700583 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700584 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
585 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700586 channel, mode, rate.mcs,
587 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700588 setattr(self, testcase_name,
589 partial(self._test_sensitivity, testcase_params))
590 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700591 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700592
593
594class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
595 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700596 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700597 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700598 [6, 36, 40, 44, 48, 149, 153, 157, 161],
599 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700600
601
602class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
603 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700604 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800605 self.tests = self.generate_test_cases([6, 36, 149],
606 ['VHT20', 'VHT40', 'VHT80'],
607 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700608
609
610class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
611 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700612 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700613 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
614 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700615
616
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800617class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
618 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700619 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700620 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700621 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
622 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800623
624
Omar El Ayach33f80c02018-09-27 15:02:03 -0700625class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
626 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700627 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800628 self.tests = self.generate_test_cases([36, 40, 44, 48],
629 ['VHT20', 'VHT40', 'VHT80'],
630 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700631
632
633class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
634 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700635 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700636 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700637 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700638 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700639
640
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700641# Over-the air version of senstivity tests
642class WifiOtaSensitivityTest(WifiSensitivityTest):
643 """Class to test over-the-air senstivity.
644
645 This class implements measures WiFi sensitivity tests in an OTA chamber.
646 It allows setting orientation and other chamber parameters to study
647 performance in varying channel conditions
648 """
Omar El Ayach40099d02019-09-12 15:17:33 -0700649 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700650 base_test.BaseTestClass.__init__(self, controllers)
651 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700652 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700653 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700654 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700655 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700656
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700657 def setup_class(self):
658 WifiSensitivityTest.setup_class(self)
Omar El Ayache5b8be12019-10-01 16:27:12 -0700659 self.current_chain_mask = '2x2'
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700660 self.ota_chamber = ota_chamber.create(
661 self.user_params['OTAChamber'])[0]
662
663 def teardown_class(self):
664 WifiSensitivityTest.teardown_class(self)
665 self.ota_chamber.reset_chamber()
666
667 def setup_sensitivity_test(self, testcase_params):
668 # Setup turntable
669 self.ota_chamber.set_orientation(testcase_params['orientation'])
670 # Continue test setup
671 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
672
Omar El Ayache5b8be12019-10-01 16:27:12 -0700673 def setup_dut(self, testcase_params):
674 """Sets up the DUT in the configuration required by the test.
675
676 Args:
677 testcase_params: dict containing AP and other test params
678 """
679 # Configure the right INI settings
680 if testcase_params['chain_mask'] != self.current_chain_mask:
681 self.log.info('Updating WiFi chain mask to: {}'.format(
682 testcase_params['chain_mask']))
Omar El Ayachf7143fc2020-02-03 10:15:55 -0800683 self.current_chain_mask = testcase_params['chain_mask']
Omar El Ayache5b8be12019-10-01 16:27:12 -0700684 if testcase_params['chain_mask'] in ['0', '1']:
685 wputils.set_ini_single_chain_mode(
686 self.dut, int(testcase_params['chain_mask']))
687 else:
688 wputils.set_ini_two_chain_mode(self.dut)
689 # Check battery level before test
690 if not wputils.health_check(self.dut, 10):
691 asserts.skip('Battery level too low. Skipping test.')
692 # Turn screen off to preserve battery
693 self.dut.go_to_sleep()
694 if wputils.validate_network(self.dut,
695 testcase_params['test_network']['SSID']):
696 self.log.info('Already connected to desired network')
697 else:
698 wutils.reset_wifi(self.dut)
699 wutils.set_wifi_country_code(self.dut,
700 self.testclass_params['country_code'])
701 testcase_params['test_network']['channel'] = testcase_params[
702 'channel']
703 wutils.wifi_connect(self.dut,
704 testcase_params['test_network'],
705 num_of_tries=5,
706 check_connectivity=False)
707 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
708
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700709 def process_testclass_results(self):
710 """Saves and plots test results from all executed test cases."""
711 testclass_results_dict = collections.OrderedDict()
712 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
713 plots = []
714 for result in self.testclass_results:
715 test_id = self.extract_test_id(result['testcase_params'],
716 id_fields)
717 test_id = tuple(test_id.items())
718 channel = result['testcase_params']['channel']
719 if test_id not in testclass_results_dict:
720 testclass_results_dict[test_id] = collections.OrderedDict()
721 if channel not in testclass_results_dict[test_id]:
722 testclass_results_dict[test_id][channel] = {
723 'orientation': [],
724 'sensitivity': []
725 }
726 testclass_results_dict[test_id][channel]['orientation'].append(
727 result['testcase_params']['orientation'])
Omar El Ayache9725962019-09-18 17:30:17 -0700728 if result['peak_throughput_pct'] >= 95:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700729 testclass_results_dict[test_id][channel]['sensitivity'].append(
730 result['sensitivity'])
731 else:
732 testclass_results_dict[test_id][channel]['sensitivity'].append(
733 float('nan'))
734
735 for test_id, test_data in testclass_results_dict.items():
736 test_id_dict = dict(test_id)
737 if 'legacy' in test_id_dict['mode']:
738 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
739 test_id_dict['mode'], test_id_dict['rate'],
740 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700741 metric_test_config = '{}_{}_ch{}'.format(
742 test_id_dict['mode'], test_id_dict['rate'],
743 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700744 else:
745 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
746 test_id_dict['mode'], test_id_dict['rate'],
747 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700748 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
749 test_id_dict['mode'], test_id_dict['rate'],
750 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700751 curr_plot = wputils.BokehFigure(
752 title=str(test_id_str),
753 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700754 primary_y_label='Sensitivity (dBm)')
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700755 for channel, channel_results in test_data.items():
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800756 curr_plot.add_line(channel_results['orientation'],
757 channel_results['sensitivity'],
758 legend='Channel {}'.format(channel),
759 marker='circle')
Omar El Ayach49141c02019-09-16 16:43:51 -0700760 metric_tag = 'ota_summary_ch{}_{}'.format(
761 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700762 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayache9725962019-09-18 17:30:17 -0700763 metric_value = numpy.nanmean(channel_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700764 self.testclass_metric_logger.add_metric(
765 metric_name, metric_value)
Omar El Ayache9725962019-09-18 17:30:17 -0700766 self.log.info(("Average Sensitivity for {}: {:.2f}").format(
767 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700768 current_context = (
769 context.get_current_context().get_full_output_path())
770 output_file_path = os.path.join(current_context,
771 str(test_id_str) + '.html')
772 curr_plot.generate_figure(output_file_path)
773 plots.append(curr_plot)
774 output_file_path = os.path.join(current_context, 'results.html')
775 wputils.BokehFigure.save_figures(plots, output_file_path)
776
777 def get_start_atten(self, testcase_params):
778 """Gets the starting attenuation for this sensitivity test.
779
780 The function gets the starting attenuation by checking whether a test
781 at the same rate configuration has executed. If so it sets the starting
782 point a configurable number of dBs below the reference test.
783
784 Returns:
785 start_atten: starting attenuation for current test
786 """
787 # Get the current and reference test config. The reference test is the
788 # one performed at the current MCS+1
789 ref_test_params = self.extract_test_id(
790 testcase_params,
791 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
792 # Check if reference test has been run and set attenuation accordingly
793 previous_params = [
794 self.extract_test_id(
795 result['testcase_params'],
796 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
797 for result in self.testclass_results
798 ]
799 try:
800 ref_index = previous_params[::-1].index(ref_test_params)
801 ref_index = len(previous_params) - 1 - ref_index
802 start_atten = self.testclass_results[ref_index][
803 'atten_at_range'] - (
804 self.testclass_params['adjacent_mcs_range_gap'])
805 except ValueError:
806 print('Reference test not found. Starting from {} dB'.format(
807 self.testclass_params['atten_start']))
808 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700809 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700810 return start_atten
811
Omar El Ayachffb5a462019-09-16 21:05:44 -0700812 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700813 angles):
814 """Function that auto-generates test cases for a test class."""
815 test_cases = []
816 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800817 requested_modes = [
818 mode for mode in modes
819 if mode in self.VALID_TEST_CONFIGS[channel]
820 ]
Omar El Ayacheacef872020-01-31 16:22:28 -0800821 for chain, mode in itertools.product(chain_mask, requested_modes):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700822 if 'VHT' in mode:
823 valid_rates = self.VALID_RATES[mode]
824 elif 'HT' in mode:
825 valid_rates = self.VALID_RATES[mode]
826 elif 'legacy' in mode and channel < 14:
827 valid_rates = self.VALID_RATES['legacy_2GHz']
828 elif 'legacy' in mode and channel > 14:
829 valid_rates = self.VALID_RATES['legacy_5GHz']
830 else:
831 raise ValueError('Invalid test mode.')
Omar El Ayacheacef872020-01-31 16:22:28 -0800832 for rate, angle in itertools.product(valid_rates, angles):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700833 testcase_params = collections.OrderedDict(
834 channel=channel,
835 mode=mode,
836 rate=rate.mcs,
837 num_streams=rate.streams,
838 short_gi=1,
839 chain_mask=chain,
840 orientation=angle)
841 if rate not in requested_rates:
842 continue
843 if str(chain) in ['0', '1'] and rate[1] == 2:
844 # Do not test 2-stream rates in single chain mode
845 continue
846 if 'legacy' in mode:
847 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
848 '_ch{}_{}deg'.format(
849 channel, mode,
850 str(rate.mcs).replace('.', 'p'),
851 rate.streams, chain, angle))
852 else:
853 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
854 '_ch{}_{}deg'.format(
855 channel, mode, rate.mcs,
856 rate.streams, chain, angle))
857 setattr(self, testcase_name,
858 partial(self._test_sensitivity, testcase_params))
859 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700860 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700861
862
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700863class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700864 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700865 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700866 requested_channels = [6, 36, 149]
867 requested_rates = [
868 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700869 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700870 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700871 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700872 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800873 self.tests = self.generate_test_cases(requested_channels,
874 ['VHT20', 'VHT80'],
875 requested_rates, ['2x2'],
876 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700877
878
Omar El Ayache5b8be12019-10-01 16:27:12 -0700879class WifiOtaSensitivity_PerChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700880 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700881 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700882 requested_channels = [6, 36, 149]
Omar El Ayache5b8be12019-10-01 16:27:12 -0700883 requested_rates = [self.RateTuple(2, 1, 21.7)]
884 self.tests = self.generate_test_cases(requested_channels, ['VHT20'],
885 requested_rates,
886 ['0', '1', '2x2'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800887 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700888
889
Omar El Ayachd3850d02019-09-23 16:35:49 -0700890class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
891 def __init__(self, controllers):
892 WifiOtaSensitivityTest.__init__(self, controllers)
893 requested_channels = [6, 36, 149]
894 requested_rates = [
895 self.RateTuple(9, 1, 96),
896 self.RateTuple(8, 1, 86.7),
897 self.RateTuple(7, 1, 72.2),
898 self.RateTuple(4, 1, 43.3),
899 self.RateTuple(2, 1, 21.7),
900 self.RateTuple(0, 1, 7.2),
901 self.RateTuple(9, 2, 192),
902 self.RateTuple(8, 2, 173.3),
903 self.RateTuple(7, 2, 144.4),
904 self.RateTuple(4, 2, 86.7),
905 self.RateTuple(2, 2, 43.3),
906 self.RateTuple(0, 2, 14.4)
907 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800908 self.tests = self.generate_test_cases(requested_channels,
909 ['VHT20', 'VHT80'],
910 requested_rates, ['2x2'],
911 list(range(0, 360, 30)))
Omar El Ayachd3850d02019-09-23 16:35:49 -0700912
913
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700914class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700915 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700916 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700917 requested_rates = [
918 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700919 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700920 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700921 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700922 ]
923 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700924 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
925 requested_rates, ['2x2'], list(range(0, 360, 45)))