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