• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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