Hayden Nix | 3f2eebf | 2020-01-14 10:39:21 -0800 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright 2020 - The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # 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 |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # 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 | |
| 17 | import itertools |
| 18 | |
| 19 | from acts import utils |
| 20 | from acts.controllers.ap_lib import hostapd_constants |
| 21 | from acts.controllers.ap_lib import hostapd_config |
| 22 | from acts.test_utils.abstract_devices.wlan_device import create_wlan_device |
| 23 | from acts.test_utils.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate |
| 24 | from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest |
| 25 | from acts.utils import rand_ascii_str |
| 26 | |
| 27 | # 12, 13 outside the US |
| 28 | # 14 in Japan, DSSS and CCK only |
| 29 | CHANNELS_24 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] |
| 30 | |
| 31 | # 32, 34 for 20 and 40 mhz in Europe |
| 32 | # 34, 42, 46 have mixed international support |
| 33 | # 144 is supported by some chips |
| 34 | CHANNELS_5 = [ |
| 35 | 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, |
| 36 | 132, 136, 140, 144, 149, 153, 157, 161, 165 |
| 37 | ] |
| 38 | |
| 39 | BANDWIDTH_24 = [20, 40] |
| 40 | BANDWIDTH_5 = [20, 40, 80] |
| 41 | |
| 42 | N_CAPABILITIES_DEFAULT = [ |
| 43 | hostapd_constants.N_CAPABILITY_LDPC, hostapd_constants.N_CAPABILITY_SGI20, |
| 44 | hostapd_constants.N_CAPABILITY_SGI40, |
| 45 | hostapd_constants.N_CAPABILITY_TX_STBC, |
| 46 | hostapd_constants.N_CAPABILITY_RX_STBC1 |
| 47 | ] |
| 48 | |
| 49 | AC_CAPABILITIES_DEFAULT = [ |
| 50 | hostapd_constants.AC_CAPABILITY_MAX_MPDU_11454, |
| 51 | hostapd_constants.AC_CAPABILITY_RXLDPC, |
| 52 | hostapd_constants.AC_CAPABILITY_SHORT_GI_80, |
| 53 | hostapd_constants.AC_CAPABILITY_TX_STBC_2BY1, |
| 54 | hostapd_constants.AC_CAPABILITY_RX_STBC_1, |
| 55 | hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7, |
| 56 | hostapd_constants.AC_CAPABILITY_RX_ANTENNA_PATTERN, |
| 57 | hostapd_constants.AC_CAPABILITY_TX_ANTENNA_PATTERN |
| 58 | ] |
| 59 | |
| 60 | |
| 61 | def generate_test_name(settings): |
| 62 | """Generates a string test name based on the channel and band |
| 63 | |
| 64 | Args: |
| 65 | settings: A dict with 'channel' and 'bandwidth' keys. |
| 66 | |
| 67 | Returns: |
| 68 | A string that represents a test case name. |
| 69 | """ |
| 70 | return 'test_channel_%s_bandwidth_%smhz' % (settings['channel'], |
| 71 | settings['bandwidth']) |
| 72 | |
| 73 | |
| 74 | class ChannelSweepTest(WifiBaseTest): |
| 75 | """Tests for associating with all 2.5 and 5 channels and bands. |
| 76 | |
| 77 | Testbed Requirement: |
| 78 | * One ACTS compatible device (dut) |
| 79 | * One Access Point |
| 80 | """ |
| 81 | def __init__(self, controllers): |
| 82 | WifiBaseTest.__init__(self, controllers) |
| 83 | self.tests = ['test_24ghz_channels', 'test_5ghz_channels'] |
| 84 | if 'debug_channel_sweep_tests' in self.user_params: |
| 85 | self.tests.append('test_channel_sweep_debug') |
| 86 | |
| 87 | def setup_class(self): |
| 88 | super().setup_class() |
| 89 | if 'dut' in self.user_params: |
| 90 | if self.user_params['dut'] == 'fuchsia_devices': |
| 91 | self.dut = create_wlan_device(self.fuchsia_devices[0]) |
| 92 | elif self.user_params['dut'] == 'android_devices': |
| 93 | self.dut = create_wlan_device(self.android_devices[0]) |
| 94 | else: |
| 95 | raise ValueError('Invalid DUT specified in config. (%s)' % |
| 96 | self.user_params['dut']) |
| 97 | else: |
| 98 | # Default is an android device, just like the other tests |
| 99 | self.dut = create_wlan_device(self.android_devices[0]) |
| 100 | |
| 101 | self.android_devices = getattr(self, 'android_devices', []) |
| 102 | self.access_point = self.access_points[0] |
| 103 | self.access_point.stop_all_aps() |
| 104 | |
| 105 | def setup_test(self): |
| 106 | for ad in self.android_devices: |
| 107 | ad.droid.wakeLockAcquireBright() |
| 108 | ad.droid.wakeUpNow() |
| 109 | self.dut.wifi_toggle_state(True) |
| 110 | |
| 111 | def teardown_test(self): |
| 112 | for ad in self.android_devices: |
| 113 | ad.droid.wakeLockRelease() |
| 114 | ad.droid.goToSleepNow() |
| 115 | self.dut.turn_location_off_and_scan_toggle_off() |
| 116 | self.dut.disconnect() |
| 117 | self.dut.reset_wifi() |
| 118 | self.access_point.stop_all_aps() |
| 119 | |
| 120 | def on_fail(self, test_name, begin_time): |
| 121 | self.dut.take_bug_report(test_name, begin_time) |
| 122 | self.dut.get_log(test_name, begin_time) |
| 123 | |
| 124 | def setup_and_connect(self, settings): |
| 125 | """Generates a hostapd config based on the provided channel and |
| 126 | bandwidth, starts AP with that config, and attempts to associate the |
| 127 | dut. |
| 128 | |
| 129 | Args: |
| 130 | settings: A dict with 'channel' and 'bandwidth' keys. |
| 131 | """ |
| 132 | channel = settings['channel'] |
| 133 | bandwidth = settings['bandwidth'] |
| 134 | if channel > 14: |
| 135 | vht_bandwidth = bandwidth |
| 136 | else: |
| 137 | vht_bandwidth = None |
| 138 | |
| 139 | if bandwidth == 20: |
| 140 | n_capabilities = N_CAPABILITIES_DEFAULT + [ |
| 141 | hostapd_constants.N_CAPABILITY_HT20 |
| 142 | ] |
| 143 | elif bandwidth == 40 or bandwidth == 80: |
| 144 | if hostapd_config.ht40_plus_allowed(channel): |
| 145 | extended_channel = [hostapd_constants.N_CAPABILITY_HT40_PLUS] |
| 146 | elif hostapd_config.ht40_minus_allowed(channel): |
| 147 | extended_channel = [hostapd_constants.N_CAPABILITY_HT40_MINUS] |
| 148 | else: |
| 149 | raise ValueError('Invalid Channel: %s' % channel) |
| 150 | n_capabilities = N_CAPABILITIES_DEFAULT + extended_channel |
| 151 | else: |
| 152 | raise ValueError('Invalid Bandwidth: %s' % bandwidth) |
| 153 | |
| 154 | validate_setup_ap_and_associate(access_point=self.access_point, |
| 155 | client=self.dut, |
| 156 | profile_name='whirlwind', |
| 157 | channel=channel, |
| 158 | n_capabilities=n_capabilities, |
| 159 | ac_capabilities=None, |
| 160 | force_wmm=True, |
| 161 | ssid=utils.rand_ascii_str(20), |
| 162 | vht_bandwidth=vht_bandwidth) |
| 163 | |
| 164 | def create_test_settings(self, channels, bandwidths): |
| 165 | """Creates a list of test configurations to run from the product of the |
| 166 | given channels list and bandwidths list. |
| 167 | |
| 168 | Args: |
| 169 | channels: A list of ints representing the channels to test. |
| 170 | bandwidths: A list of ints representing the bandwidths to test on |
| 171 | those channels. |
| 172 | |
| 173 | Returns: |
| 174 | A list of dictionaries containing 'channel' and 'bandwidth' keys, |
| 175 | one for each test combination to be run. |
| 176 | """ |
| 177 | test_list = [] |
| 178 | for combination in itertools.product(channels, bandwidths): |
| 179 | test_settings = { |
| 180 | 'channel': combination[0], |
| 181 | 'bandwidth': combination[1] |
| 182 | } |
| 183 | test_list.append(test_settings) |
| 184 | return test_list |
| 185 | |
| 186 | def test_24ghz_channels(self): |
| 187 | """Runs setup_and_connect for 2.4GHz channels on 20 and 40 MHz bands.""" |
| 188 | test_list = self.create_test_settings(CHANNELS_24, BANDWIDTH_24) |
| 189 | self.run_generated_testcases(self.setup_and_connect, |
| 190 | settings=test_list, |
| 191 | name_func=generate_test_name) |
| 192 | |
| 193 | def test_5ghz_channels(self): |
| 194 | """Runs setup_and_connect for 5GHz channels on 20, 40, and 80 MHz bands. |
| 195 | """ |
| 196 | test_list = self.create_test_settings(CHANNELS_5, BANDWIDTH_5) |
| 197 | # Channel 165 is 20mhz only |
| 198 | test_list.remove({'channel': 165, 'bandwidth': 40}) |
| 199 | test_list.remove({'channel': 165, 'bandwidth': 80}) |
| 200 | self.run_generated_testcases(self.setup_and_connect, |
| 201 | settings=test_list, |
| 202 | name_func=generate_test_name) |
| 203 | |
| 204 | def test_channel_sweep_debug(self): |
| 205 | """Runs test cases defined in the ACTS config file for debugging |
| 206 | purposes. |
| 207 | |
| 208 | Usage: |
| 209 | 1. Add 'debug_channel_sweep_tests' to ACTS config with list of |
| 210 | tests to run matching pattern: |
| 211 | test_channel_<CHANNEL>_bandwidth_<BANDWIDTH> |
| 212 | 2. Run test_channel_sweep_debug test. |
| 213 | """ |
| 214 | allowed_channels = CHANNELS_24 + CHANNELS_5 |
| 215 | chan_band_pattern = re.compile( |
| 216 | r'.*channel_([0-9]*)_.*bandwidth_([0-9]*)') |
| 217 | test_list = [] |
| 218 | for test_title in self.user_params['debug_channel_sweep_tests']: |
| 219 | test = re.match(chan_band_pattern, test_title) |
| 220 | if test: |
| 221 | channel = int(test.group(1)) |
| 222 | bandwidth = int(test.group(2)) |
| 223 | if channel not in allowed_channels: |
| 224 | raise ValueError("Invalid channel: %s" % channel) |
| 225 | if channel <= 14 and bandwidth not in BANDWIDTH_24: |
| 226 | raise ValueError( |
| 227 | "Channel %s does not support bandwidth %s" % |
| 228 | (channel, bandwidth)) |
| 229 | test_settings = {'channel': channel, 'bandwidth': bandwidth} |
| 230 | test_list.append(test_settings) |
| 231 | |
| 232 | self.run_generated_testcases(self.setup_and_connect, |
| 233 | settings=test_list, |
| 234 | name_func=generate_test_name) |