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 logging, threading, time 6 7from autotest_lib.client.cros.crash_test import CrashTest 8from autotest_lib.server import autotest, test 9from autotest_lib.client.common_lib import error 10 11_WAIT_DELAY = 15 12_LONG_TIMEOUT = 200 13_SUSPEND_TIME = 30 14_WAKE_PRESS_IN_SEC = 0.2 15_CRASH_PATHS = [CrashTest._SYSTEM_CRASH_DIR.replace("/crash",""), 16 CrashTest._FALLBACK_USER_CRASH_DIR.replace("/crash",""), 17 CrashTest._USER_CRASH_DIRS.replace("/crash","")] 18 19class platform_ExternalUsbPeripherals(test.test): 20 """Uses servo to repeatedly connect/remove USB devices during boot.""" 21 version = 1 22 23 24 def getPluggedUsbDevices(self): 25 """Determines the external USB devices plugged 26 27 @returns plugged_list: List of plugged usb devices names 28 29 """ 30 lsusb_output = self.host.run('lsusb').stdout.strip() 31 items = lsusb_output.split('\n') 32 plugged_list = [] 33 unnamed_device_count = 1 34 for item in items: 35 columns = item.split(' ') 36 if len(columns) == 6 or len(' '.join(columns[6:]).strip()) == 0: 37 logging.debug('Unnamed device located, adding generic name.') 38 name = 'Unnamed device %d' % unnamed_device_count 39 unnamed_device_count += 1 40 else: 41 name = ' '.join(columns[6:]).strip() 42 #Avoid servo components 43 if not name.startswith('Standard Microsystems Corp'): 44 plugged_list.append(name) 45 return plugged_list 46 47 48 def set_hub_power(self, on=True): 49 """Setting USB hub power status 50 51 @param on: To power on the servo-usb hub or not 52 53 """ 54 reset = 'off' 55 if not on: 56 reset = 'on' 57 self.host.servo.set('dut_hub1_rst1', reset) 58 self.pluged_status = on 59 60 61 def action_login(self): 62 """Login i.e. runs running client test 63 64 @exception TestFail failed to login within timeout. 65 66 """ 67 self.autotest_client.run_test(self.client_autotest, 68 exit_without_logout=True) 69 70 71 def wait_for_cmd_output(self, cmd, check, timeout, timeout_msg): 72 """Waits till command output is meta 73 74 @param cmd: executed command 75 @param check: string to be checked for in cmd output 76 @param timeout: max time in sec to wait for output 77 @param timeout_msg: timeout failure message 78 79 @returns True if check is found in command output; False otherwise 80 """ 81 start_time = int(time.time()) 82 time_delta = 0 83 command = '%s %s' % (cmd, check) 84 logging.debug('Command: %s', command) 85 while(self.host.run(command, ignore_status=True).exit_status != 0): 86 time_delta = int(time.time()) - start_time 87 if time_delta > timeout: 88 self.add_failure('%s - %d sec' % (timeout_msg, timeout)) 89 return False 90 time.sleep(0.5) 91 logging.debug('Succeeded in :%d sec', time_delta) 92 return True 93 94 95 def suspend_for_time(self, suspend_time=_SUSPEND_TIME): 96 """Calls the host method suspend with suspend_time argument. 97 98 @param suspend_time: time to suspend the device for. 99 100 """ 101 try: 102 self.host.suspend(suspend_time=suspend_time) 103 except error.AutoservSuspendError: 104 pass 105 106 107 def action_suspend(self): 108 """Suspend i.e. powerd_dbus_suspend and wait 109 110 @returns boot_id for the following resume 111 """ 112 boot_id = self.host.get_boot_id() 113 thread = threading.Thread(target=self.suspend_for_time) 114 thread.start() 115 self.host.test_wait_for_sleep(_LONG_TIMEOUT) 116 logging.debug('--- Suspended') 117 self.suspend_status = True 118 return boot_id 119 120 121 def action_resume(self, boot_id): 122 """Resume i.e. press power key and wait 123 124 @param boot_id: boot id obtained prior to suspending 125 126 """ 127 self.host.servo.power_key(_WAKE_PRESS_IN_SEC) 128 self.host.test_wait_for_resume(boot_id, _LONG_TIMEOUT) 129 logging.debug('--- Resumed') 130 self.suspend_status = False 131 132 133 def crash_not_detected(self, crash_path): 134 """Check for kernel, browser, process crashes 135 136 @param crash_path: Crash files path 137 138 @returns True if there were not crashes; False otherwise 139 """ 140 result = True 141 if str(self.host.run('ls %s' % crash_path, 142 ignore_status=True)).find('crash') != -1: 143 crash_out = self.host.run('ls %s/crash/' % crash_path).stdout 144 crash_files = crash_out.strip().split('\n') 145 for crash_file in crash_files: 146 if crash_file.find('.meta') != -1 and \ 147 crash_file.find('kernel_warning') == -1: 148 self.add_failure('CRASH DETECTED in %s/crash: %s' % 149 (crash_path, crash_file)) 150 result = False 151 return result 152 153 154 def check_plugged_usb_devices(self): 155 """Checks the plugged peripherals match device list. 156 157 @returns True if expected USB peripherals are detected; False otherwise 158 """ 159 result = True 160 if self.pluged_status and self.usb_list != None: 161 # Check for mandatory USb devices passed by usb_list flag 162 for usb_name in self.usb_list: 163 found = self.wait_for_cmd_output( 164 'lsusb | grep -E ', usb_name, _WAIT_DELAY * 4, 165 'Not detecting %s' % usb_name) 166 result = result and found 167 time.sleep(_WAIT_DELAY) 168 on_now = self.getPluggedUsbDevices() 169 if self.pluged_status: 170 if not self.diff_list.issubset(on_now): 171 missing = str(self.diff_list.difference(on_now)) 172 self.add_failure('Missing connected peripheral(s) ' 173 'when plugged: %s ' % missing) 174 result = False 175 else: 176 present = self.diff_list.intersection(on_now) 177 if len(present) > 0: 178 self.add_failure('Still presented peripheral(s) ' 179 'when unplugged: %s ' % str(present)) 180 result = False 181 return result 182 183 184 def check_usb_peripherals_details(self): 185 """Checks the effect from plugged in USB peripherals. 186 187 @returns True if command line output is matched successfuly; Else False 188 """ 189 usb_check_result = True 190 for cmd in self.usb_checks.keys(): 191 out_match_list = self.usb_checks.get(cmd) 192 if cmd.startswith('loggedin:'): 193 if not self.login_status: 194 continue 195 cmd = cmd.replace('loggedin:','') 196 # Run the usb check command 197 for out_match in out_match_list: 198 match_result = self.wait_for_cmd_output( 199 cmd, out_match, _WAIT_DELAY * 4, 200 'USB CHECKS DETAILS failed at %s %s:' % (cmd, out_match)) 201 usb_check_result = usb_check_result and match_result 202 return usb_check_result 203 204 205 def check_status(self): 206 """Performs checks after each action: 207 - for USB detected devices 208 - for generated crash files 209 - peripherals effect checks on cmd line 210 211 @returns True if all of the iteration checks pass; False otherwise. 212 """ 213 result = True 214 if not self.suspend_status: 215 # Detect the USB peripherals 216 result = self.check_plugged_usb_devices() 217 # Check for crash files 218 if self.crash_check: 219 for crash_path in _CRASH_PATHS: 220 result = result and self.crash_not_detected(crash_path) 221 if self.pluged_status and (self.usb_checks != None): 222 # Check for plugged USB devices details 223 result = result and self.check_usb_peripherals_details() 224 return result 225 226 227 def remove_crash_data(self): 228 """Delete crash meta files if present""" 229 for crash_path in _CRASH_PATHS: 230 if not self.crash_not_detected(crash_path): 231 self.host.run('rm -rf %s/crash' % crash_path, 232 ignore_status=True) 233 234 235 def add_failure(self, reason): 236 """ Adds a failure reason to list of failures to be reported at end 237 238 @param reason: failure reason to record 239 240 """ 241 if self.action_step is not None: 242 self.fail_reasons.append('%s FAILS - %s' % 243 (self.action_step, reason)) 244 245 246 247 def cleanup(self): 248 """Disconnect servo hub""" 249 self.set_hub_power(False) 250 self.host.servo.set('usb_mux_sel3', 'servo_sees_usbkey') 251 252 253 def run_once(self, host, client_autotest, action_sequence, repeat, 254 usb_list=None, usb_checks=None, crash_check=False): 255 self.client_autotest = client_autotest 256 self.host = host 257 self.autotest_client = autotest.Autotest(self.host) 258 self.usb_list = usb_list 259 self.usb_checks = usb_checks 260 self.crash_check = crash_check 261 262 self.suspend_status = False 263 self.login_status = False 264 self.fail_reasons = list() 265 self.action_step = None 266 267 self.host.servo.switch_usbkey('dut') 268 self.host.servo.set('usb_mux_sel3', 'dut_sees_usbkey') 269 time.sleep(_WAIT_DELAY) 270 271 # Collect USB peripherals when unplugged 272 self.set_hub_power(False) 273 time.sleep(_WAIT_DELAY) 274 off_list = self.getPluggedUsbDevices() 275 276 # Collect USB peripherals when plugged 277 self.set_hub_power(True) 278 time.sleep(_WAIT_DELAY * 2) 279 on_list = self.getPluggedUsbDevices() 280 281 self.diff_list = set(on_list).difference(set(off_list)) 282 if len(self.diff_list) == 0: 283 # Fail if no devices detected after 284 raise error.TestError('No connected devices were detected. Make ' 285 'sure the devices are connected to USB_KEY ' 286 'and DUT_HUB1_USB on the servo board.') 287 logging.debug('Connected devices list: %s', self.diff_list) 288 289 board = host.get_board().split(':')[1] 290 action_sequence = action_sequence.upper() 291 actions = action_sequence.split(',') 292 boot_id = 0 293 self.remove_crash_data() 294 295 for iteration in xrange(1, repeat + 1): 296 step = 0 297 for action in actions: 298 step += 1 299 action = action.strip() 300 self.action_step = 'STEP %d.%d. %s' % (iteration, step, action) 301 logging.info(self.action_step) 302 303 if action == 'RESUME': 304 self.action_resume(boot_id) 305 time.sleep(_WAIT_DELAY) 306 elif action == 'UNPLUG': 307 self.set_hub_power(False) 308 elif action == 'PLUG': 309 self.set_hub_power(True) 310 elif self.suspend_status == False: 311 if action.startswith('LOGIN'): 312 if self.login_status: 313 logging.debug('Skipping login. Already logged in.') 314 continue 315 else: 316 self.action_login() 317 self.login_status = True 318 elif action == 'REBOOT': 319 self.host.reboot() 320 time.sleep(_WAIT_DELAY * 3) 321 self.login_status = False 322 elif action == 'SUSPEND': 323 boot_id = self.action_suspend() 324 else: 325 logging.info('WRONG ACTION: %s .', self.action_step) 326 327 self.check_status() 328 329 if self.fail_reasons: 330 raise error.TestFail('Failures reported: %s' % 331 str(self.fail_reasons)) 332