blob: 9f1ea53728b67fe20abb17e5a46fc90145287033 [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 logging
Omar El Ayache9725962019-09-18 17:30:17 -070021import numpy
Omar El Ayach33f80c02018-09-27 15:02:03 -070022import os
Omar El Ayach33f80c02018-09-27 15:02:03 -070023from acts import asserts
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070024from acts import context
Omar El Ayach33f80c02018-09-27 15:02:03 -070025from acts import base_test
26from acts import utils
Omar El Ayacha210d572019-03-14 17:31:38 -070027from acts.controllers import iperf_client
Omar El Ayach14416ac2019-01-30 14:58:19 -080028from acts.controllers.utils_lib import ssh
Xianyuan Jia976d4042019-09-30 17:19:47 -070029from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070030from acts.test_utils.wifi import ota_chamber
Omar El Ayach6e518a22019-06-13 13:55:42 -070031from acts.test_utils.wifi import wifi_performance_test_utils as wputils
Omar El Ayach33f80c02018-09-27 15:02:03 -070032from acts.test_utils.wifi import wifi_test_utils as wutils
33from acts.test_utils.wifi import wifi_retail_ap as retail_ap
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070034from functools import partial
Omar El Ayach5fbc1222018-12-07 18:10:05 -080035from WifiRvrTest import WifiRvrTest
36from WifiPingTest import WifiPingTest
Omar El Ayach33f80c02018-09-27 15:02:03 -070037
38
Omar El Ayach5fbc1222018-12-07 18:10:05 -080039class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -070040 """Class to test WiFi sensitivity tests.
41
42 This class implements measures WiFi sensitivity per rate. It heavily
43 leverages the WifiRvrTest class and introduced minor differences to set
44 specific rates and the access point, and implements a different pass/fail
45 check. For an example config file to run this test class see
46 example_connectivity_performance_ap_sta.json.
47 """
48
Omar El Ayacha210d572019-03-14 17:31:38 -070049 RSSI_POLL_INTERVAL = 0.2
Omar El Ayach33f80c02018-09-27 15:02:03 -070050 VALID_TEST_CONFIGS = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070051 1: ['legacy', 'VHT20'],
52 2: ['legacy', 'VHT20'],
53 6: ['legacy', 'VHT20'],
54 10: ['legacy', 'VHT20'],
55 11: ['legacy', 'VHT20'],
56 36: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
57 40: ['legacy', 'VHT20'],
58 44: ['legacy', 'VHT20'],
59 48: ['legacy', 'VHT20'],
60 149: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
61 153: ['legacy', 'VHT20'],
62 157: ['legacy', 'VHT20'],
63 161: ['legacy', 'VHT20']
Omar El Ayach33f80c02018-09-27 15:02:03 -070064 }
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070065 RateTuple = collections.namedtuple(('RateTuple'),
66 ['mcs', 'streams', 'data_rate'])
Omar El Ayacha210d572019-03-14 17:31:38 -070067 #yapf:disable
Omar El Ayach33f80c02018-09-27 15:02:03 -070068 VALID_RATES = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070069 'legacy_2GHz': [
Omar El Ayacha210d572019-03-14 17:31:38 -070070 RateTuple(54, 1, 54), RateTuple(48, 1, 48),
71 RateTuple(36, 1, 36), RateTuple(24, 1, 24),
72 RateTuple(18, 1, 18), RateTuple(12, 1, 12),
73 RateTuple(11, 1, 11), RateTuple(9, 1, 9),
74 RateTuple(6, 1, 6), RateTuple(5.5, 1, 5.5),
75 RateTuple(2, 1, 2), RateTuple(1, 1, 1)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070076 'legacy_5GHz': [
Omar El Ayacha210d572019-03-14 17:31:38 -070077 RateTuple(54, 1, 54), RateTuple(48, 1, 48),
78 RateTuple(36, 1, 36), RateTuple(24, 1, 24),
79 RateTuple(18, 1, 18), RateTuple(12, 1, 12),
80 RateTuple(9, 1, 9), RateTuple(6, 1, 6)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070081 'HT20': [
Omar El Ayacha210d572019-03-14 17:31:38 -070082 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
83 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
84 RateTuple(3, 1, 26), RateTuple(2, 1, 21.7),
85 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
86 RateTuple(15, 2, 144.4), RateTuple(14, 2, 130),
87 RateTuple(13, 2, 115.6), RateTuple(12, 2, 86.7),
88 RateTuple(11, 2, 57.8), RateTuple(10, 2, 43.4),
89 RateTuple(9, 2, 28.9), RateTuple(8, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070090 'VHT20': [
Omar El Ayacha210d572019-03-14 17:31:38 -070091 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
92 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
93 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
94 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
95 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
96 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
97 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
98 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
99 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
100 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700101 'VHT40': [
Omar El Ayach03e40612019-05-01 16:25:39 -0700102 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
103 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
104 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
105 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
106 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
107 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
108 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
109 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
110 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
111 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700112 'VHT80': [
Omar El Ayach03e40612019-05-01 16:25:39 -0700113 RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
114 RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
115 RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
116 RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
117 RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
118 RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
119 RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
120 RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
121 RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
122 RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
Omar El Ayach33f80c02018-09-27 15:02:03 -0700123 }
Omar El Ayacha210d572019-03-14 17:31:38 -0700124 #yapf:enable
Omar El Ayach33f80c02018-09-27 15:02:03 -0700125
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800126 def __init__(self, controllers):
127 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700128 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700129 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700130 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700131 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700132 self.publish_testcase_metrics = True
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800133
Omar El Ayach33f80c02018-09-27 15:02:03 -0700134 def setup_class(self):
135 """Initializes common test hardware and parameters.
136
137 This function initializes hardwares and compiles parameters that are
138 common to all tests in this class.
139 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700140 self.dut = self.android_devices[-1]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700141 req_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700142 'RetailAccessPoints', 'sensitivity_test_params', 'testbed_params',
143 'RemoteServer'
Omar El Ayach33f80c02018-09-27 15:02:03 -0700144 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700145 opt_params = ['main_network', 'golden_files_list']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700146 self.unpack_userparams(req_params, opt_params)
147 self.testclass_params = self.sensitivity_test_params
148 self.num_atten = self.attenuators[0].instrument.num_atten
Omar El Ayach14416ac2019-01-30 14:58:19 -0800149 self.ping_server = ssh.connection.SshConnection(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700150 ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700151 self.iperf_server = self.iperf_servers[0]
Omar El Ayach14416ac2019-01-30 14:58:19 -0800152 self.iperf_client = self.iperf_clients[0]
Omar El Ayacha210d572019-03-14 17:31:38 -0700153 self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700154 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700155 self.access_point.ap_settings))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700156 self.log_path = os.path.join(logging.log_path, 'results')
Mark De Ruyter72f8df92020-02-12 13:44:49 -0800157 os.makedirs(self.log_path, exist_ok=True)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700158 if not hasattr(self, 'golden_files_list'):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700159 self.golden_files_list = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700160 os.path.join(self.testbed_params['golden_results_path'], file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700161 for file in os.listdir(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700162 self.testbed_params['golden_results_path'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700163 ]
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700164 if hasattr(self, 'bdf'):
165 self.log.info('Pushing WiFi BDF to DUT.')
166 wputils.push_bdf(self.dut, self.bdf)
167 if hasattr(self, 'firmware'):
168 self.log.info('Pushing WiFi firmware to DUT.')
169 wlanmdsp = [
170 file for file in self.firmware if "wlanmdsp.mbn" in file
171 ][0]
172 data_msc = [file for file in self.firmware
173 if "Data.msc" in file][0]
174 wputils.push_firmware(self.dut, wlanmdsp, data_msc)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800175 self.atten_dut_chain_map = {}
Omar El Ayach33f80c02018-09-27 15:02:03 -0700176 self.testclass_results = []
177
178 # Turn WiFi ON
Omar El Ayachd7109092019-09-29 18:31:33 -0700179 if self.testclass_params.get('airplane_mode', 1):
180 self.log.info('Turning on airplane mode.')
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800181 asserts.assert_true(utils.force_airplane_mode(self.dut, True),
182 "Can not turn on airplane mode.")
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700183 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700184
Omar El Ayach87222062020-03-25 19:34:12 -0700185 # Configure test retries
186 self.user_params['retry_tests'] = [self.__class__.__name__]
187
Omar El Ayach96714c82019-01-28 18:51:46 -0800188 def teardown_class(self):
189 # Turn WiFi OFF
190 for dev in self.android_devices:
191 wutils.wifi_toggle_state(dev, False)
192 self.process_testclass_results()
193
Omar El Ayach87222062020-03-25 19:34:12 -0700194 def setup_test(self):
195 self.retry_flag = False
196
197 def teardown_test(self):
198 self.retry_flag = False
199
200 def on_retry(self):
201 """Function to control test logic on retried tests.
202
203 This function is automatically executed on tests that are being
204 retried. In this case the function resets wifi, toggles it off and on
205 and sets a retry_flag to enable further tweaking the test logic on
206 second attempts.
207 """
208 self.retry_flag = True
209 for dev in self.android_devices:
210 wutils.reset_wifi(dev)
211 wutils.toggle_wifi_off_and_on(dev)
212
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800213 def pass_fail_check(self, result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700214 """Checks sensitivity against golden results and decides on pass/fail.
215
216 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800217 result: dict containing attenuation, throughput and other meta
Mark De Ruytere54396c2019-08-20 12:54:37 -0700218 data
Omar El Ayach33f80c02018-09-27 15:02:03 -0700219 """
Omar El Ayach87222062020-03-25 19:34:12 -0700220 result_string = ('Throughput = {}%, Sensitivity = {}.'.format(
221 result['peak_throughput_pct'], result['sensitivity']))
Omar El Ayache9725962019-09-18 17:30:17 -0700222 if result['peak_throughput_pct'] < 95:
Omar El Ayach87222062020-03-25 19:34:12 -0700223 asserts.fail('Result unreliable. {}'.format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800224 else:
Omar El Ayach87222062020-03-25 19:34:12 -0700225 asserts.explicit_pass('Test Passed. {}'.format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700226
227 def process_testclass_results(self):
228 """Saves and plots test results from all executed test cases."""
Omar El Ayach96714c82019-01-28 18:51:46 -0800229 # write json output
Omar El Ayach33f80c02018-09-27 15:02:03 -0700230 testclass_results_dict = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700231 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
Omar El Ayacha210d572019-03-14 17:31:38 -0700232 channels_tested = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700233 for result in self.testclass_results:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700234 testcase_params = result['testcase_params']
Omar El Ayach55f51b52019-12-06 17:56:23 -0800235 test_id = self.extract_test_id(testcase_params, id_fields)
Omar El Ayacha210d572019-03-14 17:31:38 -0700236 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700237 if test_id not in testclass_results_dict:
238 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700239 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700240 if channel not in channels_tested:
241 channels_tested.append(channel)
Omar El Ayache9725962019-09-18 17:30:17 -0700242 if result['peak_throughput_pct'] >= 95:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700243 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700244 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700245 else:
246 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700247
Omar El Ayach55f51b52019-12-06 17:56:23 -0800248 # calculate average metrics
249 metrics_dict = collections.OrderedDict()
250 id_fields = ['channel', 'mode', 'num_streams', 'chain_mask']
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800251 for test_id in testclass_results_dict.keys():
252 for channel in testclass_results_dict[test_id].keys():
253 metric_tag = collections.OrderedDict(test_id, channel=channel)
254 metric_tag = self.extract_test_id(metric_tag, id_fields)
255 metric_tag = tuple(metric_tag.items())
256 metrics_dict.setdefault(metric_tag, [])
257 sensitivity_result = testclass_results_dict[test_id][channel]
258 if sensitivity_result != '':
259 metrics_dict[metric_tag].append(sensitivity_result)
Omar El Ayach55f51b52019-12-06 17:56:23 -0800260 for metric_tag_tuple, metric_data in metrics_dict.items():
261 metric_tag_dict = collections.OrderedDict(metric_tag_tuple)
262 metric_tag = 'ch{}_{}_nss{}_chain{}'.format(
263 metric_tag_dict['channel'], metric_tag_dict['mode'],
264 metric_tag_dict['num_streams'], metric_tag_dict['chain_mask'])
265 metric_key = "{}.avg_sensitivity".format(metric_tag)
266 metric_value = numpy.nanmean(metric_data)
267 self.testclass_metric_logger.add_metric(metric_key, metric_value)
268
Omar El Ayach96714c82019-01-28 18:51:46 -0800269 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700270 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700271 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700272 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800273 results_file_path = os.path.join(self.log_path, 'results.csv')
274 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800275 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
276 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700277 for test_id, test_results in testclass_results_dict.items():
278 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700279 if 'legacy' in test_id_dict['mode']:
280 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700281 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700282 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700283 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700284 if rate[:-1] == (test_id_dict['rate'],
285 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700286 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700287 'Mode': test_id_dict['mode'],
288 'MCS': test_id_dict['rate'],
289 'Streams': test_id_dict['num_streams'],
290 'Chain': test_id_dict['chain_mask'],
291 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700292 }
293 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700294 row_value['Ch. ' + str(channel)] = test_results.pop(
295 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700296 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800297
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700298 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800299 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700300
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800301 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700302 """Post processes RvR results to compute sensitivity.
303
304 Takes in the results of the RvR tests and computes the sensitivity of
305 the current rate by looking at the point at which throughput drops
306 below the percentage specified in the config file. The function then
307 calls on its parent class process_test_results to plot the result.
308
309 Args:
310 rvr_result: dict containing attenuation, throughput and other meta
311 data
312 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700313 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
314 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700315 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700316 throughput < rvr_result['peak_throughput'] *
317 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
318 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700319 ]
320 consistency_check = [
321 idx for idx in range(len(throughput_check))
322 if all(throughput_check[idx:])
323 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700324 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700325 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700326 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
327 rvr_result['atten_at_range'])
328 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
329 self.testbed_params['ap_tx_power_offset'][str(
330 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800331 WifiRvrTest.process_test_results(self, rvr_result)
332
333 def process_ping_test_results(self, testcase_params, ping_result):
334 """Post processes RvR results to compute sensitivity.
335
336 Takes in the results of the RvR tests and computes the sensitivity of
337 the current rate by looking at the point at which throughput drops
338 below the percentage specified in the config file. The function then
339 calls on its parent class process_test_results to plot the result.
340
341 Args:
342 rvr_result: dict containing attenuation, throughput and other meta
343 data
344 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800345 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700346 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
347 self.testbed_params['ap_tx_power_offset'][str(
348 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700349
Omar El Ayach03e40612019-05-01 16:25:39 -0700350 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700351 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700352 self.setup_ping_test(testcase_params)
353 self.run_sensitivity_test = self.run_ping_test
354 self.process_sensitivity_test_results = (
355 self.process_ping_test_results)
356 else:
357 self.setup_rvr_test(testcase_params)
358 self.run_sensitivity_test = self.run_rvr_test
359 self.process_sensitivity_test_results = (
360 self.process_rvr_test_results)
361
Omar El Ayach33f80c02018-09-27 15:02:03 -0700362 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800363 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700364
365 Args:
366 testcase_params: dict containing AP and other test params
367 """
368 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700369 testcase_params['channel'])
370 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700371 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700372 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700373 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700374 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700375 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700376 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700377 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700378 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700379 self.access_point.set_region(self.testbed_params['default_region'])
380 self.access_point.set_channel(band, testcase_params['channel'])
381 self.access_point.set_bandwidth(band, testcase_params['mode'])
382 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800383 self.access_point.set_rate(band, testcase_params['mode'],
384 testcase_params['num_streams'],
385 testcase_params['rate'],
386 testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800387 # Set attenuator offsets and set attenuators to initial condition
388 atten_offsets = self.testbed_params['chain_offset'][str(
389 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800390 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800391 if 'AP-Chain-0' in atten.path:
392 atten.offset = atten_offsets[0]
393 elif 'AP-Chain-1' in atten.path:
394 atten.offset = atten_offsets[1]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800395 else:
396 atten.offset = 0
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700397 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700398 self.access_point.ap_settings))
399
Omar El Ayach6e518a22019-06-13 13:55:42 -0700400 def setup_dut(self, testcase_params):
401 """Sets up the DUT in the configuration required by the test.
402
403 Args:
404 testcase_params: dict containing AP and other test params
405 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700406 # Check battery level before test
407 if not wputils.health_check(self.dut, 10):
408 asserts.skip('Battery level too low. Skipping test.')
409 # Turn screen off to preserve battery
410 self.dut.go_to_sleep()
Omar El Ayach35bf3b82019-12-06 19:29:19 -0800411 if wputils.validate_network(self.dut,
412 testcase_params['test_network']['SSID']):
Omar El Ayach39acf802019-08-02 17:52:39 -0700413 self.log.info('Already connected to desired network')
414 else:
415 wutils.reset_wifi(self.dut)
Roshan Pius5b19a122019-09-13 08:07:30 -0700416 wutils.set_wifi_country_code(self.dut,
Omar El Ayache5b8be12019-10-01 16:27:12 -0700417 self.testclass_params['country_code'])
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800418 testcase_params['test_network']['channel'] = testcase_params[
419 'channel']
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800420 wutils.wifi_connect(self.dut,
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800421 testcase_params['test_network'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800422 num_of_tries=5,
423 check_connectivity=False)
Omar El Ayach39acf802019-08-02 17:52:39 -0700424 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayache5b8be12019-10-01 16:27:12 -0700425 # Activate/attenuate the correct chains
Omar El Ayach16f5f342019-11-23 12:27:56 -0800426 if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
427 self.atten_dut_chain_map[testcase_params[
428 'channel']] = wputils.get_current_atten_dut_chain_map(
429 self.attenuators, self.dut, self.ping_server)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800430 self.log.info("Current Attenuator-DUT Chain Map: {}".format(
431 self.atten_dut_chain_map[testcase_params['channel']]))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700432 for idx, atten in enumerate(self.attenuators):
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800433 if self.atten_dut_chain_map[testcase_params['channel']][
434 idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700435 atten.offset = atten.instrument.max_atten
436
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700437 def extract_test_id(self, testcase_params, id_fields):
438 test_id = collections.OrderedDict(
439 (param, testcase_params[param]) for param in id_fields)
440 return test_id
441
442 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700443 """Gets the starting attenuation for this sensitivity test.
444
445 The function gets the starting attenuation by checking whether a test
446 as the next higher MCS has been executed. If so it sets the starting
447 point a configurable number of dBs below the next MCS's sensitivity.
448
449 Returns:
450 start_atten: starting attenuation for current test
451 """
Omar El Ayach87222062020-03-25 19:34:12 -0700452 # If the test is being retried, start from the beginning
453 if self.retry_flag:
454 self.log.info('Retry flag set. Setting attenuation to minimum.')
455 return self.testclass_params['atten_start']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700456 # Get the current and reference test config. The reference test is the
457 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700458 current_rate = testcase_params['rate']
459 ref_test_params = self.extract_test_id(
460 testcase_params,
461 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
462 if 'legacy' in testcase_params['mode']:
463 if testcase_params['channel'] <= 13:
464 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700465 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700466 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700467 ref_index = max(
468 0,
469 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
470 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700471 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700472 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700473 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700474
475 # Check if reference test has been run and set attenuation accordingly
476 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700477 self.extract_test_id(
478 result['testcase_params'],
479 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700480 for result in self.testclass_results
481 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700482
Omar El Ayach33f80c02018-09-27 15:02:03 -0700483 try:
484 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700485 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700486 'atten_at_range'] - (
487 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700488 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700489 self.log.warning(
490 'Reference test not found. Starting from {} dB'.format(
491 self.testclass_params['atten_start']))
492 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700493 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700494 return start_atten
495
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700496 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700497 """Function that generates test params based on the test name."""
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800498 band = self.access_point.band_lookup_by_channel(
499 testcase_params['channel'])
500 testcase_params['test_network'] = self.main_network[band]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700501 if testcase_params['chain_mask'] in ['0', '1']:
502 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
503 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700504 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700505 # Set attenuated chain to -1. Do not set to None as this will be
506 # compared to RF chain map which may include None
507 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800508
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700509 self.testclass_params[
510 'range_ping_loss_threshold'] = 100 - self.testclass_params[
511 'throughput_pct_at_sensitivity']
512 if self.testclass_params['traffic_type'] == 'UDP':
513 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
514 self.testclass_params['iperf_duration'],
515 self.testclass_params['UDP_rates'][testcase_params['mode']])
516 elif self.testclass_params['traffic_type'] == 'TCP':
517 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
518 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800519
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700520 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700521 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700522 testcase_params['iperf_args'] += ' -R'
523 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800524 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700525 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800526
Omar El Ayach33f80c02018-09-27 15:02:03 -0700527 return testcase_params
528
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700529 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700530 """ Function that gets called for each test case
531
532 The function gets called in each rvr test case. The function customizes
533 the rvr test based on the test name of the test that called it
534 """
535 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700536 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700537 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700538 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800539 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700540 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
541 testcase_params['atten_step'])
542 testcase_params['atten_range'] = [
543 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800544 for x in range(0, num_atten_steps)
545 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700546
547 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700548 self.setup_sensitivity_test(testcase_params)
549 result = self.run_sensitivity_test(testcase_params)
550 self.process_sensitivity_test_results(testcase_params, result)
551
Omar El Ayach33f80c02018-09-27 15:02:03 -0700552 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800553 self.testclass_results.append(result)
554 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700555
Omar El Ayachffb5a462019-09-16 21:05:44 -0700556 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700557 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700558 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700559 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800560 requested_modes = [
561 mode for mode in modes
562 if mode in self.VALID_TEST_CONFIGS[channel]
563 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700564 for mode in requested_modes:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700565 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700566 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700567 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700568 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700569 elif 'legacy' in mode and channel < 14:
570 rates = self.VALID_RATES['legacy_2GHz']
571 elif 'legacy' in mode and channel > 14:
572 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700573 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700574 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800575 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700576 testcase_params = collections.OrderedDict(
577 channel=channel,
578 mode=mode,
579 rate=rate.mcs,
580 num_streams=rate.streams,
581 short_gi=1,
582 chain_mask=chain)
583 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800584 # Do not test 2-stream rates in single chain mode
585 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700586 if 'legacy' in mode:
587 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
588 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700589 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700590 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700591 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700592 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700593 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
594 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700595 channel, mode, rate.mcs,
596 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700597 setattr(self, testcase_name,
598 partial(self._test_sensitivity, testcase_params))
599 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700600 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700601
602
603class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
604 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700605 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700606 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700607 [6, 36, 40, 44, 48, 149, 153, 157, 161],
608 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700609
610
611class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
612 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700613 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800614 self.tests = self.generate_test_cases([6, 36, 149],
615 ['VHT20', 'VHT40', 'VHT80'],
616 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700617
618
619class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
620 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700621 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700622 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
623 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700624
625
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800626class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
627 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700628 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700629 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700630 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
631 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800632
633
Omar El Ayach33f80c02018-09-27 15:02:03 -0700634class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
635 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700636 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800637 self.tests = self.generate_test_cases([36, 40, 44, 48],
638 ['VHT20', 'VHT40', 'VHT80'],
639 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700640
641
642class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
643 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700644 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700645 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700646 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700647 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700648
649
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700650# Over-the air version of senstivity tests
651class WifiOtaSensitivityTest(WifiSensitivityTest):
652 """Class to test over-the-air senstivity.
653
654 This class implements measures WiFi sensitivity tests in an OTA chamber.
655 It allows setting orientation and other chamber parameters to study
656 performance in varying channel conditions
657 """
Omar El Ayach40099d02019-09-12 15:17:33 -0700658 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700659 base_test.BaseTestClass.__init__(self, controllers)
660 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700661 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700662 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700663 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700664 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700665
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700666 def setup_class(self):
667 WifiSensitivityTest.setup_class(self)
Omar El Ayache5b8be12019-10-01 16:27:12 -0700668 self.current_chain_mask = '2x2'
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700669 self.ota_chamber = ota_chamber.create(
670 self.user_params['OTAChamber'])[0]
671
672 def teardown_class(self):
673 WifiSensitivityTest.teardown_class(self)
674 self.ota_chamber.reset_chamber()
675
676 def setup_sensitivity_test(self, testcase_params):
677 # Setup turntable
678 self.ota_chamber.set_orientation(testcase_params['orientation'])
679 # Continue test setup
680 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
681
Omar El Ayache5b8be12019-10-01 16:27:12 -0700682 def setup_dut(self, testcase_params):
683 """Sets up the DUT in the configuration required by the test.
684
685 Args:
686 testcase_params: dict containing AP and other test params
687 """
688 # Configure the right INI settings
689 if testcase_params['chain_mask'] != self.current_chain_mask:
690 self.log.info('Updating WiFi chain mask to: {}'.format(
691 testcase_params['chain_mask']))
Omar El Ayachf7143fc2020-02-03 10:15:55 -0800692 self.current_chain_mask = testcase_params['chain_mask']
Omar El Ayache5b8be12019-10-01 16:27:12 -0700693 if testcase_params['chain_mask'] in ['0', '1']:
694 wputils.set_ini_single_chain_mode(
695 self.dut, int(testcase_params['chain_mask']))
696 else:
697 wputils.set_ini_two_chain_mode(self.dut)
698 # Check battery level before test
699 if not wputils.health_check(self.dut, 10):
700 asserts.skip('Battery level too low. Skipping test.')
701 # Turn screen off to preserve battery
702 self.dut.go_to_sleep()
703 if wputils.validate_network(self.dut,
704 testcase_params['test_network']['SSID']):
705 self.log.info('Already connected to desired network')
706 else:
707 wutils.reset_wifi(self.dut)
708 wutils.set_wifi_country_code(self.dut,
709 self.testclass_params['country_code'])
710 testcase_params['test_network']['channel'] = testcase_params[
711 'channel']
712 wutils.wifi_connect(self.dut,
713 testcase_params['test_network'],
714 num_of_tries=5,
715 check_connectivity=False)
716 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
717
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700718 def process_testclass_results(self):
719 """Saves and plots test results from all executed test cases."""
720 testclass_results_dict = collections.OrderedDict()
Omar El Ayach27114262020-02-06 15:30:08 -0800721 id_fields = ['channel', 'mode', 'rate']
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700722 plots = []
723 for result in self.testclass_results:
724 test_id = self.extract_test_id(result['testcase_params'],
725 id_fields)
726 test_id = tuple(test_id.items())
Omar El Ayach27114262020-02-06 15:30:08 -0800727 chain_mask = result['testcase_params']['chain_mask']
728 num_streams = result['testcase_params']['num_streams']
729 line_id = (chain_mask, num_streams)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700730 if test_id not in testclass_results_dict:
731 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayach27114262020-02-06 15:30:08 -0800732 if line_id not in testclass_results_dict[test_id]:
733 testclass_results_dict[test_id][line_id] = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700734 'orientation': [],
735 'sensitivity': []
736 }
Omar El Ayach87222062020-03-25 19:34:12 -0700737 orientation = result['testcase_params']['orientation']
Omar El Ayache9725962019-09-18 17:30:17 -0700738 if result['peak_throughput_pct'] >= 95:
Omar El Ayach87222062020-03-25 19:34:12 -0700739 sensitivity = result['sensitivity']
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700740 else:
Omar El Ayach87222062020-03-25 19:34:12 -0700741 sensitivity = float('nan')
742 if orientation not in testclass_results_dict[test_id][line_id][
743 'orientation']:
744 testclass_results_dict[test_id][line_id]['orientation'].append(
745 orientation)
Omar El Ayach27114262020-02-06 15:30:08 -0800746 testclass_results_dict[test_id][line_id]['sensitivity'].append(
Omar El Ayach87222062020-03-25 19:34:12 -0700747 sensitivity)
748 else:
749 testclass_results_dict[test_id][line_id]['sensitivity'][
750 -1] = sensitivity
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700751
752 for test_id, test_data in testclass_results_dict.items():
753 test_id_dict = dict(test_id)
754 if 'legacy' in test_id_dict['mode']:
Omar El Ayach27114262020-02-06 15:30:08 -0800755 test_id_str = 'Channel {} - {} {}Mbps'.format(
756 test_id_dict['channel'], test_id_dict['mode'],
757 test_id_dict['rate'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700758 else:
Omar El Ayach27114262020-02-06 15:30:08 -0800759 test_id_str = 'Channel {} - {} MCS{}'.format(
760 test_id_dict['channel'], test_id_dict['mode'],
761 test_id_dict['rate'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700762 curr_plot = wputils.BokehFigure(
763 title=str(test_id_str),
764 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700765 primary_y_label='Sensitivity (dBm)')
Omar El Ayach27114262020-02-06 15:30:08 -0800766 for line_id, line_results in test_data.items():
767 curr_plot.add_line(line_results['orientation'],
768 line_results['sensitivity'],
769 legend='Nss{} - Chain Mask {}'.format(
770 line_id[1], line_id[0]),
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800771 marker='circle')
Omar El Ayach27114262020-02-06 15:30:08 -0800772 if 'legacy' in test_id_dict['mode']:
773 metric_tag = 'ota_summary_ch{}_{}_{}_ch{}'.format(
774 test_id_dict['channel'], test_id_dict['mode'],
775 test_id_dict['rate'], line_id[0])
776 else:
777 metric_tag = 'ota_summary_ch{}_{}_mcs{}_nss{}_ch{}'.format(
778 test_id_dict['channel'], test_id_dict['mode'],
779 test_id_dict['rate'], line_id[1], line_id[0])
780
Omar El Ayach40099d02019-09-12 15:17:33 -0700781 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayach27114262020-02-06 15:30:08 -0800782 metric_value = numpy.nanmean(line_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700783 self.testclass_metric_logger.add_metric(
784 metric_name, metric_value)
Omar El Ayach27114262020-02-06 15:30:08 -0800785 self.log.info(("Average Sensitivity for {}: {:.1f}").format(
Omar El Ayache9725962019-09-18 17:30:17 -0700786 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700787 current_context = (
788 context.get_current_context().get_full_output_path())
789 output_file_path = os.path.join(current_context,
790 str(test_id_str) + '.html')
791 curr_plot.generate_figure(output_file_path)
792 plots.append(curr_plot)
793 output_file_path = os.path.join(current_context, 'results.html')
794 wputils.BokehFigure.save_figures(plots, output_file_path)
795
796 def get_start_atten(self, testcase_params):
797 """Gets the starting attenuation for this sensitivity test.
798
799 The function gets the starting attenuation by checking whether a test
800 at the same rate configuration has executed. If so it sets the starting
801 point a configurable number of dBs below the reference test.
802
803 Returns:
804 start_atten: starting attenuation for current test
805 """
Omar El Ayach87222062020-03-25 19:34:12 -0700806 # If the test is being retried, start from the beginning
807 if self.retry_flag:
808 self.log.info('Retry flag set. Setting attenuation to minimum.')
809 return self.testclass_params['atten_start']
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700810 # Get the current and reference test config. The reference test is the
811 # one performed at the current MCS+1
812 ref_test_params = self.extract_test_id(
813 testcase_params,
814 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
815 # Check if reference test has been run and set attenuation accordingly
816 previous_params = [
817 self.extract_test_id(
818 result['testcase_params'],
819 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
820 for result in self.testclass_results
821 ]
822 try:
823 ref_index = previous_params[::-1].index(ref_test_params)
824 ref_index = len(previous_params) - 1 - ref_index
825 start_atten = self.testclass_results[ref_index][
826 'atten_at_range'] - (
827 self.testclass_params['adjacent_mcs_range_gap'])
828 except ValueError:
829 print('Reference test not found. Starting from {} dB'.format(
830 self.testclass_params['atten_start']))
831 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700832 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700833 return start_atten
834
Omar El Ayachffb5a462019-09-16 21:05:44 -0700835 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700836 angles):
837 """Function that auto-generates test cases for a test class."""
838 test_cases = []
839 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800840 requested_modes = [
841 mode for mode in modes
842 if mode in self.VALID_TEST_CONFIGS[channel]
843 ]
Omar El Ayacheacef872020-01-31 16:22:28 -0800844 for chain, mode in itertools.product(chain_mask, requested_modes):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700845 if 'VHT' in mode:
846 valid_rates = self.VALID_RATES[mode]
847 elif 'HT' in mode:
848 valid_rates = self.VALID_RATES[mode]
849 elif 'legacy' in mode and channel < 14:
850 valid_rates = self.VALID_RATES['legacy_2GHz']
851 elif 'legacy' in mode and channel > 14:
852 valid_rates = self.VALID_RATES['legacy_5GHz']
853 else:
854 raise ValueError('Invalid test mode.')
Omar El Ayacheacef872020-01-31 16:22:28 -0800855 for rate, angle in itertools.product(valid_rates, angles):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700856 testcase_params = collections.OrderedDict(
857 channel=channel,
858 mode=mode,
859 rate=rate.mcs,
860 num_streams=rate.streams,
861 short_gi=1,
862 chain_mask=chain,
863 orientation=angle)
864 if rate not in requested_rates:
865 continue
866 if str(chain) in ['0', '1'] and rate[1] == 2:
867 # Do not test 2-stream rates in single chain mode
868 continue
869 if 'legacy' in mode:
870 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
871 '_ch{}_{}deg'.format(
872 channel, mode,
873 str(rate.mcs).replace('.', 'p'),
874 rate.streams, chain, angle))
875 else:
876 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
877 '_ch{}_{}deg'.format(
878 channel, mode, rate.mcs,
879 rate.streams, chain, angle))
880 setattr(self, testcase_name,
881 partial(self._test_sensitivity, testcase_params))
882 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700883 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700884
885
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700886class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700887 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700888 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700889 requested_channels = [6, 36, 149]
890 requested_rates = [
891 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700892 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700893 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700894 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700895 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800896 self.tests = self.generate_test_cases(requested_channels,
897 ['VHT20', 'VHT80'],
898 requested_rates, ['2x2'],
899 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700900
901
Omar El Ayache5b8be12019-10-01 16:27:12 -0700902class WifiOtaSensitivity_PerChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700903 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700904 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700905 requested_channels = [6, 36, 149]
Omar El Ayach27114262020-02-06 15:30:08 -0800906 requested_rates = [
907 self.RateTuple(2, 1, 21.7),
908 self.RateTuple(2, 2, 43.3)
909 ]
Omar El Ayache5b8be12019-10-01 16:27:12 -0700910 self.tests = self.generate_test_cases(requested_channels, ['VHT20'],
911 requested_rates,
912 ['0', '1', '2x2'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800913 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700914
915
Omar El Ayachd3850d02019-09-23 16:35:49 -0700916class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
917 def __init__(self, controllers):
918 WifiOtaSensitivityTest.__init__(self, controllers)
919 requested_channels = [6, 36, 149]
920 requested_rates = [
921 self.RateTuple(9, 1, 96),
922 self.RateTuple(8, 1, 86.7),
923 self.RateTuple(7, 1, 72.2),
924 self.RateTuple(4, 1, 43.3),
925 self.RateTuple(2, 1, 21.7),
926 self.RateTuple(0, 1, 7.2),
927 self.RateTuple(9, 2, 192),
928 self.RateTuple(8, 2, 173.3),
929 self.RateTuple(7, 2, 144.4),
930 self.RateTuple(4, 2, 86.7),
931 self.RateTuple(2, 2, 43.3),
932 self.RateTuple(0, 2, 14.4)
933 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800934 self.tests = self.generate_test_cases(requested_channels,
935 ['VHT20', 'VHT80'],
936 requested_rates, ['2x2'],
937 list(range(0, 360, 30)))
Omar El Ayachd3850d02019-09-23 16:35:49 -0700938
939
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700940class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700941 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700942 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700943 requested_rates = [
944 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700945 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700946 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700947 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700948 ]
949 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700950 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
951 requested_rates, ['2x2'], list(range(0, 360, 45)))