blob: cfdc7cff8d4734ca62e6785b816740eb98a3d5da [file] [log] [blame]
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import time
from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.cellular import mm1_constants
from autotest_lib.client.cros.cellular import test_environment
from autotest_lib.client.cros.cellular.pseudomodem import modem_3gpp
from autotest_lib.client.cros.cellular.pseudomodem import modem_cdma
from autotest_lib.client.cros.cellular.pseudomodem import pm_errors
from autotest_lib.client.cros.cellular.pseudomodem import utils as pm_utils
# Use our own connect/disconnect timeout for this test because we are using a
# a pseudomodem which should run faster than a real modem.
CONNECT_DISCONNECT_TIMEOUT = 10
def _GetModemSuperClass(family):
"""
Obtains the correct Modem base class to use for the given family.
@param family: The modem family. Should be one of |3GPP|/|CDMA|.
@returns: The relevant Modem base class.
@raises error.TestError, if |family| is not one of '3GPP' or 'CDMA'.
"""
if family == '3GPP':
return modem_3gpp.Modem3gpp
elif family == 'CDMA':
return modem_cdma.ModemCdma
else:
raise error.TestError('Invalid pseudomodem family: %s', family)
def GetModemDisconnectWhileStateIsDisconnecting(family):
"""
Returns a modem that fails on disconnect request.
@param family: The family of the modem returned.
@returns: A modem of the given family that fails disconnect.
"""
modem_class = _GetModemSuperClass(family)
class _TestModem(modem_class):
""" Actual modem implementation. """
@pm_utils.log_dbus_method(return_cb_arg='return_cb',
raise_cb_arg='raise_cb')
def Disconnect(
self, bearer_path, return_cb, raise_cb, *return_cb_args):
"""
Test implementation of
org.freedesktop.ModemManager1.Modem.Simple.Disconnect. Sets the
modem state to DISCONNECTING and then fails, fooling shill into
thinking that the disconnect failed while disconnecting.
Refer to modem_simple.ModemSimple.Connect for documentation.
"""
logging.info('Simulating failed Disconnect')
self.ChangeState(mm1_constants.MM_MODEM_STATE_DISCONNECTING,
mm1_constants.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN)
time.sleep(5)
raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED)
return _TestModem()
def GetModemDisconnectWhileDisconnectInProgress(family):
"""
Returns a modem implementation that fails disconnect except the first one.
@param family: The family of the returned modem.
@returns: A modem of the given family that fails all but the first
disconnect attempts.
"""
modem_class = _GetModemSuperClass(family)
class _TestModem(modem_class):
""" The actual modem implementation. """
def __init__(self):
modem_class.__init__(self)
self.disconnect_count = 0
@pm_utils.log_dbus_method(return_cb_arg='return_cb',
raise_cb_arg='raise_cb')
def Disconnect(
self, bearer_path, return_cb, raise_cb, *return_cb_args):
"""
Test implementation of
org.freedesktop.ModemManager1.Modem.Simple.Disconnect. Keeps
count of successive disconnect operations and fails during all
but the first one.
Refer to modem_simple.ModemSimple.Connect for documentation.
"""
# On the first call, set the state to DISCONNECTING.
self.disconnect_count += 1
if self.disconnect_count == 1:
self.ChangeState(
mm1_constants.MM_MODEM_STATE_DISCONNECTING,
mm1_constants.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN)
time.sleep(5)
else:
raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED)
return _TestModem()
def GetModemDisconnectFailOther(family):
"""
Returns a modem that fails a disconnect attempt with a generic error.
@param family: The family of the modem returned.
@returns: A modem of the give family that fails disconnect.
"""
modem_class = _GetModemSuperClass(family)
class _TestModem(modem_class):
""" The actual modem implementation. """
@pm_utils.log_dbus_method(return_cb_arg='return_cb',
raise_cb_arg='raise_cb')
def Disconnect(
self, bearer_path, return_cb, raise_cb, *return_cb_args):
"""
Test implementation of
org.freedesktop.ModemManager1.Modem.Simple.Disconnect.
Fails with an error.
Refer to modem_simple.ModemSimple.Connect for documentation.
"""
raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED)
return _TestModem()
class DisconnectFailTest(object):
"""
DisconnectFailTest implements common functionality in all test cases.
"""
def __init__(self, test, pseudomodem_family):
self.test = test
self._pseudomodem_family = pseudomodem_family
def IsServiceConnected(self):
"""
@return True, if service is connected.
"""
service = self.test_env.shill.find_cellular_service_object()
properties = service.GetProperties(utf8_strings=True)
state = properties.get('State', None)
return state in ['portal', 'online']
def IsServiceDisconnected(self):
"""
@return True, if service is disconnected.
"""
service = self.test_env.shill.find_cellular_service_object()
properties = service.GetProperties(utf8_strings=True)
state = properties.get('State', None)
return state == 'idle'
def Run(self):
"""
Runs the test.
@raises test.TestFail, if |test_modem| hasn't been initialized.
"""
self.test_env = test_environment.CellularPseudoMMTestEnvironment(
pseudomm_args=(
{'test-module' : __file__,
'test-modem-class' : self._GetTestModemFunctorName(),
'test-modem-arg' : [self._pseudomodem_family]},))
with self.test_env:
self._RunTest()
def _GetTestModemFunctorName(self):
""" Returns the modem to be used by the pseudomodem for this test. """
raise NotImplementedError()
def _RunTest(self):
raise NotImplementedError()
class DisconnectWhileStateIsDisconnectingTest(DisconnectFailTest):
"""
Simulates a disconnect failure while the modem is still disconnecting.
Fails if the service doesn't remain connected.
"""
def _GetTestModemFunctorName(self):
return 'GetModemDisconnectWhileStateIsDisconnecting'
def _RunTest(self):
# Connect to the service.
service = self.test_env.shill.find_cellular_service_object()
self.test_env.shill.connect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
# Disconnect attempt should fail.
self.test_env.shill.disconnect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
# Service should remain connected.
if not self.IsServiceConnected():
raise error.TestError('Service should remain connected after '
'disconnect failure.')
class DisconnectWhileDisconnectInProgressTest(DisconnectFailTest):
"""
Simulates a disconnect failure on successive disconnects. Fails if the
service doesn't remain connected.
"""
def _GetTestModemFunctorName(self):
return 'GetModemDisconnectWhileDisconnectInProgress'
def _RunTest(self):
# Connect to the service.
service = self.test_env.shill.find_cellular_service_object()
self.test_env.shill.connect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
# Issue first disconnect. Service should remain connected.
self.test_env.shill.disconnect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
if not self.IsServiceConnected():
raise error.TestError('Service should remain connected after '
'first disconnect.')
# Modem state should be disconnecting.
props = self.test_env.modem.GetAll(mm1_constants.I_MODEM)
if not props['State'] == mm1_constants.MM_MODEM_STATE_DISCONNECTING:
raise error.TestError('Modem should be in the DISCONNECTING state.')
# Issue second disconnect. Service should remain connected.
self.test_env.shill.disconnect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
if not self.IsServiceConnected():
raise error.TestError('Service should remain connected after '
'disconnect failure.')
class DisconnectFailOtherTest(DisconnectFailTest):
"""
Simulates a disconnect failure. Fails if the service doesn't disconnect.
"""
def _GetTestModemFunctorName(self):
return 'GetModemDisconnectFailOther'
def _RunTest(self):
# Connect to the service.
service = self.test_env.shill.find_cellular_service_object()
self.test_env.shill.connect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
# Disconnect attempt should fail.
self.test_env.shill.disconnect_service_synchronous(
service, CONNECT_DISCONNECT_TIMEOUT)
# Service should be cleaned up as if disconnect succeeded.
if not self.IsServiceDisconnected():
raise error.TestError('Service should be disconnected.')
class cellular_DisconnectFailure(test.test):
"""
The test uses the pseudo modem manager to simulate two failure scenarios of
a Disconnect call: failure while the modem state is DISCONNECTING and
failure while it is CONNECTED. The expected behavior of shill is to do
nothing if the modem state is DISCONNECTING and to clean up the service
otherwise.
"""
version = 1
def run_once(self, pseudomodem_family='3GPP'):
tests = [
DisconnectWhileStateIsDisconnectingTest(self,
pseudomodem_family),
DisconnectWhileDisconnectInProgressTest(self,
pseudomodem_family),
DisconnectFailOtherTest(self, pseudomodem_family),
]
for test in tests:
test.Run()