blob: a92cf21030eed6a80403552523b5abcafeb2c59c [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)
374 self.dut.droid.wifiSetCountryCode(
375 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 Ayach6e518a22019-06-13 13:55:42 -0700385 for idx, atten in enumerate(self.attenuators):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700386 if atten_dut_chain_map[idx] == testcase_params['attenuated_chain']:
Omar El Ayach6e518a22019-06-13 13:55:42 -0700387 atten.offset = atten.instrument.max_atten
388
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700389 def extract_test_id(self, testcase_params, id_fields):
390 test_id = collections.OrderedDict(
391 (param, testcase_params[param]) for param in id_fields)
392 return test_id
393
394 def get_start_atten(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700395 """Gets the starting attenuation for this sensitivity test.
396
397 The function gets the starting attenuation by checking whether a test
398 as the next higher MCS has been executed. If so it sets the starting
399 point a configurable number of dBs below the next MCS's sensitivity.
400
401 Returns:
402 start_atten: starting attenuation for current test
403 """
404 # Get the current and reference test config. The reference test is the
405 # one performed at the current MCS+1
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700406 current_rate = testcase_params['rate']
407 ref_test_params = self.extract_test_id(
408 testcase_params,
409 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
410 if 'legacy' in testcase_params['mode']:
411 if testcase_params['channel'] <= 13:
412 rate_list = self.VALID_RATES['legacy_2GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700413 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700414 rate_list = self.VALID_RATES['legacy_5GHz']
Omar El Ayach03e40612019-05-01 16:25:39 -0700415 ref_index = max(
416 0,
417 rate_list.index(self.RateTuple(current_rate, 1, current_rate))
418 - 1)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700419 ref_test_params['rate'] = rate_list[ref_index].mcs
Omar El Ayach33f80c02018-09-27 15:02:03 -0700420 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700421 ref_test_params['rate'] = current_rate + 1
Omar El Ayach33f80c02018-09-27 15:02:03 -0700422
423 # Check if reference test has been run and set attenuation accordingly
424 previous_params = [
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700425 self.extract_test_id(
426 result['testcase_params'],
427 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700428 for result in self.testclass_results
429 ]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700430
Omar El Ayach33f80c02018-09-27 15:02:03 -0700431 try:
432 ref_index = previous_params.index(ref_test_params)
Omar El Ayacha210d572019-03-14 17:31:38 -0700433 start_atten = self.testclass_results[ref_index][
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700434 'atten_at_range'] - (
435 self.testclass_params['adjacent_mcs_range_gap'])
Omar El Ayach03e40612019-05-01 16:25:39 -0700436 except ValueError:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700437 self.log.warning(
438 'Reference test not found. Starting from {} dB'.format(
439 self.testclass_params['atten_start']))
440 start_atten = self.testclass_params['atten_start']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700441 return start_atten
442
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700443 def compile_test_params(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700444 """Function that generates test params based on the test name."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700445 if testcase_params['chain_mask'] in ['0', '1']:
446 testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
447 1 if testcase_params['chain_mask'] == '0' else 0)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700448 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700449 testcase_params['attenuated_chain'] = None
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800450
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700451 self.testclass_params[
452 'range_ping_loss_threshold'] = 100 - self.testclass_params[
453 'throughput_pct_at_sensitivity']
454 if self.testclass_params['traffic_type'] == 'UDP':
455 testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
456 self.testclass_params['iperf_duration'],
457 self.testclass_params['UDP_rates'][testcase_params['mode']])
458 elif self.testclass_params['traffic_type'] == 'TCP':
459 testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
460 self.testclass_params['iperf_duration'])
Omar El Ayach14416ac2019-01-30 14:58:19 -0800461
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700462 if self.testclass_params['traffic_type'] != 'ping' and isinstance(
Omar El Ayacha210d572019-03-14 17:31:38 -0700463 self.iperf_client, iperf_client.IPerfClientOverAdb):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700464 testcase_params['iperf_args'] += ' -R'
465 testcase_params['use_client_output'] = True
Omar El Ayach14416ac2019-01-30 14:58:19 -0800466 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700467 testcase_params['use_client_output'] = False
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800468
Omar El Ayach33f80c02018-09-27 15:02:03 -0700469 return testcase_params
470
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700471 def _test_sensitivity(self, testcase_params):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700472 """ Function that gets called for each test case
473
474 The function gets called in each rvr test case. The function customizes
475 the rvr test based on the test name of the test that called it
476 """
477 # Compile test parameters from config and test name
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700478 testcase_params = self.compile_test_params(testcase_params)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700479 testcase_params.update(self.testclass_params)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700480 testcase_params['atten_start'] = self.get_start_atten(testcase_params)
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800481 num_atten_steps = int(
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700482 (testcase_params['atten_stop'] - testcase_params['atten_start']) /
483 testcase_params['atten_step'])
484 testcase_params['atten_range'] = [
485 testcase_params['atten_start'] + x * testcase_params['atten_step']
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800486 for x in range(0, num_atten_steps)
487 ]
Omar El Ayach33f80c02018-09-27 15:02:03 -0700488
489 # Prepare devices and run test
Omar El Ayach03e40612019-05-01 16:25:39 -0700490 self.setup_sensitivity_test(testcase_params)
491 result = self.run_sensitivity_test(testcase_params)
492 self.process_sensitivity_test_results(testcase_params, result)
493
Omar El Ayach33f80c02018-09-27 15:02:03 -0700494 # Post-process results
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800495 self.testclass_results.append(result)
496 self.pass_fail_check(result)
Omar El Ayach33f80c02018-09-27 15:02:03 -0700497
Omar El Ayach96714c82019-01-28 18:51:46 -0800498 def generate_test_cases(self, channels, chain_mask):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700499 """Function that auto-generates test cases for a test class."""
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700500 test_cases = []
Omar El Ayach33f80c02018-09-27 15:02:03 -0700501 for channel in channels:
502 for mode in self.VALID_TEST_CONFIGS[channel]:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700503 if 'VHT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700504 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700505 elif 'HT' in mode:
Omar El Ayacha210d572019-03-14 17:31:38 -0700506 rates = self.VALID_RATES[mode]
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700507 elif 'legacy' in mode and channel < 14:
508 rates = self.VALID_RATES['legacy_2GHz']
509 elif 'legacy' in mode and channel > 14:
510 rates = self.VALID_RATES['legacy_5GHz']
Omar El Ayach33f80c02018-09-27 15:02:03 -0700511 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700512 raise ValueError('Invalid test mode.')
Omar El Ayach96714c82019-01-28 18:51:46 -0800513 for chain, rate in itertools.product(chain_mask, rates):
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700514 testcase_params = collections.OrderedDict(
515 channel=channel,
516 mode=mode,
517 rate=rate.mcs,
518 num_streams=rate.streams,
519 short_gi=1,
520 chain_mask=chain)
521 if chain in ['0', '1'] and rate[1] == 2:
Omar El Ayach96714c82019-01-28 18:51:46 -0800522 # Do not test 2-stream rates in single chain mode
523 continue
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700524 if 'legacy' in mode:
525 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
526 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700527 channel, mode,
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700528 str(rate.mcs).replace('.', 'p'),
Omar El Ayach03e40612019-05-01 16:25:39 -0700529 rate.streams, chain))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700530 else:
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700531 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
532 '_ch{}'.format(
Omar El Ayach03e40612019-05-01 16:25:39 -0700533 channel, mode, rate.mcs,
534 rate.streams, chain))
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700535 setattr(self, testcase_name,
536 partial(self._test_sensitivity, testcase_params))
537 test_cases.append(testcase_name)
Omar El Ayachab047c02019-09-03 11:38:30 -0700538 return test_cases
Omar El Ayach33f80c02018-09-27 15:02:03 -0700539
540
541class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
542 def __init__(self, controllers):
543 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700544 self.tests = self.generate_test_cases(
Omar El Ayach9873c082019-09-04 12:14:50 -0700545 [6, 36, 40, 44, 48, 149, 153, 157, 161], ['0', '1', '2x2'])
546
547
548class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
549 def __init__(self, controllers):
550 base_test.BaseTestClass.__init__(self, controllers)
551 self.tests = self.generate_test_cases([6, 36, 149], ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700552
553
554class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
555 def __init__(self, controllers):
556 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700557 self.tests = self.generate_test_cases([1, 2, 6, 10, 11],
558 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700559
560
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800561class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
562 def __init__(self, controllers):
563 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700564 self.tests = self.generate_test_cases(
565 [36, 40, 44, 48, 149, 153, 157, 161], ['0', '1', '2x2'])
Omar El Ayach5fbc1222018-12-07 18:10:05 -0800566
567
Omar El Ayach33f80c02018-09-27 15:02:03 -0700568class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
569 def __init__(self, controllers):
570 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700571 self.tests = self.generate_test_cases([36, 40, 44, 48],
572 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700573
574
575class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
576 def __init__(self, controllers):
577 base_test.BaseTestClass.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700578 self.tests = self.generate_test_cases([149, 153, 157, 161],
579 ['0', '1', '2x2'])
Omar El Ayach33f80c02018-09-27 15:02:03 -0700580
581
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700582# Over-the air version of senstivity tests
583class WifiOtaSensitivityTest(WifiSensitivityTest):
584 """Class to test over-the-air senstivity.
585
586 This class implements measures WiFi sensitivity tests in an OTA chamber.
587 It allows setting orientation and other chamber parameters to study
588 performance in varying channel conditions
589 """
590
Omar El Ayach40099d02019-09-12 15:17:33 -0700591 def __init__(self, controllers):
592 WifiSensitivityTest.__init__(self, controllers)
593 self.bb_metric_logger = (
594 wputils.BlackboxMappedMetricLogger.for_test_class())
595
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700596 def setup_class(self):
597 WifiSensitivityTest.setup_class(self)
598 self.ota_chamber = ota_chamber.create(
599 self.user_params['OTAChamber'])[0]
600
601 def teardown_class(self):
602 WifiSensitivityTest.teardown_class(self)
603 self.ota_chamber.reset_chamber()
604
605 def setup_sensitivity_test(self, testcase_params):
606 # Setup turntable
607 self.ota_chamber.set_orientation(testcase_params['orientation'])
608 # Continue test setup
609 WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
610
611 def process_testclass_results(self):
612 """Saves and plots test results from all executed test cases."""
613 testclass_results_dict = collections.OrderedDict()
614 id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
615 plots = []
616 for result in self.testclass_results:
617 test_id = self.extract_test_id(result['testcase_params'],
618 id_fields)
619 test_id = tuple(test_id.items())
620 channel = result['testcase_params']['channel']
621 if test_id not in testclass_results_dict:
622 testclass_results_dict[test_id] = collections.OrderedDict()
623 if channel not in testclass_results_dict[test_id]:
624 testclass_results_dict[test_id][channel] = {
625 'orientation': [],
626 'sensitivity': []
627 }
628 testclass_results_dict[test_id][channel]['orientation'].append(
629 result['testcase_params']['orientation'])
630 if result['peak_throughput_pct'] == 100:
631 testclass_results_dict[test_id][channel]['sensitivity'].append(
632 result['sensitivity'])
633 else:
634 testclass_results_dict[test_id][channel]['sensitivity'].append(
635 float('nan'))
636
637 for test_id, test_data in testclass_results_dict.items():
638 test_id_dict = dict(test_id)
639 if 'legacy' in test_id_dict['mode']:
640 test_id_str = '{} {}Mbps, Chain Mask = {}'.format(
641 test_id_dict['mode'], test_id_dict['rate'],
642 test_id_dict['chain_mask'])
Omar El Ayach40099d02019-09-12 15:17:33 -0700643 metric_test_config = '{}_{}_ch{}'.format(test_id_dict['mode'], test_id_dict['rate'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700644 else:
645 test_id_str = '{} MCS{} Nss{}, Chain Mask = {}'.format(
646 test_id_dict['mode'], test_id_dict['rate'],
647 test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayach40099d02019-09-12 15:17:33 -0700648 metric_test_config = '{}_mcs{}_nss{}_ch{}'.format(test_id_dict['mode'], test_id_dict['rate'], test_id_dict['num_streams'], test_id_dict['chain_mask'])
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700649 curr_plot = wputils.BokehFigure(
650 title=str(test_id_str),
651 x_label='Orientation (deg)',
652 primary_y='Sensitivity (dBm)')
653 for channel, channel_results in test_data.items():
654 curr_plot.add_line(
655 channel_results['orientation'],
656 channel_results['sensitivity'],
657 legend='Channel {}'.format(channel))
Omar El Ayach40099d02019-09-12 15:17:33 -0700658 metric_tag = 'ota_summary_ch{}_{}'.format(channel, metric_test_config)
659 metric_name = metric_tag + '.avg_sensitivity'
660 metric_value = sum(channel_results['sensitivity']) / len(
661 channel_results['sensitivity'])
662 self.bb_metric_logger.add_metric(metric_name, metric_value)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700663 current_context = (
664 context.get_current_context().get_full_output_path())
665 output_file_path = os.path.join(current_context,
666 str(test_id_str) + '.html')
667 curr_plot.generate_figure(output_file_path)
668 plots.append(curr_plot)
669 output_file_path = os.path.join(current_context, 'results.html')
670 wputils.BokehFigure.save_figures(plots, output_file_path)
671
672 def get_start_atten(self, testcase_params):
673 """Gets the starting attenuation for this sensitivity test.
674
675 The function gets the starting attenuation by checking whether a test
676 at the same rate configuration has executed. If so it sets the starting
677 point a configurable number of dBs below the reference test.
678
679 Returns:
680 start_atten: starting attenuation for current test
681 """
682 # Get the current and reference test config. The reference test is the
683 # one performed at the current MCS+1
684 ref_test_params = self.extract_test_id(
685 testcase_params,
686 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
687 # Check if reference test has been run and set attenuation accordingly
688 previous_params = [
689 self.extract_test_id(
690 result['testcase_params'],
691 ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
692 for result in self.testclass_results
693 ]
694 try:
695 ref_index = previous_params[::-1].index(ref_test_params)
696 ref_index = len(previous_params) - 1 - ref_index
697 start_atten = self.testclass_results[ref_index][
698 'atten_at_range'] - (
699 self.testclass_params['adjacent_mcs_range_gap'])
700 except ValueError:
701 print('Reference test not found. Starting from {} dB'.format(
702 self.testclass_params['atten_start']))
703 start_atten = self.testclass_params['atten_start']
704 return start_atten
705
706 def generate_test_cases(self, channels, requested_rates, chain_mask,
707 angles):
708 """Function that auto-generates test cases for a test class."""
709 test_cases = []
710 for channel in channels:
711 for mode in self.VALID_TEST_CONFIGS[channel]:
712 if 'VHT' in mode:
713 valid_rates = self.VALID_RATES[mode]
714 elif 'HT' in mode:
715 valid_rates = self.VALID_RATES[mode]
716 elif 'legacy' in mode and channel < 14:
717 valid_rates = self.VALID_RATES['legacy_2GHz']
718 elif 'legacy' in mode and channel > 14:
719 valid_rates = self.VALID_RATES['legacy_5GHz']
720 else:
721 raise ValueError('Invalid test mode.')
722 for chain, rate, angle in itertools.product(
723 chain_mask, valid_rates, angles):
724 testcase_params = collections.OrderedDict(
725 channel=channel,
726 mode=mode,
727 rate=rate.mcs,
728 num_streams=rate.streams,
729 short_gi=1,
730 chain_mask=chain,
731 orientation=angle)
732 if rate not in requested_rates:
733 continue
734 if str(chain) in ['0', '1'] and rate[1] == 2:
735 # Do not test 2-stream rates in single chain mode
736 continue
737 if 'legacy' in mode:
738 testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
739 '_ch{}_{}deg'.format(
740 channel, mode,
741 str(rate.mcs).replace('.', 'p'),
742 rate.streams, chain, angle))
743 else:
744 testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
745 '_ch{}_{}deg'.format(
746 channel, mode, rate.mcs,
747 rate.streams, chain, angle))
748 setattr(self, testcase_name,
749 partial(self._test_sensitivity, testcase_params))
750 test_cases.append(testcase_name)
751
752
753class WifiOtaSensitivity_10Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700754 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700755 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700756 requested_channels = [6, 36, 149]
757 requested_rates = [
758 self.RateTuple(8, 1, 86.7),
759 self.RateTuple(0, 1, 7.2),
760 self.RateTuple(8, 2, 173.3),
761 self.RateTuple(0, 2, 14.4)
762 ]
763 self.tests = self.generate_test_cases(requested_channels,
764 requested_rates, ['2x2'],
765 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700766
767
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700768class WifiOtaSensitivity_SingleChain_10Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700769 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700770 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700771 requested_channels = [6, 36, 149]
772 requested_rates = [
773 self.RateTuple(8, 1, 86.7),
774 self.RateTuple(0, 1, 7.2),
775 ]
776 self.tests = self.generate_test_cases(requested_channels,
777 requested_rates, ['2x2'],
778 list(range(0, 360, 10)))
Omar El Ayach33f80c02018-09-27 15:02:03 -0700779
780
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700781class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
Omar El Ayach33f80c02018-09-27 15:02:03 -0700782 def __init__(self, controllers):
Omar El Ayach40099d02019-09-12 15:17:33 -0700783 WifiOtaSensitivityTest.__init__(self, controllers)
Omar El Ayachbbc6a0a2019-07-22 10:26:11 -0700784 requested_rates = [
785 self.RateTuple(8, 1, 86.7),
786 self.RateTuple(0, 1, 7.2),
787 self.RateTuple(8, 2, 173.3),
788 self.RateTuple(0, 2, 14.4)
789 ]
790 self.tests = self.generate_test_cases(
791 [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], requested_rates,
792 ['2x2'], list(range(0, 360, 45)))