blob: 2c8f1e3226bfa5eae1d0281a0f4d52d8ff6edd55 [file] [log] [blame]
Omar El Ayach33f80c02018-09-27 15:02:03 -07001#!/usr/bin/env python3.4
2#
3# Copyright 2017 - The Android Open Source Project
4#
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -07005# Licensed under the Apache License, Version 2.0 (the 'License');
Omar El Ayach33f80c02018-09-27 15:02:03 -07006# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -070012# distributed under the License is distributed on an 'AS IS' BASIS,
Omar El Ayach33f80c02018-09-27 15:02:03 -070013# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import collections
Omar El Ayach96714c82019-01-28 18:51:46 -080018import csv
19import itertools
Omar El Ayach33f80c02018-09-27 15:02:03 -070020import json
21import logging
22import 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
Omar El Ayach5fbc1222018-12-07 18:10:05 -080029from acts.metrics.loggers.blackbox import BlackboxMetricLogger
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)
128 self.failure_count_metric = BlackboxMetricLogger.for_test_case(
129 metric_name='sensitivity')
130
Omar El Ayach33f80c02018-09-27 15:02:03 -0700131 def setup_class(self):
132 """Initializes common test hardware and parameters.
133
134 This function initializes hardwares and compiles parameters that are
135 common to all tests in this class.
136 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700137 self.dut = self.android_devices[-1]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700138 req_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700139 'RetailAccessPoints', 'sensitivity_test_params', 'testbed_params',
140 'RemoteServer'
Omar El Ayach33f80c02018-09-27 15:02:03 -0700141 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700142 opt_params = ['main_network', 'golden_files_list']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700143 self.unpack_userparams(req_params, opt_params)
144 self.testclass_params = self.sensitivity_test_params
145 self.num_atten = self.attenuators[0].instrument.num_atten
Omar El Ayach14416ac2019-01-30 14:58:19 -0800146 self.ping_server = ssh.connection.SshConnection(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700147 ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700148 self.iperf_server = self.iperf_servers[0]
Omar El Ayach14416ac2019-01-30 14:58:19 -0800149 self.iperf_client = self.iperf_clients[0]
Omar El Ayacha210d572019-03-14 17:31:38 -0700150 self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700151 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700152 self.access_point.ap_settings))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700153 self.log_path = os.path.join(logging.log_path, 'results')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700154 utils.create_dir(self.log_path)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700155 if not hasattr(self, 'golden_files_list'):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700156 self.golden_files_list = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700157 os.path.join(self.testbed_params['golden_results_path'], file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700158 for file in os.listdir(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700159 self.testbed_params['golden_results_path'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700160 ]
161 self.testclass_results = []
162
163 # Turn WiFi ON
164 for dev in self.android_devices:
165 wutils.wifi_toggle_state(dev, True)
166
Omar El Ayach96714c82019-01-28 18:51:46 -0800167 def teardown_class(self):
168 # Turn WiFi OFF
169 for dev in self.android_devices:
170 wutils.wifi_toggle_state(dev, False)
171 self.process_testclass_results()
172
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800173 def pass_fail_check(self, result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700174 """Checks sensitivity against golden results and decides on pass/fail.
175
176 Args:
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800177 result: dict containing attenuation, throughput and other meta
Omar El Ayach33f80c02018-09-27 15:02:03 -0700178 data
179 """
180 try:
181 golden_path = next(file_name
182 for file_name in self.golden_files_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700183 if 'sensitivity_targets' in file_name)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700184 with open(golden_path, 'r') as golden_file:
185 golden_results = json.load(golden_file)
Omar El Ayacha210d572019-03-14 17:31:38 -0700186 golden_sensitivity = golden_results[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700187 self.current_test_name]['sensitivity']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700188 except:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700189 golden_sensitivity = float('nan')
Omar El Ayach33f80c02018-09-27 15:02:03 -0700190
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700191 result_string = ('Througput = {}%, Sensitivity = {}.'
192 'Target Sensitivity = {}'.format(
193 result['peak_throughput_pct'],
194 result['sensitivity'], golden_sensitivity))
195 if result['peak_throughput_pct'] < 100:
196 self.log.warning('Result unreliable. Peak rate unstable')
197 if result['sensitivity'] - golden_sensitivity < self.testclass_params[
198 'sensitivity_tolerance']:
199 asserts.explicit_pass('Test Passed. {}'.format(result_string))
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800200 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700201 asserts.fail('Test Failed. {}'.format(result_string))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700202
203 def process_testclass_results(self):
204 """Saves and plots test results from all executed test cases."""
Omar El Ayach96714c82019-01-28 18:51:46 -0800205 # write json output
Omar El Ayach33f80c02018-09-27 15:02:03 -0700206 testclass_results_dict = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700207 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
Omar El Ayacha210d572019-03-14 17:31:38 -0700208 channels_tested = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700209 for result in self.testclass_results:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700210 testcase_params = result['testcase_params']
Omar El Ayacha210d572019-03-14 17:31:38 -0700211 test_id = collections.OrderedDict(
212 (key, value) for key, value in testcase_params.items()
213 if key in id_fields)
214 test_id = tuple(test_id.items())
Omar El Ayach6e518a22019-06-13 13:55:42 -0700215 if test_id not in testclass_results_dict:
216 testclass_results_dict[test_id] = collections.OrderedDict()
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700217 channel = testcase_params['channel']
Omar El Ayacha210d572019-03-14 17:31:38 -0700218 if channel not in channels_tested:
219 channels_tested.append(channel)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700220 if result['peak_throughput_pct'] == 100:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700221 testclass_results_dict[test_id][channel] = result[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700222 'sensitivity']
Omar El Ayach6e518a22019-06-13 13:55:42 -0700223 else:
224 testclass_results_dict[test_id][channel] = ''
Omar El Ayacha210d572019-03-14 17:31:38 -0700225
Omar El Ayach96714c82019-01-28 18:51:46 -0800226 # write csv
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700227 csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
Omar El Ayacha210d572019-03-14 17:31:38 -0700228 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700229 csv_header.append('Ch. ' + str(channel))
Omar El Ayach96714c82019-01-28 18:51:46 -0800230 results_file_path = os.path.join(self.log_path, 'results.csv')
231 with open(results_file_path, mode='w') as csv_file:
Omar El Ayach96714c82019-01-28 18:51:46 -0800232 writer = csv.DictWriter(csv_file, fieldnames=csv_header)
233 writer.writeheader()
Omar El Ayacha210d572019-03-14 17:31:38 -0700234 for test_id, test_results in testclass_results_dict.items():
235 test_id_dict = dict(test_id)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700236 if 'legacy' in test_id_dict['mode']:
237 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayacha210d572019-03-14 17:31:38 -0700238 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700239 rate_list = self.VALID_RATES[test_id_dict['mode']]
Omar El Ayacha210d572019-03-14 17:31:38 -0700240 data_rate = next(rate.data_rate for rate in rate_list
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700241 if rate[:-1] == (test_id_dict['rate'],
242 test_id_dict['num_streams']))
Omar El Ayacha210d572019-03-14 17:31:38 -0700243 row_value = {
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700244 'Mode': test_id_dict['mode'],
245 'MCS': test_id_dict['rate'],
246 'Streams': test_id_dict['num_streams'],
247 'Chain': test_id_dict['chain_mask'],
248 'Rate (Mbps)': data_rate,
Omar El Ayacha210d572019-03-14 17:31:38 -0700249 }
250 for channel in channels_tested:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700251 row_value['Ch. ' + str(channel)] = test_results.pop(
252 channel, ' ')
Omar El Ayacha210d572019-03-14 17:31:38 -0700253 writer.writerow(row_value)
Omar El Ayach96714c82019-01-28 18:51:46 -0800254
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700255 if not self.testclass_params['traffic_type'].lower() == 'ping':
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800256 WifiRvrTest.process_testclass_results(self)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700257
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800258 def process_rvr_test_results(self, testcase_params, rvr_result):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700259 """Post processes RvR results to compute sensitivity.
260
261 Takes in the results of the RvR tests and computes the sensitivity of
262 the current rate by looking at the point at which throughput drops
263 below the percentage specified in the config file. The function then
264 calls on its parent class process_test_results to plot the result.
265
266 Args:
267 rvr_result: dict containing attenuation, throughput and other meta
268 data
269 """
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700270 rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
271 rvr_result['peak_throughput_pct'] = 100
Omar El Ayach33f80c02018-09-27 15:02:03 -0700272 throughput_check = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700273 throughput < rvr_result['peak_throughput'] *
274 (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
275 for throughput in rvr_result['throughput_receive']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700276 ]
277 consistency_check = [
278 idx for idx in range(len(throughput_check))
279 if all(throughput_check[idx:])
280 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700281 rvr_result['atten_at_range'] = rvr_result['attenuation'][
Omar El Ayach33f80c02018-09-27 15:02:03 -0700282 consistency_check[0] - 1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700283 rvr_result['range'] = rvr_result['fixed_attenuation'] + (
284 rvr_result['atten_at_range'])
285 rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
286 self.testbed_params['ap_tx_power_offset'][str(
287 testcase_params['channel'])] - rvr_result['range'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800288 WifiRvrTest.process_test_results(self, rvr_result)
289
290 def process_ping_test_results(self, testcase_params, ping_result):
291 """Post processes RvR results to compute sensitivity.
292
293 Takes in the results of the RvR tests and computes the sensitivity of
294 the current rate by looking at the point at which throughput drops
295 below the percentage specified in the config file. The function then
296 calls on its parent class process_test_results to plot the result.
297
298 Args:
299 rvr_result: dict containing attenuation, throughput and other meta
300 data
301 """
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800302 WifiPingTest.process_ping_results(self, testcase_params, ping_result)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700303 ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
304 self.testbed_params['ap_tx_power_offset'][str(
305 testcase_params['channel'])] - ping_result['range'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700306
Omar El Ayach03e40612019-05-01 16:25:39 -0700307 def setup_sensitivity_test(self, testcase_params):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700308 if testcase_params['traffic_type'].lower() == 'ping':
Omar El Ayach03e40612019-05-01 16:25:39 -0700309 self.setup_ping_test(testcase_params)
310 self.run_sensitivity_test = self.run_ping_test
311 self.process_sensitivity_test_results = (
312 self.process_ping_test_results)
313 else:
314 self.setup_rvr_test(testcase_params)
315 self.run_sensitivity_test = self.run_rvr_test
316 self.process_sensitivity_test_results = (
317 self.process_rvr_test_results)
318
Omar El Ayach33f80c02018-09-27 15:02:03 -0700319 def setup_ap(self, testcase_params):
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800320 """Sets up the AP and attenuator to compensate for AP chain imbalance.
Omar El Ayach33f80c02018-09-27 15:02:03 -0700321
322 Args:
323 testcase_params: dict containing AP and other test params
324 """
325 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700326 testcase_params['channel'])
327 if '2G' in band:
Omar El Ayacha210d572019-03-14 17:31:38 -0700328 frequency = wutils.WifiEnums.channel_2G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700329 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700330 else:
Omar El Ayacha210d572019-03-14 17:31:38 -0700331 frequency = wutils.WifiEnums.channel_5G_to_freq[
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700332 testcase_params['channel']]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700333 if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700334 self.access_point.set_region(self.testbed_params['DFS_region'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700335 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700336 self.access_point.set_region(self.testbed_params['default_region'])
337 self.access_point.set_channel(band, testcase_params['channel'])
338 self.access_point.set_bandwidth(band, testcase_params['mode'])
339 self.access_point.set_power(band, testcase_params['ap_tx_power'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700340 self.access_point.set_rate(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700341 band, testcase_params['mode'], testcase_params['num_streams'],
342 testcase_params['rate'], testcase_params['short_gi'])
Omar El Ayach96714c82019-01-28 18:51:46 -0800343 # Set attenuator offsets and set attenuators to initial condition
344 atten_offsets = self.testbed_params['chain_offset'][str(
345 testcase_params['channel'])]
Omar El Ayach5a1496bf2019-01-09 11:43:02 -0800346 for atten in self.attenuators:
Omar El Ayach96714c82019-01-28 18:51:46 -0800347 if 'AP-Chain-0' in atten.path:
348 atten.offset = atten_offsets[0]
349 elif 'AP-Chain-1' in atten.path:
350 atten.offset = atten_offsets[1]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700351 self.log.info('Access Point Configuration: {}'.format(
Omar El Ayach33f80c02018-09-27 15:02:03 -0700352 self.access_point.ap_settings))
353
Omar El Ayach6e518a22019-06-13 13:55:42 -0700354 def setup_dut(self, testcase_params):
355 """Sets up the DUT in the configuration required by the test.
356
357 Args:
358 testcase_params: dict containing AP and other test params
359 """
Omar El Ayach656bf3d2019-07-31 15:04:53 -0700360 # Check battery level before test
361 if not wputils.health_check(self.dut, 10):
362 asserts.skip('Battery level too low. Skipping test.')
363 # Turn screen off to preserve battery
364 self.dut.go_to_sleep()
Omar El Ayach6e518a22019-06-13 13:55:42 -0700365 band = self.access_point.band_lookup_by_channel(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700366 testcase_params['channel'])
Omar El Ayach39acf802019-08-02 17:52:39 -0700367 current_network = self.dut.droid.wifiGetConnectionInfo()
368 valid_connection = wutils.validate_connection(self.dut)
369 if valid_connection and current_network['SSID'] == self.main_network[
370 band]['SSID']:
371 self.log.info('Already connected to desired network')
372 else:
373 wutils.reset_wifi(self.dut)
Roshan Pius0367e082019-09-13 08:07:30 -0700374 wutils.set_wifi_country_code(self.dut,
Omar El Ayach39acf802019-08-02 17:52:39 -0700375 self.testclass_params['country_code'])
376 self.main_network[band]['channel'] = testcase_params['channel']
377 wutils.wifi_connect(
378 self.dut,
379 self.main_network[band],
380 num_of_tries=5,
381 check_connectivity=False)
382 self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
Omar El Ayach189dcbd2019-08-28 17:26:18 -0700383 atten_dut_chain_map = wputils.get_current_atten_dut_chain_map(
384 self.attenuators, self.dut, self.ping_server)
Omar El Ayach49141c02019-09-16 16:43:51 -0700385 self.log.info(
386 "Current Attenuator-DUT Chain Map: {}".format(atten_dut_chain_map))
Omar El Ayach6e518a22019-06-13 13:55:42 -0700387 for idx, atten in enumerate(self.attenuators):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700388 if atten_dut_chain_map[idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700389 atten.offset = atten.instrument.max_atten
390
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700391 def extract_test_id(self, testcase_params, id_fields):
392 test_id = collections.OrderedDict(
393 (param, testcase_params[param]) for param in id_fields)
394 return test_id
395
396 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700397 """Gets the starting attenuation for this sensitivity test.
398
399 The function gets the starting attenuation by checking whether a test
400 as the next higher MCS has been executed. If so it sets the starting
401 point a configurable number of dBs below the next MCS's sensitivity.
402
403 Returns:
404 start_atten: starting attenuation for current test
405 """
406 # Get the current and reference test config. The reference test is the
407 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700408 current_rate = testcase_params['rate']
409 ref_test_params = self.extract_test_id(
410 testcase_params,
411 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
412 if 'legacy' in testcase_params['mode']:
413 if testcase_params['channel'] <= 13:
414 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700415 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700416 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700417 ref_index = max(
418 0,
419 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
420 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700421 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700422 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700423 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700424
425 # Check if reference test has been run and set attenuation accordingly
426 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700427 self.extract_test_id(
428 result['testcase_params'],
429 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700430 for result in self.testclass_results
431 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700432
Omar El Ayach33f80c02018-09-27 15:02:03 -0700433 try:
434 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700435 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700436 'atten_at_range'] - (
437 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700438 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700439 self.log.warning(
440 'Reference test not found. Starting from {} dB'.format(
441 self.testclass_params['atten_start']))
442 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700443 start_atten = max(start_atten, 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700444 return start_atten
445
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700446 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700447 """Function that generates test params based on the test name."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700448 if testcase_params['chain_mask'] in ['0', '1']:
449 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
450 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700451 else:
Omar El Ayachdf470fb2019-09-16 12:22:28 -0700452 # Set attenuated chain to -1. Do not set to None as this will be
453 # compared to RF chain map which may include None
454 testcase_params['attenuated_chain'] = -1
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800455
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700456 self.testclass_params[
457 'range_ping_loss_threshold'] = 100 - self.testclass_params[
458 'throughput_pct_at_sensitivity']
459 if self.testclass_params['traffic_type'] == 'UDP':
460 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
461 self.testclass_params['iperf_duration'],
462 self.testclass_params['UDP_rates'][testcase_params['mode']])
463 elif self.testclass_params['traffic_type'] == 'TCP':
464 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
465 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800466
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700467 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700468 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700469 testcase_params['iperf_args'] += ' -R'
470 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800471 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700472 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800473
Omar El Ayach33f80c02018-09-27 15:02:03 -0700474 return testcase_params
475
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700476 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700477 """ Function that gets called for each test case
478
479 The function gets called in each rvr test case. The function customizes
480 the rvr test based on the test name of the test that called it
481 """
482 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700483 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700484 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700485 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800486 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700487 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
488 testcase_params['atten_step'])
489 testcase_params['atten_range'] = [
490 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800491 for x in range(0, num_atten_steps)
492 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700493
494 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700495 self.setup_sensitivity_test(testcase_params)
496 result = self.run_sensitivity_test(testcase_params)
497 self.process_sensitivity_test_results(testcase_params, result)
498
Omar El Ayach33f80c02018-09-27 15:02:03 -0700499 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800500 self.testclass_results.append(result)
501 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700502
Omar El Ayach96714c82019-01-28 18:51:46 -0800503 def generate_test_cases(self, channels, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700504 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700505 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700506 for channel in channels:
507 for mode in self.VALID_TEST_CONFIGS[channel]:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700508 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700509 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700510 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700511 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700512 elif 'legacy' in mode and channel < 14:
513 rates = self.VALID_RATES['legacy_2GHz']
514 elif 'legacy' in mode and channel > 14:
515 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700516 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700517 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800518 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700519 testcase_params = collections.OrderedDict(
520 channel=channel,
521 mode=mode,
522 rate=rate.mcs,
523 num_streams=rate.streams,
524 short_gi=1,
525 chain_mask=chain)
526 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800527 # Do not test 2-stream rates in single chain mode
528 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700529 if 'legacy' in mode:
530 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
531 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700532 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700533 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700534 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700535 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700536 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
537 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700538 channel, mode, rate.mcs,
539 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700540 setattr(self, testcase_name,
541 partial(self._test_sensitivity, testcase_params))
542 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700543 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700544
545
546class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
547 def __init__(self, controllers):
548 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700549 self.tests = self.generate_test_cases(
Omar El Ayach9873c082019-09-04 12:14:50 -0700550 [6, 36, 40, 44, 48, 149, 153, 157, 161], ['0', '1', '2x2'])
551
552
553class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
554 def __init__(self, controllers):
555 base_test.BaseTestClass.__init__(self, controllers)
556 self.tests = self.generate_test_cases([6, 36, 149], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700557
558
559class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
560 def __init__(self, controllers):
561 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700562 self.tests = self.generate_test_cases([1, 2, 6, 10, 11],
563 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700564
565
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800566class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
567 def __init__(self, controllers):
568 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700569 self.tests = self.generate_test_cases(
570 [36, 40, 44, 48, 149, 153, 157, 161], ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800571
572
Omar El Ayach33f80c02018-09-27 15:02:03 -0700573class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
574 def __init__(self, controllers):
575 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700576 self.tests = self.generate_test_cases([36, 40, 44, 48],
577 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700578
579
580class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
581 def __init__(self, controllers):
582 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700583 self.tests = self.generate_test_cases([149, 153, 157, 161],
584 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700585
586
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700587# Over-the air version of senstivity tests
588class WifiOtaSensitivityTest(WifiSensitivityTest):
589 """Class to test over-the-air senstivity.
590
591 This class implements measures WiFi sensitivity tests in an OTA chamber.
592 It allows setting orientation and other chamber parameters to study
593 performance in varying channel conditions
594 """
595
Omar El Ayach40099d02019-09-12 15:17:33 -0700596 def __init__(self, controllers):
597 WifiSensitivityTest.__init__(self, controllers)
598 self.bb_metric_logger = (
599 wputils.BlackboxMappedMetricLogger.for_test_class())
600
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700601 def setup_class(self):
602 WifiSensitivityTest.setup_class(self)
603 self.ota_chamber = ota_chamber.create(
604 self.user_params['OTAChamber'])[0]
605
606 def teardown_class(self):
607 WifiSensitivityTest.teardown_class(self)
608 self.ota_chamber.reset_chamber()
609
610 def setup_sensitivity_test(self, testcase_params):
611 # Setup turntable
612 self.ota_chamber.set_orientation(testcase_params['orientation'])
613 # Continue test setup
614 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
615
616 def process_testclass_results(self):
617 """Saves and plots test results from all executed test cases."""
618 testclass_results_dict = collections.OrderedDict()
619 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
620 plots = []
621 for result in self.testclass_results:
622 test_id = self.extract_test_id(result['testcase_params'],
623 id_fields)
624 test_id = tuple(test_id.items())
625 channel = result['testcase_params']['channel']
626 if test_id not in testclass_results_dict:
627 testclass_results_dict[test_id] = collections.OrderedDict()
628 if channel not in testclass_results_dict[test_id]:
629 testclass_results_dict[test_id][channel] = {
630 'orientation': [],
631 'sensitivity': []
632 }
633 testclass_results_dict[test_id][channel]['orientation'].append(
634 result['testcase_params']['orientation'])
635 if result['peak_throughput_pct'] == 100:
636 testclass_results_dict[test_id][channel]['sensitivity'].append(
637 result['sensitivity'])
638 else:
639 testclass_results_dict[test_id][channel]['sensitivity'].append(
640 float('nan'))
641
642 for test_id, test_data in testclass_results_dict.items():
643 test_id_dict = dict(test_id)
644 if 'legacy' in test_id_dict['mode']:
645 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
646 test_id_dict['mode'], test_id_dict['rate'],
647 test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700648 metric_test_config = '{}_{}_ch{}'.format(
649 test_id_dict['mode'], test_id_dict['rate'],
650 test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700651 else:
652 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
653 test_id_dict['mode'], test_id_dict['rate'],
654 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach49141c02019-09-16 16:43:51 -0700655 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(
656 test_id_dict['mode'], test_id_dict['rate'],
657 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700658 curr_plot = wputils.BokehFigure(
659 title=str(test_id_str),
660 x_label='Orientation (deg)',
661 primary_y='Sensitivity (dBm)')
662 for channel, channel_results in test_data.items():
663 curr_plot.add_line(
664 channel_results['orientation'],
665 channel_results['sensitivity'],
666 legend='Channel {}'.format(channel))
Omar El Ayach49141c02019-09-16 16:43:51 -0700667 metric_tag = 'ota_summary_ch{}_{}'.format(
668 channel, metric_test_config)
Omar El Ayach40099d02019-09-12 15:17:33 -0700669 metric_name = metric_tag + '.avg_sensitivity'
670 metric_value = sum(channel_results['sensitivity']) / len(
671 channel_results['sensitivity'])
672 self.bb_metric_logger.add_metric(metric_name, metric_value)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700673 current_context = (
674 context.get_current_context().get_full_output_path())
675 output_file_path = os.path.join(current_context,
676 str(test_id_str) + '.html')
677 curr_plot.generate_figure(output_file_path)
678 plots.append(curr_plot)
679 output_file_path = os.path.join(current_context, 'results.html')
680 wputils.BokehFigure.save_figures(plots, output_file_path)
681
682 def get_start_atten(self, testcase_params):
683 """Gets the starting attenuation for this sensitivity test.
684
685 The function gets the starting attenuation by checking whether a test
686 at the same rate configuration has executed. If so it sets the starting
687 point a configurable number of dBs below the reference test.
688
689 Returns:
690 start_atten: starting attenuation for current test
691 """
692 # Get the current and reference test config. The reference test is the
693 # one performed at the current MCS+1
694 ref_test_params = self.extract_test_id(
695 testcase_params,
696 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
697 # Check if reference test has been run and set attenuation accordingly
698 previous_params = [
699 self.extract_test_id(
700 result['testcase_params'],
701 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
702 for result in self.testclass_results
703 ]
704 try:
705 ref_index = previous_params[::-1].index(ref_test_params)
706 ref_index = len(previous_params) - 1 - ref_index
707 start_atten = self.testclass_results[ref_index][
708 'atten_at_range'] - (
709 self.testclass_params['adjacent_mcs_range_gap'])
710 except ValueError:
711 print('Reference test not found. Starting from {} dB'.format(
712 self.testclass_params['atten_start']))
713 start_atten = self.testclass_params['atten_start']
Omar El Ayach49141c02019-09-16 16:43:51 -0700714 start_atten = max(start_atten, 0)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700715 return start_atten
716
717 def generate_test_cases(self, channels, requested_rates, chain_mask,
718 angles):
719 """Function that auto-generates test cases for a test class."""
720 test_cases = []
721 for channel in channels:
722 for mode in self.VALID_TEST_CONFIGS[channel]:
723 if 'VHT' in mode:
724 valid_rates = self.VALID_RATES[mode]
725 elif 'HT' in mode:
726 valid_rates = self.VALID_RATES[mode]
727 elif 'legacy' in mode and channel < 14:
728 valid_rates = self.VALID_RATES['legacy_2GHz']
729 elif 'legacy' in mode and channel > 14:
730 valid_rates = self.VALID_RATES['legacy_5GHz']
731 else:
732 raise ValueError('Invalid test mode.')
733 for chain, rate, angle in itertools.product(
734 chain_mask, valid_rates, angles):
735 testcase_params = collections.OrderedDict(
736 channel=channel,
737 mode=mode,
738 rate=rate.mcs,
739 num_streams=rate.streams,
740 short_gi=1,
741 chain_mask=chain,
742 orientation=angle)
743 if rate not in requested_rates:
744 continue
745 if str(chain) in ['0', '1'] and rate[1] == 2:
746 # Do not test 2-stream rates in single chain mode
747 continue
748 if 'legacy' in mode:
749 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
750 '_ch{}_{}deg'.format(
751 channel, mode,
752 str(rate.mcs).replace('.', 'p'),
753 rate.streams, chain, angle))
754 else:
755 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
756 '_ch{}_{}deg'.format(
757 channel, mode, rate.mcs,
758 rate.streams, chain, angle))
759 setattr(self, testcase_name,
760 partial(self._test_sensitivity, testcase_params))
761 test_cases.append(testcase_name)
Omar El Ayachf2f99c32019-09-16 17:18:45 -0700762 return test_cases
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700763
764
765class WifiOtaSensitivity_10Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700766 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700767 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700768 requested_channels = [6, 36, 149]
769 requested_rates = [
770 self.RateTuple(8, 1, 86.7),
771 self.RateTuple(0, 1, 7.2),
772 self.RateTuple(8, 2, 173.3),
773 self.RateTuple(0, 2, 14.4)
774 ]
775 self.tests = self.generate_test_cases(requested_channels,
776 requested_rates, ['2x2'],
777 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700778
779
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700780class WifiOtaSensitivity_SingleChain_10Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700781 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700782 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700783 requested_channels = [6, 36, 149]
784 requested_rates = [
785 self.RateTuple(8, 1, 86.7),
786 self.RateTuple(0, 1, 7.2),
787 ]
788 self.tests = self.generate_test_cases(requested_channels,
789 requested_rates, ['2x2'],
790 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700791
792
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700793class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700794 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700795 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700796 requested_rates = [
797 self.RateTuple(8, 1, 86.7),
798 self.RateTuple(0, 1, 7.2),
799 self.RateTuple(8, 2, 173.3),
800 self.RateTuple(0, 2, 14.4)
801 ]
802 self.tests = self.generate_test_cases(
803 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], requested_rates,
804 ['2x2'], list(range(0, 360, 45)))