1# Copyright (c) 2012 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
5import dbus
6import logging
7import random
8import time
9
10from autotest_lib.client.bin import test
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.cros.networking import cellular_proxy
13from autotest_lib.client.cros.networking import shill_context
14from autotest_lib.client.cros.networking import shill_proxy
15
16
17class network_3GSafetyDance(test.test):
18    """
19    Stress tests all connection manager 3G operations.
20
21    This test runs a long series of 3G operations in pseudorandom order. All of
22    these 3G operations must return a convincing result (EINPROGRESS or no
23    error).
24
25    """
26    version = 1
27
28    def _filterexns(self, fn):
29        v = None
30        try:
31            v = fn()
32        except dbus.exceptions.DBusException, error:
33            if error.get_dbus_name() in self.okerrors:
34                return v, error.get_dbus_message()
35            else:
36                raise error
37        return v, ''
38
39    def _enable(self):
40        logging.info('Enable')
41        self._filterexns(lambda:
42            self.test_env.shill.manager.EnableTechnology('cellular'))
43
44    def _disable(self):
45        logging.info('Disable')
46        self._filterexns(lambda:
47            self.test_env.shill.manager.DisableTechnology('cellular'))
48
49    def _ignoring(self, reason):
50        if ('AlreadyConnected' in reason or
51            'Not connected' in reason or
52            'Bearer already being connected' in reason or
53            'Bearer already being disconnected' in reason or
54            'InProgress' in reason):
55            return True
56        if 'NotSupported' in reason:
57            # We should only ignore this error if we've previously disabled
58            # cellular technology and the service subsequently disappeared
59            # when we tried to connect again.
60            return not self.test_env.shill.find_cellular_service_object()
61        return False
62
63    def _connect(self):
64        logging.info('Connect')
65        try:
66            service = self.test_env.shill.wait_for_cellular_service_object(
67                    timeout_seconds=5)
68        except shill_proxy.ShillProxyError:
69            return
70
71        success, reason = self._filterexns(lambda:
72                self.test_env.shill.connect_service_synchronous(
73                        service=service,
74                        timeout_seconds=
75                        cellular_proxy.CellularProxy.SERVICE_CONNECT_TIMEOUT))
76        if not success and not self._ignoring(reason):
77            raise error.TestFail('Could not connect: %s' % reason)
78
79    def _disconnect(self):
80        logging.info('Disconnect')
81        try:
82            service = self.test_env.shill.wait_for_cellular_service_object(
83                    timeout_seconds=5)
84        except shill_proxy.ShillProxyError:
85            return
86
87        success, reason = self._filterexns(lambda:
88                self.test_env.shill.disconnect_service_synchronous(
89                        service=service,
90                        timeout_seconds=
91                        cellular_proxy.CellularProxy.
92                        SERVICE_DISCONNECT_TIMEOUT))
93        if not success and not self._ignoring(reason):
94            raise error.TestFail('Could not disconnect: %s' % reason)
95
96    def _op(self):
97        n = random.randint(0, len(self.ops) - 1)
98        self.ops[n]()
99        time.sleep(random.randint(5, 20) / 10.0)
100
101    def _run_once_internal(self, ops=30, seed=None):
102        if not seed:
103            seed = int(time.time())
104        self.okerrors = [
105            'org.chromium.flimflam.Error.InProgress',
106            'org.chromium.flimflam.Error.AlreadyConnected',
107            'org.chromium.flimflam.Error.AlreadyEnabled',
108            'org.chromium.flimflam.Error.AlreadyDisabled'
109        ]
110        self.ops = [ self._enable,
111                     self._disable,
112                     self._connect,
113                     self._disconnect ]
114        self.device = self.test_env.shill.find_cellular_device_object()
115        if not self.device:
116            raise error.TestFail('Could not find cellular device.')
117
118        # Start in a disabled state.
119        self._disable()
120        logging.info('Seed: %d', seed)
121        random.seed(seed)
122        for _ in xrange(ops):
123            self._op()
124
125    def run_once(self, test_env, ops=30, seed=None):
126        self.test_env = test_env
127        with test_env, shill_context.ServiceAutoConnectContext(
128                test_env.shill.find_cellular_service_object, False):
129            self._run_once_internal(ops, seed)
130
131            # Enable device to restore autoconnect settings.
132            self._enable()
133            test_env.shill.wait_for_cellular_service_object()
134