blob: b31d0bbebaa1a85a09a50af7047edb3ce55edc52 [file] [log] [blame] [edit]
# Copyright (c) 2012 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 dbus
import logging
from random import choice, randint
import time
from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import cros_ui_test
from autotest_lib.client.cros import rtc, sys_power
# Special import to define the location of the flimflam library.
from autotest_lib.client.cros import flimflam_test_path
import flimflam
SHILL_LOG_SCOPES = 'cellular+dbus+device+dhcp+manager+modem+portal+service'
class network_MobileSuspendResume(cros_ui_test.UITest):
version = 1
TIMEOUT = 60
device_okerrors = [
# Setting of device power can sometimes result with InProgress error
# if it is in the process of already doing so.
'org.chromium.flimflam.Error.InProgress',
]
service_okerrors = [
'org.chromium.flimflam.Error.InProgress',
'org.chromium.flimflam.Error.AlreadyConnected',
]
scenarios = {
'all': [
'scenario_suspend_mobile_enabled',
'scenario_suspend_mobile_disabled',
'scenario_suspend_mobile_disabled_twice',
'scenario_autoconnect',
],
'stress': [
'scenario_suspend_mobile_random',
],
}
modem_status_checks = [
lambda s: ('org/chromium/ModemManager' in s) or
('org/freedesktop/ModemManager' in s) or
('org/freedesktop/ModemManager1' in s),
lambda s: ('meid' in s) or ('EquipmentIdentifier' in s),
lambda s: 'Manufacturer' in s,
lambda s: 'Device' in s
]
def filterexns(self, function, exn_list):
try:
function()
except dbus.exceptions.DBusException, e:
if e._dbus_error_name not in exn_list:
raise e
# This function returns True when mobile service is available. Otherwise,
# if the timeout period has been hit, it returns false.
def mobile_service_available(self, timeout=60):
service = self.FindMobileService(timeout)
if service:
logging.info('Mobile service is available.')
return service
logging.info('Mobile service is not available.')
return None
def get_powered(self, device):
properties = device.GetProperties(utf8_strings=True)
logging.debug(properties)
logging.info('Power state of mobile device is %s.',
['off', 'on'][properties['Powered']])
return properties['Powered']
def _check_powered(self, device, check_enabled):
properties = device.GetProperties(utf8_strings=True)
power_state = (properties['Powered'] == 1)
return power_state if check_enabled else not power_state
def check_powered(self, device, check_enabled):
logging.info('Polling to check device state is %s.',
'enabled' if check_enabled else 'disabled')
utils.poll_for_condition(
lambda: self._check_powered(device, check_enabled),
exception=error.TestFail(
'Failed to verify the device is in power state %s.',
'enabled' if check_enabled else 'disabled'),
timeout=self.TIMEOUT)
logging.info('Verified device power state.')
def enable_device(self, device, enable):
lambda_func = lambda: device.Enable() if enable else device.Disable()
self.filterexns(lambda_func,
network_MobileSuspendResume.device_okerrors)
# Sometimes if we disable the modem then immediately enable the modem
# we hit a condition where the modem seems to ignore the enable command
# and keep the modem disabled. This is to prevent that from happening.
time.sleep(4)
return self.get_powered(device) == enable
def suspend_resume(self, duration=10):
sys_power.do_suspend(duration)
logging.info('Machine resumed')
# Race condition hack alert: Before we added this sleep, this
# test was very sensitive to the relative timing of the test
# and modem resumption. There is a window where flimflam has
# not yet learned that the old modem has gone away (it doesn't
# find this out until seconds after we resume) and the test is
# running. If the test finds and attempts to use the old
# modem, those operations will fail. There's no good
# hardware-independent way to see the modem go away and come
# back, so instead we sleep
time.sleep(4)
# __get_mobile_device is a hack wrapper around the FindMobileDevice
# that verifies that GetProperties can be called before proceeding.
# There appears to be an issue after suspend/resume where GetProperties
# returns with UnknownMethod called until some time later.
def __get_mobile_device(self, timeout=TIMEOUT):
properties = None
start_time = time.time()
timeout = start_time + timeout
while properties is None and time.time() < timeout:
try:
device = self.FindMobileDevice(timeout)
properties = device.GetProperties(utf8_strings=True)
except dbus.exceptions.DBusException:
logging.debug('Mobile device not ready yet')
properties = None
time.sleep(1)
if not device:
# If device is not found, spit the output of lsusb for debugging.
lsusb_output = utils.system_output('lsusb', timeout=self.TIMEOUT)
logging.debug('Mobile device not found. lsusb output:')
logging.debug(lsusb_output)
raise error.TestError('Mobile device not found.')
return device
# The suspend_mobile_enabled test suspends, then resumes the machine while
# mobile is enabled.
def scenario_suspend_mobile_enabled(self, **kwargs):
device = self.__get_mobile_device()
self.enable_device(device, True)
if not self.mobile_service_available():
raise error.TestError('Unable to find mobile service.')
self.suspend_resume(20)
# The suspend_mobile_disabled test suspends, then resumes the machine
# while mobile is disabled.
def scenario_suspend_mobile_disabled(self, **kwargs):
device = self.__get_mobile_device()
self.enable_device(device, False)
self.suspend_resume(20)
# This verifies that the device is in the same state before and after
# the device is suspended/resumed.
device = self.__get_mobile_device()
logging.info('Checking to see if device is in the same state as prior '
'to suspend/resume')
self.check_powered(device, False)
# Turn on the device to make sure we can bring it back up.
self.enable_device(device, True)
# The suspend_mobile_disabled_twice subroutine is here because
# of bug 9405. The test will suspend/resume the device twice
# while mobile is disabled. We will then verify that mobile can be
# enabled thereafter.
def scenario_suspend_mobile_disabled_twice(self, **kwargs):
device = self.__get_mobile_device()
self.enable_device(device, False)
for _ in [0, 1]:
self.suspend_resume(20)
# This verifies that the device is in the same state before
# and after the device is suspended/resumed.
device = self.__get_mobile_device()
logging.info('Checking to see if device is in the same state as '
'prior to suspend/resume')
self.check_powered(device, False)
# Turn on the device to make sure we can bring it back up.
self.enable_device(device, True)
# Special override for connecting to wimax devices since it requires
# EAP parameters.
def connect_wimax(self, service=None, identity='test',
password='test', **kwargs):
service.SetProperty('EAP.Identity', identity)
service.SetProperty('EAP.Password', identity)
self.flim.ConnectService(service=service, **kwargs)
# This test randomly enables or disables the modem. This is mainly used
# for stress tests as it does not check the power state of the modem before
# and after suspend/resume.
def scenario_suspend_mobile_random(self, stress_iterations=10, **kwargs):
logging.debug('Running suspend_mobile_random %d times' %
stress_iterations)
device = self.__get_mobile_device()
self.enable_device(device, choice([True, False]))
# Suspend the device for a random duration, wake it,
# wait for the service to appear, then wait for
# some random duration before suspending again.
for i in range(stress_iterations):
logging.debug('Running iteration %d' % (i+1))
self.suspend_resume(randint(10, 40))
device = self.__get_mobile_device()
self.enable_device(device, True)
if not self.FindMobileService(self.TIMEOUT*2):
raise error.TestError('Unable to find mobile service')
time.sleep(randint(1, 30))
# This verifies that autoconnect works.
def scenario_autoconnect(self, **kwargs):
device = self.__get_mobile_device()
self.enable_device(device, True)
service = self.FindMobileService(self.TIMEOUT)
if not service:
raise error.TestError('Unable to find mobile service')
props = service.GetProperties(utf8_strings=True)
if props['AutoConnect']:
expected_states = ['ready', 'online', 'portal']
else:
expected_states = ['idle']
for _ in xrange(5):
# Must wait at least 20 seconds to ensure that the suspend occurs
self.suspend_resume(20)
# wait for the device to come back
device = self.__get_mobile_device()
# verify the service state is correct
service = self.FindMobileService(self.TIMEOUT)
if not service:
raise error.TestFail('Cannot find mobile service')
state, _ = self.flim.WaitForServiceState(service,
expected_states,
self.TIMEOUT)
if not state in expected_states:
raise error.TestFail('Mobile state %s not in %s as expected'
% (state, ', '.join(expected_states)))
# Running modem status is not supported by all modems, specifically wimax
# type modems.
def _skip_modem_status(self, *args, **kwargs):
return 1
# Returns 1 if modem_status returned output within duration.
# otherwise, returns 0
def _get_modem_status(self, duration=TIMEOUT):
time_end = time.time() + duration
while time.time() < time_end:
status = utils.system_output('modem status', timeout=self.TIMEOUT)
if reduce(lambda x, y: x & y(status),
network_MobileSuspendResume.modem_status_checks,
True):
break
else:
return 0
return 1
# This is the wrapper around the running of each scenario with
# initialization steps and final checks.
def run_scenario(self, function_name, **kwargs):
device = self.__get_mobile_device()
# Initialize all tests with the power off.
self.enable_device(device, False)
function = getattr(self, function_name)
logging.info('Running %s' % function_name)
function(**kwargs)
# By the end of each test, the mobile device should be up.
# Here we verify that the power state of the device is up, and
# that the mobile service can be found.
device = self.__get_mobile_device()
logging.info('Checking that modem is powered on after scenario %s.',
function_name)
self.check_powered(device, True)
logging.info('Scenario complete: %s.' % function_name)
if not self.modem_status():
raise error.TestFail('Failed to get modem_status after %s.'
% function_name)
service = self.mobile_service_available()
if not service:
raise error.TestFail('Could not find mobile service at the end '
'of test %s.' % function_name)
def init_flimflam(self, device_type):
# Initialize flimflam and device type specific functions.
self.flim = flimflam.FlimFlam(dbus.SystemBus())
self.flim.SetDebugTags(SHILL_LOG_SCOPES)
logging.debug('Using device type: %s' % device_type)
if device_type == flimflam.FlimFlam.DEVICE_WIMAX:
self.FindMobileService = self.flim.FindWimaxService
self.FindMobileDevice = self.flim.FindWimaxDevice
self.modem_status = self._skip_modem_status
self.connect_mobile_service= self.connect_wimax
elif device_type == flimflam.FlimFlam.DEVICE_CELLULAR:
self.FindMobileService = self.flim.FindCellularService
self.FindMobileDevice = self.flim.FindCellularDevice
self.modem_status = self._get_modem_status
self.connect_mobile_service = self.flim.ConnectService
else:
raise error.TestError('Device type %s not supported yet.' %
device_type)
def run_once(self, scenario_group='all', autoconnect=False,
device_type=flimflam.FlimFlam.DEVICE_CELLULAR, **kwargs):
# Replace the test type with the list of tests
if scenario_group not in network_MobileSuspendResume.scenarios.keys():
scenario_group = 'all'
logging.info('Running scenario group: %s' % scenario_group)
scenarios = network_MobileSuspendResume.scenarios[scenario_group]
self.init_flimflam(device_type)
device = self.__get_mobile_device()
if not device:
raise error.TestFail('Cannot find mobile device.')
self.enable_device(device, True)
service = self.FindMobileService(self.TIMEOUT)
if not service:
raise error.TestFail('Cannot find mobile service.')
service.SetProperty('AutoConnect', dbus.Boolean(autoconnect))
logging.info('Running scenarios with autoconnect %s.' % autoconnect)
for t in scenarios:
self.run_scenario(t, **kwargs)