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