• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7from random import choice, randint
8import time
9
10from autotest_lib.client.bin import test, utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib.cros import chrome
13from autotest_lib.client.cros import rtc
14from autotest_lib.client.cros.power import power_suspend
15
16# Special import to define the location of the flimflam library.
17from autotest_lib.client.cros import flimflam_test_path
18import flimflam
19
20SHILL_LOG_SCOPES = 'cellular+dbus+device+dhcp+manager+modem+portal+service'
21
22class cellular_SuspendResume(test.test):
23    version = 1
24    TIMEOUT = 60
25
26    device_okerrors = [
27        # Setting of device power can sometimes result with InProgress error
28        # if it is in the process of already doing so.
29        'org.chromium.flimflam.Error.InProgress',
30    ]
31
32    service_okerrors = [
33        'org.chromium.flimflam.Error.InProgress',
34        'org.chromium.flimflam.Error.AlreadyConnected',
35    ]
36
37    scenarios = {
38        'all': [
39            'scenario_suspend_mobile_enabled',
40            'scenario_suspend_mobile_disabled',
41            'scenario_suspend_mobile_disabled_twice',
42            'scenario_autoconnect',
43        ],
44        'stress': [
45            'scenario_suspend_mobile_random',
46        ],
47    }
48
49    modem_status_checks = [
50        lambda s: ('org/chromium/ModemManager' in s) or
51                  ('org/freedesktop/ModemManager' in s) or
52                  ('org/freedesktop/ModemManager1' in s),
53        lambda s: ('meid' in s) or ('EquipmentIdentifier' in s),
54        lambda s: 'Manufacturer' in s,
55        lambda s: 'Device' in s
56    ]
57
58    def filterexns(self, function, exn_list):
59        try:
60            function()
61        except dbus.exceptions.DBusException, e:
62            if e._dbus_error_name not in exn_list:
63                raise e
64
65    # This function returns True when mobile service is available.  Otherwise,
66    # if the timeout period has been hit, it returns false.
67    def mobile_service_available(self, timeout=60):
68        service = self.flim.FindCellularService(timeout)
69        if service:
70            logging.info('Mobile service is available.')
71            return service
72        logging.info('Mobile service is not available.')
73        return None
74
75    def get_powered(self, device):
76        properties = device.GetProperties(utf8_strings=True)
77        logging.debug(properties)
78        logging.info('Power state of mobile device is %s.',
79                     ['off', 'on'][properties['Powered']])
80        return properties['Powered']
81
82    def _check_powered(self, device, check_enabled):
83        properties = device.GetProperties(utf8_strings=True)
84        power_state = (properties['Powered'] == 1)
85        return power_state if check_enabled else not power_state
86
87    def check_powered(self, device, check_enabled):
88        logging.info('Polling to check device state is %s.',
89                     'enabled' if check_enabled else 'disabled')
90        utils.poll_for_condition(
91            lambda: self._check_powered(device, check_enabled),
92            exception=error.TestFail(
93                'Failed to verify the device is in power state %s.',
94                'enabled' if check_enabled else 'disabled'),
95            timeout=self.TIMEOUT)
96        logging.info('Verified device power state.')
97
98    def enable_device(self, device, enable):
99        lambda_func = lambda: device.Enable() if enable else device.Disable()
100        self.filterexns(lambda_func,
101                        cellular_SuspendResume.device_okerrors)
102        # Sometimes if we disable the modem then immediately enable the modem
103        # we hit a condition where the modem seems to ignore the enable command
104        # and keep the modem disabled.  This is to prevent that from happening.
105        time.sleep(4)
106        return self.get_powered(device) == enable
107
108    def suspend_resume(self, duration=10):
109        suspender = power_suspend.Suspender(self.resultsdir, throw=True)
110        suspender.suspend(duration)
111        logging.info('Machine resumed')
112
113        # Race condition hack alert: Before we added this sleep, this
114        # test was very sensitive to the relative timing of the test
115        # and modem resumption.  There is a window where flimflam has
116        # not yet learned that the old modem has gone away (it doesn't
117        # find this out until seconds after we resume) and the test is
118        # running.  If the test finds and attempts to use the old
119        # modem, those operations will fail.  There's no good
120        # hardware-independent way to see the modem go away and come
121        # back, so instead we sleep
122        time.sleep(4)
123
124    # __get_mobile_device is a hack wrapper around the flim.FindCellularDevice
125    # that verifies that GetProperties can be called before proceeding.
126    # There appears to be an issue after suspend/resume where GetProperties
127    # returns with UnknownMethod called until some time later.
128    def __get_mobile_device(self, timeout=TIMEOUT):
129        device = None
130        properties = None
131        start_time = time.time()
132        timeout = start_time + timeout
133        while properties is None and time.time() < timeout:
134            try:
135                device = self.flim.FindCellularDevice(timeout)
136                properties = device.GetProperties(utf8_strings=True)
137            except dbus.exceptions.DBusException:
138                logging.debug('Mobile device not ready yet')
139                device = None
140                properties = None
141
142            time.sleep(1)
143
144        if not device:
145            # If device is not found, spit the output of lsusb for debugging.
146            lsusb_output = utils.system_output('lsusb', timeout=self.TIMEOUT)
147            logging.debug('Mobile device not found. lsusb output:')
148            logging.debug(lsusb_output)
149            raise error.TestError('Mobile device not found.')
150        return device
151
152    # The suspend_mobile_enabled test suspends, then resumes the machine while
153    # mobile is enabled.
154    def scenario_suspend_mobile_enabled(self, **kwargs):
155        device = self.__get_mobile_device()
156        self.enable_device(device, True)
157        if not self.mobile_service_available():
158            raise error.TestError('Unable to find mobile service.')
159        self.suspend_resume(20)
160
161    # The suspend_mobile_disabled test suspends, then resumes the machine
162    # while mobile is disabled.
163    def scenario_suspend_mobile_disabled(self, **kwargs):
164        device = self.__get_mobile_device()
165        self.enable_device(device, False)
166        self.suspend_resume(20)
167
168        # This verifies that the device is in the same state before and after
169        # the device is suspended/resumed.
170        device = self.__get_mobile_device()
171        logging.info('Checking to see if device is in the same state as prior '
172                     'to suspend/resume')
173        self.check_powered(device, False)
174
175        # Turn on the device to make sure we can bring it back up.
176        self.enable_device(device, True)
177
178    # The suspend_mobile_disabled_twice subroutine is here because
179    # of bug 9405.  The test will suspend/resume the device twice
180    # while mobile is disabled.  We will then verify that mobile can be
181    # enabled thereafter.
182    def scenario_suspend_mobile_disabled_twice(self, **kwargs):
183        device = self.__get_mobile_device()
184        self.enable_device(device, False)
185
186        for _ in [0, 1]:
187            self.suspend_resume(20)
188
189            # This verifies that the device is in the same state before
190            # and after the device is suspended/resumed.
191            device = self.__get_mobile_device()
192            logging.info('Checking to see if device is in the same state as '
193                         'prior to suspend/resume')
194            self.check_powered(device, False)
195
196        # Turn on the device to make sure we can bring it back up.
197        self.enable_device(device, True)
198
199
200    # This test randomly enables or disables the modem.  This is mainly used
201    # for stress tests as it does not check the power state of the modem before
202    # and after suspend/resume.
203    def scenario_suspend_mobile_random(self, stress_iterations=10, **kwargs):
204        logging.debug('Running suspend_mobile_random %d times' %
205                      stress_iterations)
206        device = self.__get_mobile_device()
207        self.enable_device(device, choice([True, False]))
208
209        # Suspend the device for a random duration, wake it,
210        # wait for the service to appear, then wait for
211        # some random duration before suspending again.
212        for i in range(stress_iterations):
213            logging.debug('Running iteration %d' % (i+1))
214            self.suspend_resume(randint(10, 40))
215            device = self.__get_mobile_device()
216            self.enable_device(device, True)
217            if not self.flim.FindCellularService(self.TIMEOUT*2):
218                raise error.TestError('Unable to find mobile service')
219            time.sleep(randint(1, 30))
220
221
222    # This verifies that autoconnect works.
223    def scenario_autoconnect(self, **kwargs):
224        device = self.__get_mobile_device()
225        self.enable_device(device, True)
226        service = self.flim.FindCellularService(self.TIMEOUT)
227        if not service:
228            raise error.TestError('Unable to find mobile service')
229
230        props = service.GetProperties(utf8_strings=True)
231        if props['AutoConnect']:
232            expected_states = ['ready', 'online', 'portal']
233        else:
234            expected_states = ['idle']
235
236        for _ in xrange(5):
237            # Must wait at least 20 seconds to ensure that the suspend occurs
238            self.suspend_resume(20)
239
240            # wait for the device to come back
241            device = self.__get_mobile_device()
242
243            # verify the service state is correct
244            service = self.flim.FindCellularService(self.TIMEOUT)
245            if not service:
246                raise error.TestFail('Cannot find mobile service')
247
248            state, _ = self.flim.WaitForServiceState(service,
249                                                     expected_states,
250                                                     self.TIMEOUT)
251            if not state in expected_states:
252                raise error.TestFail('Mobile state %s not in %s as expected'
253                                     % (state, ', '.join(expected_states)))
254
255
256    # Returns 1 if modem_status returned output within duration.
257    # otherwise, returns 0
258    def _get_modem_status(self, duration=TIMEOUT):
259        time_end = time.time() + duration
260        while time.time() < time_end:
261            status = utils.system_output('modem status', timeout=self.TIMEOUT)
262            if reduce(lambda x, y: x & y(status),
263                      cellular_SuspendResume.modem_status_checks,
264                      True):
265                break
266        else:
267            return 0
268        return 1
269
270    # This is the wrapper around the running of each scenario with
271    # initialization steps and final checks.
272    def run_scenario(self, function_name, **kwargs):
273        device = self.__get_mobile_device()
274
275        # Initialize all tests with the power off.
276        self.enable_device(device, False)
277
278        function = getattr(self, function_name)
279        logging.info('Running %s' % function_name)
280        function(**kwargs)
281
282        # By the end of each test, the mobile device should be up.
283        # Here we verify that the power state of the device is up, and
284        # that the mobile service can be found.
285        device = self.__get_mobile_device()
286        logging.info('Checking that modem is powered on after scenario %s.',
287                     function_name)
288        self.check_powered(device, True)
289
290        logging.info('Scenario complete: %s.' % function_name)
291
292        if not self._get_modem_status():
293            raise error.TestFail('Failed to get modem_status after %s.'
294                              % function_name)
295        service = self.mobile_service_available()
296        if not service:
297            raise error.TestFail('Could not find mobile service at the end '
298                                 'of test %s.' % function_name)
299
300    def init_flimflam(self):
301        # Initialize flimflam and device type specific functions.
302        self.flim = flimflam.FlimFlam(dbus.SystemBus())
303        self.flim.SetDebugTags(SHILL_LOG_SCOPES)
304
305        self.flim.FindCellularService = self.flim.FindCellularService
306        self.flim.FindCellularDevice = self.flim.FindCellularDevice
307
308    def run_once(self, scenario_group='all', autoconnect=False, **kwargs):
309
310        with chrome.Chrome():
311            # Replace the test type with the list of tests
312            if (scenario_group not in
313                    cellular_SuspendResume.scenarios.keys()):
314                scenario_group = 'all'
315            logging.info('Running scenario group: %s' % scenario_group)
316            scenarios = cellular_SuspendResume.scenarios[scenario_group]
317
318            self.init_flimflam()
319
320            device = self.__get_mobile_device()
321            if not device:
322                raise error.TestFail('Cannot find mobile device.')
323            self.enable_device(device, True)
324
325            service = self.flim.FindCellularService(self.TIMEOUT)
326            if not service:
327                raise error.TestFail('Cannot find mobile service.')
328
329            service.SetProperty('AutoConnect', dbus.Boolean(autoconnect))
330
331            logging.info('Running scenarios with autoconnect %s.' % autoconnect)
332
333            for t in scenarios:
334                self.run_scenario(t, **kwargs)
335