307 lines
11 KiB
Python
307 lines
11 KiB
Python
# 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()
|