blob: b876ae9adb730e31c1ea24a87b76f1a1d4475309 [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
Xianyuan Jia63751fb2020-11-17 00:07:40 +000030from acts_contrib.test_utils.wifi import ota_chamber
31from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
32from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
33from acts_contrib.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 Ayach6e96e132020-04-02 10:21:09 -0700145 opt_params = ['main_network']
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 Ayacha218f6b2019-11-22 18:24:48 -0800158 self.atten_dut_chain_map = {}
Omar El Ayach33f80c02018-09-27 15:02:03 -0700159 self.testclass_results = []
160
161 # Turn WiFi ON
Omar El Ayachd7109092019-09-29 18:31:33 -0700162 if self.testclass_params.get('airplane_mode', 1):
163 self.log.info('Turning on airplane mode.')
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800164 asserts.assert_true(utils.force_airplane_mode(self.dut, True),
Omar El Ayachd4694892020-12-04 14:24:09 -0800165 'Can not turn on airplane mode.')
Omar El Ayach9c56cf32019-09-19 13:07:51 -0700166 wutils.wifi_toggle_state(self.dut, True)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700167
Omar El Ayach87222062020-03-25 19:34:12 -0700168 # Configure test retries
169 self.user_params['retry_tests'] = [self.__class__.__name__]
170
Omar El Ayach96714c82019-01-28 18:51:46 -0800171 def teardown_class(self):
172 # Turn WiFi OFF
173 for dev in self.android_devices:
174 wutils.wifi_toggle_state(dev, False)
175 self.process_testclass_results()
176
Omar El Ayach87222062020-03-25 19:34:12 -0700177 def setup_test(self):
178 self.retry_flag = False
179
180 def teardown_test(self):
181 self.retry_flag = False
182
183 def on_retry(self):
184 """Function to control test logic on retried tests.
185
186 This function is automatically executed on tests that are being
187 retried. In this case the function resets wifi, toggles it off and on
188 and sets a retry_flag to enable further tweaking the test logic on
189 second attempts.
190 """
191 self.retry_flag = True
192 for dev in self.android_devices:
193 wutils.reset_wifi(dev)
194 wutils.toggle_wifi_off_and_on(dev)
195
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800196 def pass_fail_check(self, result):
Omar El Ayach6e96e132020-04-02 10:21:09 -0700197 """Checks sensitivity results and decides on pass/fail.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700198
199 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800200 result: dict containing attenuation, throughput and other meta
Mark De Ruytere54396c2019-08-20 12:54:37 -0700201 data
Omar El Ayach33f80c02018-09-27 15:02:03 -0700202 """
Omar El Ayach87222062020-03-25 19:34:12 -0700203 result_string = ('Throughput = {}%, Sensitivity = {}.'.format(
204 result['peak_throughput_pct'], result['sensitivity']))
Omar El Ayache9725962019-09-18 17:30:17 -0700205 if result['peak_throughput_pct'] < 95:
Omar El Ayach87222062020-03-25 19:34:12 -0700206 asserts.fail('Result unreliable. {}'.format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800207 else:
Omar El Ayach87222062020-03-25 19:34:12 -0700208 asserts.explicit_pass('Test Passed. {}'.format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700209
210 def process_testclass_results(self):
211 """Saves and plots test results from all executed test cases."""
Omar El Ayach96714c82019-01-28 18:51:46 -0800212 # write json output
Omar El Ayach33f80c02018-09-27 15:02:03 -0700213 testclass_results_dict = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700214 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
Omar El Ayacha210d572019-03-14 17:31:38 -0700215 channels_tested = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700216 for result in self.testclass_results:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700217 testcase_params = result['testcase_params']
Omar El Ayach55f51b52019-12-06 17:56:23 -0800218 test_id = self.extract_test_id(testcase_params, id_fields)
Omar El Ayacha210d572019-03-14 17:31:38 -0700219 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700220 if test_id not in testclass_results_dict:
221 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700222 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700223 if channel not in channels_tested:
224 channels_tested.append(channel)
Omar El Ayache9725962019-09-18 17:30:17 -0700225 if result['peak_throughput_pct'] >= 95:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700226 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700227 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700228 else:
229 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700230
Omar El Ayach55f51b52019-12-06 17:56:23 -0800231 # calculate average metrics
232 metrics_dict = collections.OrderedDict()
233 id_fields = ['channel', 'mode', 'num_streams', 'chain_mask']
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800234 for test_id in testclass_results_dict.keys():
235 for channel in testclass_results_dict[test_id].keys():
236 metric_tag = collections.OrderedDict(test_id, channel=channel)
237 metric_tag = self.extract_test_id(metric_tag, id_fields)
238 metric_tag = tuple(metric_tag.items())
239 metrics_dict.setdefault(metric_tag, [])
240 sensitivity_result = testclass_results_dict[test_id][channel]
241 if sensitivity_result != '':
242 metrics_dict[metric_tag].append(sensitivity_result)
Omar El Ayach55f51b52019-12-06 17:56:23 -0800243 for metric_tag_tuple, metric_data in metrics_dict.items():
244 metric_tag_dict = collections.OrderedDict(metric_tag_tuple)
245 metric_tag = 'ch{}_{}_nss{}_chain{}'.format(
246 metric_tag_dict['channel'], metric_tag_dict['mode'],
247 metric_tag_dict['num_streams'], metric_tag_dict['chain_mask'])
Omar El Ayachd4694892020-12-04 14:24:09 -0800248 metric_key = '{}.avg_sensitivity'.format(metric_tag)
Omar El Ayach55f51b52019-12-06 17:56:23 -0800249 metric_value = numpy.nanmean(metric_data)
250 self.testclass_metric_logger.add_metric(metric_key, metric_value)
251
Omar El Ayach96714c82019-01-28 18:51:46 -0800252 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700253 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700254 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700255 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800256 results_file_path = os.path.join(self.log_path, 'results.csv')
257 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800258 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
259 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700260 for test_id, test_results in testclass_results_dict.items():
261 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700262 if 'legacy' in test_id_dict['mode']:
263 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700264 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700265 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700266 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700267 if rate[:-1] == (test_id_dict['rate'],
268 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700269 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700270 'Mode': test_id_dict['mode'],
271 'MCS': test_id_dict['rate'],
272 'Streams': test_id_dict['num_streams'],
273 'Chain': test_id_dict['chain_mask'],
274 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700275 }
276 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700277 row_value['Ch. ' + str(channel)] = test_results.pop(
278 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700279 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800280
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700281 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800282 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700283
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800284 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700285 """Post processes RvR results to compute sensitivity.
286
287 Takes in the results of the RvR tests and computes the sensitivity of
288 the current rate by looking at the point at which throughput drops
289 below the percentage specified in the config file. The function then
290 calls on its parent class process_test_results to plot the result.
291
292 Args:
293 rvr_result: dict containing attenuation, throughput and other meta
294 data
295 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700296 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
297 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700298 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700299 throughput < rvr_result['peak_throughput'] *
300 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
301 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700302 ]
303 consistency_check = [
304 idx for idx in range(len(throughput_check))
305 if all(throughput_check[idx:])
306 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700307 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700308 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700309 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
310 rvr_result['atten_at_range'])
311 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
312 self.testbed_params['ap_tx_power_offset'][str(
313 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800314 WifiRvrTest.process_test_results(self, rvr_result)
315
316 def process_ping_test_results(self, testcase_params, ping_result):
317 """Post processes RvR results to compute sensitivity.
318
319 Takes in the results of the RvR tests and computes the sensitivity of
320 the current rate by looking at the point at which throughput drops
321 below the percentage specified in the config file. The function then
322 calls on its parent class process_test_results to plot the result.
323
324 Args:
325 rvr_result: dict containing attenuation, throughput and other meta
326 data
327 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800328 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700329 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
330 self.testbed_params['ap_tx_power_offset'][str(
331 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700332
Omar El Ayach03e40612019-05-01 16:25:39 -0700333 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700334 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700335 self.setup_ping_test(testcase_params)
336 self.run_sensitivity_test = self.run_ping_test
337 self.process_sensitivity_test_results = (
338 self.process_ping_test_results)
339 else:
340 self.setup_rvr_test(testcase_params)
341 self.run_sensitivity_test = self.run_rvr_test
342 self.process_sensitivity_test_results = (
343 self.process_rvr_test_results)
344
Omar El Ayach33f80c02018-09-27 15:02:03 -0700345 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800346 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700347
348 Args:
349 testcase_params: dict containing AP and other test params
350 """
351 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700352 testcase_params['channel'])
353 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700354 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700355 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700356 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700357 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700358 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700359 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700360 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700361 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700362 self.access_point.set_region(self.testbed_params['default_region'])
363 self.access_point.set_channel(band, testcase_params['channel'])
364 self.access_point.set_bandwidth(band, testcase_params['mode'])
365 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800366 self.access_point.set_rate(band, testcase_params['mode'],
367 testcase_params['num_streams'],
368 testcase_params['rate'],
369 testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800370 # Set attenuator offsets and set attenuators to initial condition
371 atten_offsets = self.testbed_params['chain_offset'][str(
372 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800373 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800374 if 'AP-Chain-0' in atten.path:
375 atten.offset = atten_offsets[0]
376 elif 'AP-Chain-1' in atten.path:
377 atten.offset = atten_offsets[1]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800378 else:
379 atten.offset = 0
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700380 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700381 self.access_point.ap_settings))
382
Omar El Ayach6e518a22019-06-13 13:55:42 -0700383 def setup_dut(self, testcase_params):
384 """Sets up the DUT in the configuration required by the test.
385
386 Args:
387 testcase_params: dict containing AP and other test params
388 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700389 # Check battery level before test
390 if not wputils.health_check(self.dut, 10):
391 asserts.skip('Battery level too low. Skipping test.')
392 # Turn screen off to preserve battery
393 self.dut.go_to_sleep()
Omar El Ayach35bf3b82019-12-06 19:29:19 -0800394 if wputils.validate_network(self.dut,
395 testcase_params['test_network']['SSID']):
Omar El Ayach39acf802019-08-02 17:52:39 -0700396 self.log.info('Already connected to desired network')
397 else:
398 wutils.reset_wifi(self.dut)
Roshan Pius5b19a122019-09-13 08:07:30 -0700399 wutils.set_wifi_country_code(self.dut,
Omar El Ayache5b8be12019-10-01 16:27:12 -0700400 self.testclass_params['country_code'])
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800401 testcase_params['test_network']['channel'] = testcase_params[
402 'channel']
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800403 wutils.wifi_connect(self.dut,
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800404 testcase_params['test_network'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800405 num_of_tries=5,
406 check_connectivity=False)
Omar El Ayach39acf802019-08-02 17:52:39 -0700407 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayache5b8be12019-10-01 16:27:12 -0700408 # Activate/attenuate the correct chains
Omar El Ayach16f5f342019-11-23 12:27:56 -0800409 if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
410 self.atten_dut_chain_map[testcase_params[
411 'channel']] = wputils.get_current_atten_dut_chain_map(
412 self.attenuators, self.dut, self.ping_server)
Omar El Ayachd4694892020-12-04 14:24:09 -0800413 self.log.info('Current Attenuator-DUT Chain Map: {}'.format(
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800414 self.atten_dut_chain_map[testcase_params['channel']]))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700415 for idx, atten in enumerate(self.attenuators):
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800416 if self.atten_dut_chain_map[testcase_params['channel']][
417 idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700418 atten.offset = atten.instrument.max_atten
419
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700420 def extract_test_id(self, testcase_params, id_fields):
421 test_id = collections.OrderedDict(
422 (param, testcase_params[param]) for param in id_fields)
423 return test_id
424
425 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700426 """Gets the starting attenuation for this sensitivity test.
427
428 The function gets the starting attenuation by checking whether a test
429 as the next higher MCS has been executed. If so it sets the starting
430 point a configurable number of dBs below the next MCS's sensitivity.
431
432 Returns:
433 start_atten: starting attenuation for current test
434 """
Omar El Ayach87222062020-03-25 19:34:12 -0700435 # If the test is being retried, start from the beginning
436 if self.retry_flag:
437 self.log.info('Retry flag set. Setting attenuation to minimum.')
438 return self.testclass_params['atten_start']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700439 # Get the current and reference test config. The reference test is the
440 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700441 current_rate = testcase_params['rate']
442 ref_test_params = self.extract_test_id(
443 testcase_params,
444 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
445 if 'legacy' in testcase_params['mode']:
446 if testcase_params['channel'] <= 13:
447 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700448 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700449 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700450 ref_index = max(
451 0,
452 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
453 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700454 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700455 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700456 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700457
458 # Check if reference test has been run and set attenuation accordingly
459 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700460 self.extract_test_id(
461 result['testcase_params'],
462 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700463 for result in self.testclass_results
464 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700465
Omar El Ayach33f80c02018-09-27 15:02:03 -0700466 try:
467 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700468 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700469 'atten_at_range'] - (
470 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700471 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700472 self.log.warning(
473 'Reference test not found. Starting from {} dB'.format(
474 self.testclass_params['atten_start']))
475 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700476 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700477 return start_atten
478
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700479 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700480 """Function that generates test params based on the test name."""
Omar El Ayachc27b7f92019-12-04 15:46:43 -0800481 band = self.access_point.band_lookup_by_channel(
482 testcase_params['channel'])
483 testcase_params['test_network'] = self.main_network[band]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700484 if testcase_params['chain_mask'] in ['0', '1']:
485 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
486 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700487 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700488 # Set attenuated chain to -1. Do not set to None as this will be
489 # compared to RF chain map which may include None
490 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800491
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700492 self.testclass_params[
493 'range_ping_loss_threshold'] = 100 - self.testclass_params[
494 'throughput_pct_at_sensitivity']
495 if self.testclass_params['traffic_type'] == 'UDP':
496 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
497 self.testclass_params['iperf_duration'],
498 self.testclass_params['UDP_rates'][testcase_params['mode']])
499 elif self.testclass_params['traffic_type'] == 'TCP':
500 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
501 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800502
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700503 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700504 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700505 testcase_params['iperf_args'] += ' -R'
506 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800507 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700508 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800509
Omar El Ayach33f80c02018-09-27 15:02:03 -0700510 return testcase_params
511
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700512 def _test_sensitivity(self, testcase_params):
Omar El Ayachd4694892020-12-04 14:24:09 -0800513 """Function that gets called for each test case
Omar El Ayach33f80c02018-09-27 15:02:03 -0700514
515 The function gets called in each rvr test case. The function customizes
516 the rvr test based on the test name of the test that called it
517 """
518 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700519 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700520 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700521 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800522 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700523 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
524 testcase_params['atten_step'])
525 testcase_params['atten_range'] = [
526 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800527 for x in range(0, num_atten_steps)
528 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700529
530 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700531 self.setup_sensitivity_test(testcase_params)
532 result = self.run_sensitivity_test(testcase_params)
533 self.process_sensitivity_test_results(testcase_params, result)
534
Omar El Ayach33f80c02018-09-27 15:02:03 -0700535 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800536 self.testclass_results.append(result)
537 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700538
Omar El Ayachffb5a462019-09-16 21:05:44 -0700539 def generate_test_cases(self, channels, modes, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700540 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700541 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700542 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800543 requested_modes = [
544 mode for mode in modes
545 if mode in self.VALID_TEST_CONFIGS[channel]
546 ]
Omar El Ayachffb5a462019-09-16 21:05:44 -0700547 for mode in requested_modes:
Omar El Ayachd4694892020-12-04 14:24:09 -0800548 bandwidth = int(''.join([x for x in mode if x.isdigit()]))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700549 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700550 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700551 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700552 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700553 elif 'legacy' in mode and channel < 14:
554 rates = self.VALID_RATES['legacy_2GHz']
555 elif 'legacy' in mode and channel > 14:
556 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700557 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700558 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800559 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700560 testcase_params = collections.OrderedDict(
561 channel=channel,
562 mode=mode,
Omar El Ayachd4694892020-12-04 14:24:09 -0800563 bandwidth=bandwidth,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700564 rate=rate.mcs,
565 num_streams=rate.streams,
566 short_gi=1,
567 chain_mask=chain)
568 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800569 # Do not test 2-stream rates in single chain mode
570 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700571 if 'legacy' in mode:
572 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
573 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700574 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700575 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700576 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700577 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700578 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
579 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700580 channel, mode, rate.mcs,
581 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700582 setattr(self, testcase_name,
583 partial(self._test_sensitivity, testcase_params))
584 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700585 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700586
587
588class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
589 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700590 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700591 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700592 [6, 36, 40, 44, 48, 149, 153, 157, 161],
593 ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
Omar El Ayach9873c082019-09-04 12:14:50 -0700594
595
596class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
597 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700598 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800599 self.tests = self.generate_test_cases([6, 36, 149],
600 ['VHT20', 'VHT40', 'VHT80'],
601 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700602
603
604class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
605 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700606 super().__init__(controllers)
Omar El Ayache9725962019-09-18 17:30:17 -0700607 self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
608 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700609
610
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800611class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
612 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700613 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700614 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700615 [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
616 ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800617
618
Omar El Ayach33f80c02018-09-27 15:02:03 -0700619class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
620 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700621 super().__init__(controllers)
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800622 self.tests = self.generate_test_cases([36, 40, 44, 48],
623 ['VHT20', 'VHT40', 'VHT80'],
624 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700625
626
627class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
628 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700629 super().__init__(controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700630 self.tests = self.generate_test_cases([149, 153, 157, 161],
Omar El Ayachffb5a462019-09-16 21:05:44 -0700631 ['VHT20', 'VHT40', 'VHT80'],
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700632 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700633
634
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700635# Over-the air version of senstivity tests
636class WifiOtaSensitivityTest(WifiSensitivityTest):
637 """Class to test over-the-air senstivity.
638
639 This class implements measures WiFi sensitivity tests in an OTA chamber.
640 It allows setting orientation and other chamber parameters to study
641 performance in varying channel conditions
642 """
Omar El Ayach40099d02019-09-12 15:17:33 -0700643 def __init__(self, controllers):
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700644 base_test.BaseTestClass.__init__(self, controllers)
645 self.testcase_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700646 BlackboxMappedMetricLogger.for_test_case())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700647 self.testclass_metric_logger = (
Xianyuan Jia976d4042019-09-30 17:19:47 -0700648 BlackboxMappedMetricLogger.for_test_class())
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700649 self.publish_testcase_metrics = False
Omar El Ayach40099d02019-09-12 15:17:33 -0700650
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700651 def setup_class(self):
652 WifiSensitivityTest.setup_class(self)
Omar El Ayache5b8be12019-10-01 16:27:12 -0700653 self.current_chain_mask = '2x2'
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700654 self.ota_chamber = ota_chamber.create(
655 self.user_params['OTAChamber'])[0]
656
657 def teardown_class(self):
658 WifiSensitivityTest.teardown_class(self)
659 self.ota_chamber.reset_chamber()
660
661 def setup_sensitivity_test(self, testcase_params):
662 # Setup turntable
663 self.ota_chamber.set_orientation(testcase_params['orientation'])
664 # Continue test setup
665 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
666
Omar El Ayache5b8be12019-10-01 16:27:12 -0700667 def setup_dut(self, testcase_params):
668 """Sets up the DUT in the configuration required by the test.
669
670 Args:
671 testcase_params: dict containing AP and other test params
672 """
673 # Configure the right INI settings
674 if testcase_params['chain_mask'] != self.current_chain_mask:
675 self.log.info('Updating WiFi chain mask to: {}'.format(
676 testcase_params['chain_mask']))
Omar El Ayachf7143fc2020-02-03 10:15:55 -0800677 self.current_chain_mask = testcase_params['chain_mask']
Omar El Ayache5b8be12019-10-01 16:27:12 -0700678 if testcase_params['chain_mask'] in ['0', '1']:
679 wputils.set_ini_single_chain_mode(
680 self.dut, int(testcase_params['chain_mask']))
681 else:
682 wputils.set_ini_two_chain_mode(self.dut)
683 # Check battery level before test
684 if not wputils.health_check(self.dut, 10):
685 asserts.skip('Battery level too low. Skipping test.')
686 # Turn screen off to preserve battery
687 self.dut.go_to_sleep()
688 if wputils.validate_network(self.dut,
689 testcase_params['test_network']['SSID']):
690 self.log.info('Already connected to desired network')
691 else:
692 wutils.reset_wifi(self.dut)
693 wutils.set_wifi_country_code(self.dut,
694 self.testclass_params['country_code'])
695 testcase_params['test_network']['channel'] = testcase_params[
696 'channel']
697 wutils.wifi_connect(self.dut,
698 testcase_params['test_network'],
699 num_of_tries=5,
700 check_connectivity=False)
701 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
702
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700703 def process_testclass_results(self):
704 """Saves and plots test results from all executed test cases."""
705 testclass_results_dict = collections.OrderedDict()
Omar El Ayach27114262020-02-06 15:30:08 -0800706 id_fields = ['channel', 'mode', 'rate']
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700707 plots = []
708 for result in self.testclass_results:
709 test_id = self.extract_test_id(result['testcase_params'],
710 id_fields)
711 test_id = tuple(test_id.items())
Omar El Ayach27114262020-02-06 15:30:08 -0800712 chain_mask = result['testcase_params']['chain_mask']
713 num_streams = result['testcase_params']['num_streams']
714 line_id = (chain_mask, num_streams)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700715 if test_id not in testclass_results_dict:
716 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayach27114262020-02-06 15:30:08 -0800717 if line_id not in testclass_results_dict[test_id]:
718 testclass_results_dict[test_id][line_id] = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700719 'orientation': [],
720 'sensitivity': []
721 }
Omar El Ayach87222062020-03-25 19:34:12 -0700722 orientation = result['testcase_params']['orientation']
Omar El Ayache9725962019-09-18 17:30:17 -0700723 if result['peak_throughput_pct'] >= 95:
Omar El Ayach87222062020-03-25 19:34:12 -0700724 sensitivity = result['sensitivity']
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700725 else:
Omar El Ayach87222062020-03-25 19:34:12 -0700726 sensitivity = float('nan')
727 if orientation not in testclass_results_dict[test_id][line_id][
728 'orientation']:
729 testclass_results_dict[test_id][line_id]['orientation'].append(
730 orientation)
Omar El Ayach27114262020-02-06 15:30:08 -0800731 testclass_results_dict[test_id][line_id]['sensitivity'].append(
Omar El Ayach87222062020-03-25 19:34:12 -0700732 sensitivity)
733 else:
734 testclass_results_dict[test_id][line_id]['sensitivity'][
735 -1] = sensitivity
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700736
737 for test_id, test_data in testclass_results_dict.items():
738 test_id_dict = dict(test_id)
739 if 'legacy' in test_id_dict['mode']:
Omar El Ayach27114262020-02-06 15:30:08 -0800740 test_id_str = 'Channel {} - {} {}Mbps'.format(
741 test_id_dict['channel'], test_id_dict['mode'],
742 test_id_dict['rate'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700743 else:
Omar El Ayach27114262020-02-06 15:30:08 -0800744 test_id_str = 'Channel {} - {} MCS{}'.format(
745 test_id_dict['channel'], test_id_dict['mode'],
746 test_id_dict['rate'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700747 curr_plot = wputils.BokehFigure(
748 title=str(test_id_str),
749 x_label='Orientation (deg)',
Omar El Ayach954eb282019-09-30 15:33:32 -0700750 primary_y_label='Sensitivity (dBm)')
Omar El Ayach27114262020-02-06 15:30:08 -0800751 for line_id, line_results in test_data.items():
752 curr_plot.add_line(line_results['orientation'],
753 line_results['sensitivity'],
754 legend='Nss{} - Chain Mask {}'.format(
755 line_id[1], line_id[0]),
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800756 marker='circle')
Omar El Ayach27114262020-02-06 15:30:08 -0800757 if 'legacy' in test_id_dict['mode']:
758 metric_tag = 'ota_summary_ch{}_{}_{}_ch{}'.format(
759 test_id_dict['channel'], test_id_dict['mode'],
760 test_id_dict['rate'], line_id[0])
761 else:
762 metric_tag = 'ota_summary_ch{}_{}_mcs{}_nss{}_ch{}'.format(
763 test_id_dict['channel'], test_id_dict['mode'],
764 test_id_dict['rate'], line_id[1], line_id[0])
765
Omar El Ayach40099d02019-09-12 15:17:33 -0700766 metric_name = metric_tag + '.avg_sensitivity'
Omar El Ayach27114262020-02-06 15:30:08 -0800767 metric_value = numpy.nanmean(line_results['sensitivity'])
Omar El Ayach02a1cce2019-09-19 17:51:39 -0700768 self.testclass_metric_logger.add_metric(
769 metric_name, metric_value)
Omar El Ayachd4694892020-12-04 14:24:09 -0800770 self.log.info(('Average Sensitivity for {}: {:.1f}').format(
Omar El Ayache9725962019-09-18 17:30:17 -0700771 metric_tag, metric_value))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700772 current_context = (
773 context.get_current_context().get_full_output_path())
774 output_file_path = os.path.join(current_context,
775 str(test_id_str) + '.html')
776 curr_plot.generate_figure(output_file_path)
777 plots.append(curr_plot)
778 output_file_path = os.path.join(current_context, 'results.html')
779 wputils.BokehFigure.save_figures(plots, output_file_path)
780
781 def get_start_atten(self, testcase_params):
782 """Gets the starting attenuation for this sensitivity test.
783
784 The function gets the starting attenuation by checking whether a test
785 at the same rate configuration has executed. If so it sets the starting
786 point a configurable number of dBs below the reference test.
787
788 Returns:
789 start_atten: starting attenuation for current test
790 """
Omar El Ayach87222062020-03-25 19:34:12 -0700791 # If the test is being retried, start from the beginning
792 if self.retry_flag:
793 self.log.info('Retry flag set. Setting attenuation to minimum.')
794 return self.testclass_params['atten_start']
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700795 # Get the current and reference test config. The reference test is the
796 # one performed at the current MCS+1
797 ref_test_params = self.extract_test_id(
798 testcase_params,
799 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
800 # Check if reference test has been run and set attenuation accordingly
801 previous_params = [
802 self.extract_test_id(
803 result['testcase_params'],
804 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
805 for result in self.testclass_results
806 ]
807 try:
808 ref_index = previous_params[::-1].index(ref_test_params)
809 ref_index = len(previous_params) - 1 - ref_index
810 start_atten = self.testclass_results[ref_index][
811 'atten_at_range'] - (
812 self.testclass_params['adjacent_mcs_range_gap'])
813 except ValueError:
814 print('Reference test not found. Starting from {} dB'.format(
815 self.testclass_params['atten_start']))
816 start_atten = self.testclass_params['atten_start']
Omar El Ayache9725962019-09-18 17:30:17 -0700817 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700818 return start_atten
819
Omar El Ayachffb5a462019-09-16 21:05:44 -0700820 def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700821 angles):
822 """Function that auto-generates test cases for a test class."""
823 test_cases = []
824 for channel in channels:
Omar El Ayachcbf6ba92019-12-07 11:54:06 -0800825 requested_modes = [
826 mode for mode in modes
827 if mode in self.VALID_TEST_CONFIGS[channel]
828 ]
Omar El Ayacheacef872020-01-31 16:22:28 -0800829 for chain, mode in itertools.product(chain_mask, requested_modes):
Omar El Ayachd4694892020-12-04 14:24:09 -0800830 bandwidth = int(''.join([x for x in mode if x.isdigit()]))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700831 if 'VHT' in mode:
832 valid_rates = self.VALID_RATES[mode]
833 elif 'HT' in mode:
834 valid_rates = self.VALID_RATES[mode]
835 elif 'legacy' in mode and channel < 14:
836 valid_rates = self.VALID_RATES['legacy_2GHz']
837 elif 'legacy' in mode and channel > 14:
838 valid_rates = self.VALID_RATES['legacy_5GHz']
839 else:
840 raise ValueError('Invalid test mode.')
Omar El Ayacheacef872020-01-31 16:22:28 -0800841 for rate, angle in itertools.product(valid_rates, angles):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700842 testcase_params = collections.OrderedDict(
843 channel=channel,
844 mode=mode,
Omar El Ayachd4694892020-12-04 14:24:09 -0800845 bandwidth=bandwidth,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700846 rate=rate.mcs,
847 num_streams=rate.streams,
848 short_gi=1,
849 chain_mask=chain,
850 orientation=angle)
851 if rate not in requested_rates:
852 continue
853 if str(chain) in ['0', '1'] and rate[1] == 2:
854 # Do not test 2-stream rates in single chain mode
855 continue
856 if 'legacy' in mode:
857 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
858 '_ch{}_{}deg'.format(
859 channel, mode,
860 str(rate.mcs).replace('.', 'p'),
861 rate.streams, chain, angle))
862 else:
863 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
864 '_ch{}_{}deg'.format(
865 channel, mode, rate.mcs,
866 rate.streams, chain, angle))
867 setattr(self, testcase_name,
868 partial(self._test_sensitivity, testcase_params))
869 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700870 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700871
872
Omar El Ayacha8ed99f2019-09-18 13:41:58 -0700873class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700874 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700875 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700876 requested_channels = [6, 36, 149]
877 requested_rates = [
878 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700879 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700880 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700881 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700882 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800883 self.tests = self.generate_test_cases(requested_channels,
884 ['VHT20', 'VHT80'],
885 requested_rates, ['2x2'],
886 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700887
888
Omar El Ayache5b8be12019-10-01 16:27:12 -0700889class WifiOtaSensitivity_PerChain_TenDegree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700890 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700891 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700892 requested_channels = [6, 36, 149]
Omar El Ayach27114262020-02-06 15:30:08 -0800893 requested_rates = [
894 self.RateTuple(2, 1, 21.7),
895 self.RateTuple(2, 2, 43.3)
896 ]
Omar El Ayache5b8be12019-10-01 16:27:12 -0700897 self.tests = self.generate_test_cases(requested_channels, ['VHT20'],
898 requested_rates,
899 ['0', '1', '2x2'],
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800900 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700901
902
Omar El Ayachd3850d02019-09-23 16:35:49 -0700903class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
904 def __init__(self, controllers):
905 WifiOtaSensitivityTest.__init__(self, controllers)
906 requested_channels = [6, 36, 149]
907 requested_rates = [
908 self.RateTuple(9, 1, 96),
909 self.RateTuple(8, 1, 86.7),
910 self.RateTuple(7, 1, 72.2),
911 self.RateTuple(4, 1, 43.3),
912 self.RateTuple(2, 1, 21.7),
913 self.RateTuple(0, 1, 7.2),
914 self.RateTuple(9, 2, 192),
915 self.RateTuple(8, 2, 173.3),
916 self.RateTuple(7, 2, 144.4),
917 self.RateTuple(4, 2, 86.7),
918 self.RateTuple(2, 2, 43.3),
919 self.RateTuple(0, 2, 14.4)
920 ]
Omar El Ayacha218f6b2019-11-22 18:24:48 -0800921 self.tests = self.generate_test_cases(requested_channels,
922 ['VHT20', 'VHT80'],
923 requested_rates, ['2x2'],
924 list(range(0, 360, 30)))
Omar El Ayachd3850d02019-09-23 16:35:49 -0700925
926
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700927class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700928 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700929 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700930 requested_rates = [
931 self.RateTuple(8, 1, 86.7),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700932 self.RateTuple(2, 1, 21.7),
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700933 self.RateTuple(8, 2, 173.3),
Omar El Ayachd9024eb2019-09-16 18:22:46 -0700934 self.RateTuple(2, 2, 43.3)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700935 ]
936 self.tests = self.generate_test_cases(
Omar El Ayachffb5a462019-09-16 21:05:44 -0700937 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
938 requested_rates, ['2x2'], list(range(0, 360, 45)))