1# Lint as: python2, python3 2# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import logging 7import time 8 9from autotest_lib.client.bin import test 10from autotest_lib.client.common_lib import error 11from autotest_lib.client.cros.cellular import mm1_constants 12from autotest_lib.client.cros.cellular import test_environment 13from autotest_lib.client.cros.cellular.pseudomodem import modem_3gpp 14from autotest_lib.client.cros.cellular.pseudomodem import modem_cdma 15from autotest_lib.client.cros.cellular.pseudomodem import pm_errors 16from autotest_lib.client.cros.cellular.pseudomodem import utils as pm_utils 17 18# Use our own connect/disconnect timeout for this test because we are using a 19# a pseudomodem which should run faster than a real modem. 20CONNECT_DISCONNECT_TIMEOUT = 10 21 22 23def _GetModemSuperClass(family): 24 """ 25 Obtains the correct Modem base class to use for the given family. 26 27 @param family: The modem family. Should be one of |3GPP|/|CDMA|. 28 @returns: The relevant Modem base class. 29 @raises error.TestError, if |family| is not one of '3GPP' or 'CDMA'. 30 31 """ 32 if family == '3GPP': 33 return modem_3gpp.Modem3gpp 34 elif family == 'CDMA': 35 return modem_cdma.ModemCdma 36 else: 37 raise error.TestError('Invalid pseudomodem family: %s', family) 38 39 40def GetModemDisconnectWhileStateIsDisconnecting(family): 41 """ 42 Returns a modem that fails on disconnect request. 43 44 @param family: The family of the modem returned. 45 @returns: A modem of the given family that fails disconnect. 46 47 """ 48 modem_class = _GetModemSuperClass(family) 49 class _TestModem(modem_class): 50 """ Actual modem implementation. """ 51 @pm_utils.log_dbus_method(return_cb_arg='return_cb', 52 raise_cb_arg='raise_cb') 53 def Disconnect( 54 self, bearer_path, return_cb, raise_cb, *return_cb_args): 55 """ 56 Test implementation of 57 org.freedesktop.ModemManager1.Modem.Simple.Disconnect. Sets the 58 modem state to DISCONNECTING and then fails, fooling shill into 59 thinking that the disconnect failed while disconnecting. 60 61 Refer to modem_simple.ModemSimple.Connect for documentation. 62 63 """ 64 logging.info('Simulating failed Disconnect') 65 self.ChangeState(mm1_constants.MM_MODEM_STATE_DISCONNECTING, 66 mm1_constants.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN) 67 time.sleep(5) 68 raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED) 69 70 return _TestModem() 71 72 73def GetModemDisconnectWhileDisconnectInProgress(family): 74 """ 75 Returns a modem implementation that fails disconnect except the first one. 76 77 @param family: The family of the returned modem. 78 @returns: A modem of the given family that fails all but the first 79 disconnect attempts. 80 81 """ 82 modem_class = _GetModemSuperClass(family) 83 class _TestModem(modem_class): 84 """ The actual modem implementation. """ 85 def __init__(self): 86 modem_class.__init__(self) 87 self.disconnect_count = 0 88 89 @pm_utils.log_dbus_method(return_cb_arg='return_cb', 90 raise_cb_arg='raise_cb') 91 def Disconnect( 92 self, bearer_path, return_cb, raise_cb, *return_cb_args): 93 """ 94 Test implementation of 95 org.freedesktop.ModemManager1.Modem.Simple.Disconnect. Keeps 96 count of successive disconnect operations and fails during all 97 but the first one. 98 99 Refer to modem_simple.ModemSimple.Connect for documentation. 100 101 """ 102 # On the first call, set the state to DISCONNECTING. 103 self.disconnect_count += 1 104 if self.disconnect_count == 1: 105 self.ChangeState( 106 mm1_constants.MM_MODEM_STATE_DISCONNECTING, 107 mm1_constants.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN) 108 time.sleep(5) 109 else: 110 raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED) 111 112 return _TestModem() 113 114 115def GetModemDisconnectFailOther(family): 116 """ 117 Returns a modem that fails a disconnect attempt with a generic error. 118 119 @param family: The family of the modem returned. 120 @returns: A modem of the give family that fails disconnect. 121 122 """ 123 modem_class = _GetModemSuperClass(family) 124 class _TestModem(modem_class): 125 """ The actual modem implementation. """ 126 @pm_utils.log_dbus_method(return_cb_arg='return_cb', 127 raise_cb_arg='raise_cb') 128 def Disconnect( 129 self, bearer_path, return_cb, raise_cb, *return_cb_args): 130 """ 131 Test implementation of 132 org.freedesktop.ModemManager1.Modem.Simple.Disconnect. 133 Fails with an error. 134 135 Refer to modem_simple.ModemSimple.Connect for documentation. 136 137 """ 138 raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED) 139 140 return _TestModem() 141 142 143class DisconnectFailTest(object): 144 """ 145 DisconnectFailTest implements common functionality in all test cases. 146 147 """ 148 def __init__(self, test, pseudomodem_family): 149 self.test = test 150 self._pseudomodem_family = pseudomodem_family 151 152 153 def IsServiceConnected(self): 154 """ 155 @return True, if service is connected. 156 157 """ 158 service = self.test_env.shill.find_cellular_service_object() 159 properties = service.GetProperties() 160 state = properties.get('State', None) 161 return state in ['portal', 'online'] 162 163 164 def IsServiceDisconnected(self): 165 """ 166 @return True, if service is disconnected. 167 168 """ 169 service = self.test_env.shill.find_cellular_service_object() 170 properties = service.GetProperties() 171 state = properties.get('State', None) 172 return state == 'idle' 173 174 175 def Run(self): 176 """ 177 Runs the test. 178 179 @raises test.TestFail, if |test_modem| hasn't been initialized. 180 181 """ 182 self.test_env = test_environment.CellularPseudoMMTestEnvironment( 183 pseudomm_args=( 184 {'test-module' : __file__, 185 'test-modem-class' : self._GetTestModemFunctorName(), 186 'test-modem-arg' : [self._pseudomodem_family]},)) 187 with self.test_env: 188 self._RunTest() 189 190 191 def _GetTestModemFunctorName(self): 192 """ Returns the modem to be used by the pseudomodem for this test. """ 193 raise NotImplementedError() 194 195 196 def _RunTest(self): 197 raise NotImplementedError() 198 199 200class DisconnectWhileStateIsDisconnectingTest(DisconnectFailTest): 201 """ 202 Simulates a disconnect failure while the modem is still disconnecting. 203 Fails if the service doesn't remain connected. 204 205 """ 206 def _GetTestModemFunctorName(self): 207 return 'GetModemDisconnectWhileStateIsDisconnecting' 208 209 210 def _RunTest(self): 211 # Connect to the service. 212 service = self.test_env.shill.find_cellular_service_object() 213 self.test_env.shill.connect_service_synchronous( 214 service, CONNECT_DISCONNECT_TIMEOUT) 215 216 # Disconnect attempt should fail. 217 self.test_env.shill.disconnect_service_synchronous( 218 service, CONNECT_DISCONNECT_TIMEOUT) 219 220 # Service should remain connected. 221 if not self.IsServiceConnected(): 222 raise error.TestError('Service should remain connected after ' 223 'disconnect failure.') 224 225 226class DisconnectWhileDisconnectInProgressTest(DisconnectFailTest): 227 """ 228 Simulates a disconnect failure on successive disconnects. Fails if the 229 service doesn't remain connected. 230 231 """ 232 def _GetTestModemFunctorName(self): 233 return 'GetModemDisconnectWhileDisconnectInProgress' 234 235 236 def _RunTest(self): 237 # Connect to the service. 238 service = self.test_env.shill.find_cellular_service_object() 239 self.test_env.shill.connect_service_synchronous( 240 service, CONNECT_DISCONNECT_TIMEOUT) 241 242 # Issue first disconnect. Service should remain connected. 243 self.test_env.shill.disconnect_service_synchronous( 244 service, CONNECT_DISCONNECT_TIMEOUT) 245 if not self.IsServiceConnected(): 246 raise error.TestError('Service should remain connected after ' 247 'first disconnect.') 248 249 # Modem state should be disconnecting. 250 props = self.test_env.modem.GetAll(mm1_constants.I_MODEM) 251 if not props['State'] == mm1_constants.MM_MODEM_STATE_DISCONNECTING: 252 raise error.TestError('Modem should be in the DISCONNECTING state.') 253 254 # Issue second disconnect. Service should remain connected. 255 self.test_env.shill.disconnect_service_synchronous( 256 service, CONNECT_DISCONNECT_TIMEOUT) 257 if not self.IsServiceConnected(): 258 raise error.TestError('Service should remain connected after ' 259 'disconnect failure.') 260 261 262class DisconnectFailOtherTest(DisconnectFailTest): 263 """ 264 Simulates a disconnect failure. Fails if the service doesn't disconnect. 265 266 """ 267 def _GetTestModemFunctorName(self): 268 return 'GetModemDisconnectFailOther' 269 270 271 def _RunTest(self): 272 # Connect to the service. 273 service = self.test_env.shill.find_cellular_service_object() 274 self.test_env.shill.connect_service_synchronous( 275 service, CONNECT_DISCONNECT_TIMEOUT) 276 277 # Disconnect attempt should fail. 278 self.test_env.shill.disconnect_service_synchronous( 279 service, CONNECT_DISCONNECT_TIMEOUT) 280 281 # Service should be cleaned up as if disconnect succeeded. 282 if not self.IsServiceDisconnected(): 283 raise error.TestError('Service should be disconnected.') 284 285 286class cellular_DisconnectFailure(test.test): 287 """ 288 The test uses the pseudo modem manager to simulate two failure scenarios of 289 a Disconnect call: failure while the modem state is DISCONNECTING and 290 failure while it is CONNECTED. The expected behavior of shill is to do 291 nothing if the modem state is DISCONNECTING and to clean up the service 292 otherwise. 293 294 """ 295 version = 1 296 297 def run_once(self, pseudomodem_family='3GPP'): 298 tests = [ 299 DisconnectWhileStateIsDisconnectingTest(self, 300 pseudomodem_family), 301 DisconnectWhileDisconnectInProgressTest(self, 302 pseudomodem_family), 303 DisconnectFailOtherTest(self, pseudomodem_family), 304 ] 305 306 for test in tests: 307 test.Run() 308