OLD | NEW |
(Empty) | |
| 1 # Copyright 2015 The Chromium 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 # pylint: disable=unused-argument |
| 6 |
| 7 import collections |
| 8 import itertools |
| 9 import logging |
| 10 import subprocess |
| 11 import tempfile |
| 12 import time |
| 13 import re |
| 14 |
| 15 from pylib.device import adb_wrapper |
| 16 from pylib.device import decorators |
| 17 from pylib.device import device_errors |
| 18 |
| 19 |
| 20 class LogcatMonitor(object): |
| 21 |
| 22 # Format: <DATE> <TIME> <PID> <TID> <LEVEL> <COMPONENT>: <MESSAGE> |
| 23 _THREADTIME_RE_FORMAT = r'\S* +\S* +(%s) +(%s) +(%s) +(%s): +(%s)$' |
| 24 |
| 25 def __init__(self, adb, clear=True): |
| 26 """Create a LogcatMonitor instance. |
| 27 |
| 28 Args: |
| 29 adb: An instance of adb_wrapper.AdbWrapper. |
| 30 clear: If True, clear the logcat when monitoring starts. |
| 31 """ |
| 32 if isinstance(adb, adb_wrapper.AdbWrapper): |
| 33 self._adb = adb |
| 34 else: |
| 35 raise ValueError('Unsupported type passed for argument "device"') |
| 36 self._clear = clear |
| 37 self._logcat_out = None |
| 38 self._logcat_out_file = None |
| 39 self._logcat_proc = None |
| 40 |
| 41 @decorators.WithTimeoutAndRetriesDefaults(10, 0) |
| 42 def WaitFor(self, success_regex, failure_regex=None, timeout=None, |
| 43 retries=None): |
| 44 """Wait for a matching logcat line or until a timeout occurs. |
| 45 |
| 46 This will attempt to match lines in the logcat against both |success_regex| |
| 47 and |failure_regex| (if provided). Note that this calls re.search on each |
| 48 logcat line, not re.match, so the provided regular expressions don't have |
| 49 to match an entire line. |
| 50 |
| 51 Args: |
| 52 success_regex: The regular expression to search for. |
| 53 failure_regex: An optional regular expression that, if hit, causes this |
| 54 to stop looking for a match. Can be None. |
| 55 timeout: timeout in seconds |
| 56 retries: number of retries |
| 57 |
| 58 Returns: |
| 59 A match object if |success_regex| matches a part of a logcat line, or |
| 60 None if |failure_regex| matches a part of a logcat line. |
| 61 Raises: |
| 62 CommandFailedError on logcat failure (NOT on a |failure_regex| match). |
| 63 CommandTimeoutError if no logcat line matching either |success_regex| or |
| 64 |failure_regex| is found in |timeout| seconds. |
| 65 DeviceUnreachableError if the device becomes unreachable. |
| 66 """ |
| 67 if isinstance(success_regex, basestring): |
| 68 success_regex = re.compile(success_regex) |
| 69 if isinstance(failure_regex, basestring): |
| 70 failure_regex = re.compile(failure_regex) |
| 71 |
| 72 logging.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern) |
| 73 |
| 74 # NOTE This will continue looping until: |
| 75 # - success_regex matches a line, in which case the match object is |
| 76 # returned. |
| 77 # - failure_regex matches a line, in which case None is returned |
| 78 # - the timeout is hit, in which case a CommandTimeoutError is raised. |
| 79 for l in self._adb.Logcat(): |
| 80 m = success_regex.search(l) |
| 81 if m: |
| 82 return m |
| 83 if failure_regex and failure_regex.search(l): |
| 84 return None |
| 85 |
| 86 def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None, |
| 87 component=None): |
| 88 """Finds all lines in the logcat that match the provided constraints. |
| 89 |
| 90 Args: |
| 91 message_regex: The regular expression that the <message> section must |
| 92 match. |
| 93 proc_id: The process ID to match. If None, matches any process ID. |
| 94 thread_id: The thread ID to match. If None, matches any thread ID. |
| 95 log_level: The log level to match. If None, matches any log level. |
| 96 component: The component to match. If None, matches any component. |
| 97 |
| 98 Returns: |
| 99 An iterable containing objects with five attributes: |
| 100 |proc_id|: the process ID |
| 101 |thread_id|: the thread ID |
| 102 |log_level|: the log level |
| 103 |component|: the component |
| 104 |message|: the logcat message |
| 105 """ |
| 106 LogcatLine = collections.namedtuple( |
| 107 'LogcatLine', |
| 108 ['proc_id', 'thread_id', 'log_level', 'component', 'message']) |
| 109 |
| 110 if proc_id is None: |
| 111 proc_id = r'\d+' |
| 112 if thread_id is None: |
| 113 thread_id = r'\d+' |
| 114 if log_level is None: |
| 115 log_level = r'[VDIWEF]' |
| 116 if component is None: |
| 117 component = r'[^\s:]+' |
| 118 threadtime_re = re.compile( |
| 119 type(self)._THREADTIME_RE_FORMAT % ( |
| 120 proc_id, thread_id, log_level, component, message_regex)) |
| 121 |
| 122 regexed_lines = ( |
| 123 re.match(threadtime_re, l) |
| 124 for l in self._adb.Logcat(dump=True, logcat_format='threadtime')) |
| 125 only_matches = (m for m in regexed_lines if m) |
| 126 return (LogcatLine(*m.group(1, 2, 3, 4, 5)) for m in only_matches) |
| 127 |
| 128 def Start(self): |
| 129 """Starts the logcat monitor. |
| 130 |
| 131 Clears the logcat if |clear| was set in |__init__|. |
| 132 """ |
| 133 if self._clear: |
| 134 self._adb.Logcat(clear=True) |
| 135 |
| 136 def __enter__(self): |
| 137 """Starts the logcat monitor.""" |
| 138 self.Start() |
| 139 return self |
| 140 |
| 141 def __exit__(self, exc_type, exc_val, exc_tb): |
| 142 """Stops the logcat monitor.""" |
| 143 pass |
OLD | NEW |