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 5 import dbus 6 import logging 7 from random import choice, randint 8 import time 9 10 from autotest_lib.client.bin import test, utils 11 from autotest_lib.client.common_lib import error 12 from autotest_lib.client.common_lib.cros import chrome 13 from autotest_lib.client.cros import rtc 14 from autotest_lib.client.cros.power import power_suspend 15 16 # Special import to define the location of the flimflam library. 17 from autotest_lib.client.cros import flimflam_test_path 18 import flimflam 19 20 SHILL_LOG_SCOPES = 'cellular+dbus+device+dhcp+manager+modem+portal+service' 21 22 class 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