site_utils.py revision c018b97e074c876c765ccfc4e9732a3d8c6a4cb8
1# Copyright (c) 2011 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, os, platform, re, signal, tempfile, time, uuid 6from autotest_lib.client.common_lib import error 7from autotest_lib.client.common_lib import utils 8 9class TimeoutError(error.TestError): 10 """Error raised when we time out when waiting on a condition.""" 11 pass 12 13 14class Crossystem(object): 15 """A wrapper for the crossystem utility.""" 16 17 def __init__(self, client): 18 self.cros_system_data = {} 19 self._client = client 20 21 def init(self): 22 self.cros_system_data = {} 23 (_, fname) = tempfile.mkstemp() 24 f = open(fname, 'w') 25 self._client.run('crossystem', stdout_tee=f) 26 f.close() 27 text = utils.read_file(fname) 28 for line in text.splitlines(): 29 assignment_string = line.split('#')[0] 30 if not assignment_string.count('='): 31 continue 32 (name, value) = assignment_string.split('=', 1) 33 self.cros_system_data[name.strip()] = value.strip() 34 os.remove(fname) 35 36 def __getattr__(self, name): 37 """ 38 Retrieve a crosssystem attribute. 39 40 The call crossystemobject.name() will return the crossystem reported 41 string. 42 """ 43 return lambda : self.cros_system_data[name] 44 45 46def get_oldest_pid_by_name(name): 47 """ 48 Return the oldest pid of a process whose name perfectly matches |name|. 49 50 name is an egrep expression, which will be matched against the entire name 51 of processes on the system. For example: 52 53 get_oldest_pid_by_name('chrome') 54 55 on a system running 56 8600 ? 00:00:04 chrome 57 8601 ? 00:00:00 chrome 58 8602 ? 00:00:00 chrome-sandbox 59 60 would return 8600, as that's the oldest process that matches. 61 chrome-sandbox would not be matched. 62 63 Arguments: 64 name: egrep expression to match. Will be anchored at the beginning and 65 end of the match string. 66 67 Returns: 68 pid as an integer, or None if one cannot be found. 69 70 Raises: 71 ValueError if pgrep returns something odd. 72 """ 73 str_pid = utils.system_output( 74 'pgrep -o ^%s$' % name, ignore_status=True).rstrip() 75 if str_pid: 76 return int(str_pid) 77 78 79def get_process_list(name, command_line=None): 80 """ 81 Return the list of pid for matching process |name command_line|. 82 83 on a system running 84 31475 ? 0:06 /opt/google/chrome/chrome --allow-webui-compositing - 85 31478 ? 0:00 /opt/google/chrome/chrome-sandbox /opt/google/chrome/ 86 31485 ? 0:00 /opt/google/chrome/chrome --type=zygote --log-level=1 87 31532 ? 1:05 /opt/google/chrome/chrome --type=renderer 88 89 get_process_list('chrome') 90 would return ['31475', '31485', '31532'] 91 92 get_process_list('chrome', '--type=renderer') 93 would return ['31532'] 94 95 Arguments: 96 name: process name to search for. If command_line is provided, name is 97 matched against full command line. If command_line is not provided, 98 name is only matched against the process name. 99 command line: when command line is passed, the full process command line 100 is used for matching. 101 102 Returns: 103 list of PIDs of the matching processes. 104 105 """ 106 # TODO(rohitbm) crbug.com/268861 107 flag = '-x' if not command_line else '-f' 108 name = '\'%s.*%s\'' % (name, command_line) if command_line else name 109 str_pid = utils.system_output( 110 'pgrep %s %s' % (flag, name), ignore_status=True).rstrip() 111 return str_pid 112 113 114def nuke_process_by_name(name, with_prejudice=False): 115 try: 116 pid = get_oldest_pid_by_name(name) 117 except Exception as e: 118 logging.error(e) 119 return 120 if with_prejudice: 121 utils.nuke_pid(pid, [signal.SIGKILL]) 122 else: 123 utils.nuke_pid(pid) 124 125 126def poll_for_condition( 127 condition, exception=None, timeout=10, sleep_interval=0.1, desc=None): 128 """Poll until a condition becomes true. 129 130 Arguments: 131 condition: function taking no args and returning bool 132 exception: exception to throw if condition doesn't become true 133 timeout: maximum number of seconds to wait 134 sleep_interval: time to sleep between polls 135 desc: description of default TimeoutError used if 'exception' is None 136 137 Returns: 138 The true value that caused the poll loop to terminate. 139 140 Raises: 141 'exception' arg if supplied; site_utils.TimeoutError otherwise 142 """ 143 start_time = time.time() 144 while True: 145 value = condition() 146 if value: 147 return value 148 if time.time() + sleep_interval - start_time > timeout: 149 if exception: 150 logging.error(exception) 151 raise exception 152 153 if desc: 154 desc = 'Timed out waiting for condition: %s' % desc 155 else: 156 desc = 'Timed out waiting for unnamed condition' 157 logging.error(desc) 158 raise TimeoutError, desc 159 160 time.sleep(sleep_interval) 161 162 163def save_vm_state(checkpoint): 164 """Saves the current state of the virtual machine. 165 166 This function is a NOOP if the test is not running under a virtual machine 167 with the USB serial port redirected. 168 169 Arguments: 170 checkpoint - Name used to identify this state 171 172 Returns: 173 None 174 """ 175 # The QEMU monitor has been redirected to the guest serial port located at 176 # /dev/ttyUSB0. To save the state of the VM, we just send the 'savevm' 177 # command to the serial port. 178 proc = platform.processor() 179 if 'QEMU' in proc and os.path.exists('/dev/ttyUSB0'): 180 logging.info('Saving VM state "%s"' % checkpoint) 181 serial = open('/dev/ttyUSB0', 'w') 182 serial.write("savevm %s\r\n" % checkpoint) 183 logging.info('Done saving VM state "%s"' % checkpoint) 184 185 186def check_raw_dmesg(dmesg, message_level, whitelist): 187 """Checks dmesg for unexpected warnings. 188 189 This function parses dmesg for message with message_level <= message_level 190 which do not appear in the whitelist. 191 192 Arguments: 193 dmesg - string containing raw dmesg buffer 194 message_level - minimum message priority to check 195 whitelist - messages to ignore 196 197 Returns: 198 List of unexpected warnings 199 """ 200 whitelist_re = re.compile(r'(%s)' % '|'.join(whitelist)) 201 unexpected = [] 202 for line in dmesg.splitlines(): 203 if int(line[1]) <= message_level: 204 stripped_line = line.split('] ', 1)[1] 205 if whitelist_re.search(stripped_line): 206 continue 207 unexpected.append(stripped_line) 208 return unexpected 209 210def verify_mesg_set(mesg, regex, whitelist): 211 """Verifies that the exact set of messages are present in a text. 212 213 This function finds all strings in the text matching a certain regex, and 214 then verifies that all expected strings are present in the set, and no 215 unexpected strings are there. 216 217 Arguments: 218 mesg - the mutiline text to be scanned 219 regex - regular expression to match 220 whitelist - messages to find in the output, a list of strings 221 (potentially regexes) to look for in the filtered output. All these 222 strings must be there, and no other strings should be present in the 223 filtered output. 224 225 Returns: 226 string of inconsistent findings (i.e. an empty string on success). 227 """ 228 229 rv = [] 230 231 missing_strings = [] 232 present_strings = [] 233 for line in mesg.splitlines(): 234 if not re.search(r'%s' % regex, line): 235 continue 236 present_strings.append(line.split('] ', 1)[1]) 237 238 for string in whitelist: 239 for present_string in list(present_strings): 240 if re.search(r'^%s$' % string, present_string): 241 present_strings.remove(present_string) 242 break 243 else: 244 missing_strings.append(string) 245 246 if present_strings: 247 rv.append('unexpected strings:') 248 rv.extend(present_strings) 249 if missing_strings: 250 rv.append('missing strings:') 251 rv.extend(missing_strings) 252 253 return '\n'.join(rv) 254 255 256def target_is_pie(): 257 """Returns whether the toolchain produces a PIE (position independent 258 executable) by default. 259 260 Arguments: 261 None 262 263 Returns: 264 True if the target toolchain produces a PIE by default. 265 False otherwise. 266 """ 267 268 269 command = 'echo | ${CC} -E -dD -P - | grep -i pie' 270 result = utils.system_output(command, retain_output=True, 271 ignore_status=True) 272 if re.search('#define __PIE__', result): 273 return True 274 else: 275 return False 276 277def target_is_x86(): 278 """Returns whether the toolchain produces an x86 object 279 280 Arguments: 281 None 282 283 Returns: 284 True if the target toolchain produces an x86 object 285 False otherwise. 286 """ 287 288 289 command = 'echo | ${CC} -E -dD -P - | grep -i 86' 290 result = utils.system_output(command, retain_output=True, 291 ignore_status=True) 292 if re.search('__i386__', result) or re.search('__x86_64__', result): 293 return True 294 else: 295 return False 296 297def mounts(): 298 ret = [] 299 for line in file('/proc/mounts'): 300 m = re.match(r'(?P<src>\S+) (?P<dest>\S+) (?P<type>\S+) (?P<opts>\S+).*', line) 301 if m: 302 ret.append(m.groupdict()) 303 return ret 304 305def is_mountpoint(path): 306 return path in [ m['dest'] for m in mounts() ] 307 308def require_mountpoint(path): 309 """ 310 Raises an exception if path is not a mountpoint. 311 """ 312 if not is_mountpoint(path): 313 raise error.TestFail('Path not mounted: "%s"' % path) 314 315def random_username(): 316 return str(uuid.uuid4()) + '@example.com' 317 318 319def parse_cmd_output(command, run_method=utils.run): 320 """Runs a command on a host object to retrieve host attributes. 321 322 The command should output to stdout in the format of: 323 <key> = <value> # <optional_comment> 324 325 326 @param command: Command to execute on the host. 327 @param run_method: Function to use to execute the command. Defaults to 328 utils.run so that the command will be executed locally. 329 Can be replace with a host.run call so that it will 330 execute on a DUT or external machine. Method must accept 331 a command argument, stdout_tee and stderr_tee args and 332 return a result object with a string attribute stdout 333 which will be parsed. 334 335 @returns a dictionary mapping host attributes to their values. 336 """ 337 result = {} 338 # Suppresses stdout so that the files are not printed to the logs. 339 cmd_result = run_method(command, stdout_tee=None, stderr_tee=None) 340 for line in cmd_result.stdout.splitlines(): 341 # Lines are of the format "<key> = <value> # <comment>" 342 key_value = re.match('^\s*(?P<key>[^ ]+)\s*=\s*(?P<value>[^ ]+)' 343 '(?:\s*#.*)?$', line) 344 if key_value: 345 result[key_value.group('key')] = key_value.group('value') 346 return result 347 348 349def set_from_keyval_output(out, delimiter=' '): 350 """Parse delimiter-separated key-val output into a set of tuples. 351 352 Output is expected to be multiline text output from a command. 353 Stuffs the key-vals into tuples in a set to be later compared. 354 355 e.g. deactivated 0 356 disableForceClear 0 357 ==> set(('deactivated', '0'), ('disableForceClear', '0')) 358 359 @param out: multiple lines of space-separated key-val pairs. 360 @param delimiter: character that separates key from val. Usually a 361 space but may be '=' or something else. 362 @return set of key-val tuples. 363 """ 364 results = set() 365 kv_match_re = re.compile('([^ ]+)%s(.*)' % delimiter) 366 for linecr in out.splitlines(): 367 match = kv_match_re.match(linecr.strip()) 368 if match: 369 results.add((match.group(1), match.group(2))) 370 return results 371