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