Index: build/android/pylib/device/logcat_monitor.py |
diff --git a/build/android/pylib/device/logcat_monitor.py b/build/android/pylib/device/logcat_monitor.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7ede49c53b811f41f381389e26c0798e066d0111 |
--- /dev/null |
+++ b/build/android/pylib/device/logcat_monitor.py |
@@ -0,0 +1,143 @@ |
+# Copyright 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+# pylint: disable=unused-argument |
+ |
+import collections |
+import itertools |
+import logging |
+import subprocess |
+import tempfile |
+import time |
+import re |
+ |
+from pylib.device import adb_wrapper |
+from pylib.device import decorators |
+from pylib.device import device_errors |
+ |
+ |
+class LogcatMonitor(object): |
+ |
+ # Format: <DATE> <TIME> <PID> <TID> <LEVEL> <COMPONENT>: <MESSAGE> |
+ _THREADTIME_RE_FORMAT = r'\S* +\S* +(%s) +(%s) +(%s) +(%s): +(%s)$' |
+ |
+ def __init__(self, adb, clear=True): |
+ """Create a LogcatMonitor instance. |
+ |
+ Args: |
+ adb: An instance of adb_wrapper.AdbWrapper. |
+ clear: If True, clear the logcat when monitoring starts. |
+ """ |
+ if isinstance(adb, adb_wrapper.AdbWrapper): |
+ self._adb = adb |
+ else: |
+ raise ValueError('Unsupported type passed for argument "device"') |
+ self._clear = clear |
+ self._logcat_out = None |
+ self._logcat_out_file = None |
+ self._logcat_proc = None |
+ |
+ @decorators.WithTimeoutAndRetriesDefaults(10, 0) |
+ def WaitFor(self, success_regex, failure_regex=None, timeout=None, |
+ retries=None): |
+ """Wait for a matching logcat line or until a timeout occurs. |
+ |
+ This will attempt to match lines in the logcat against both |success_regex| |
+ and |failure_regex| (if provided). Note that this calls re.search on each |
+ logcat line, not re.match, so the provided regular expressions don't have |
+ to match an entire line. |
+ |
+ Args: |
+ success_regex: The regular expression to search for. |
+ failure_regex: An optional regular expression that, if hit, causes this |
+ to stop looking for a match. Can be None. |
+ timeout: timeout in seconds |
+ retries: number of retries |
+ |
+ Returns: |
+ A match object if |success_regex| matches a part of a logcat line, or |
+ None if |failure_regex| matches a part of a logcat line. |
+ Raises: |
+ CommandFailedError on logcat failure (NOT on a |failure_regex| match). |
+ CommandTimeoutError if no logcat line matching either |success_regex| or |
+ |failure_regex| is found in |timeout| seconds. |
+ DeviceUnreachableError if the device becomes unreachable. |
+ """ |
+ if isinstance(success_regex, basestring): |
+ success_regex = re.compile(success_regex) |
+ if isinstance(failure_regex, basestring): |
+ failure_regex = re.compile(failure_regex) |
+ |
+ logging.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern) |
+ |
+ # NOTE This will continue looping until: |
+ # - success_regex matches a line, in which case the match object is |
+ # returned. |
+ # - failure_regex matches a line, in which case None is returned |
+ # - the timeout is hit, in which case a CommandTimeoutError is raised. |
+ for l in self._adb.Logcat(): |
+ m = success_regex.search(l) |
+ if m: |
+ return m |
+ if failure_regex and failure_regex.search(l): |
+ return None |
+ |
+ def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None, |
+ component=None): |
+ """Finds all lines in the logcat that match the provided constraints. |
+ |
+ Args: |
+ message_regex: The regular expression that the <message> section must |
+ match. |
+ proc_id: The process ID to match. If None, matches any process ID. |
+ thread_id: The thread ID to match. If None, matches any thread ID. |
+ log_level: The log level to match. If None, matches any log level. |
+ component: The component to match. If None, matches any component. |
+ |
+ Returns: |
+ An iterable containing objects with five attributes: |
+ |proc_id|: the process ID |
+ |thread_id|: the thread ID |
+ |log_level|: the log level |
+ |component|: the component |
+ |message|: the logcat message |
+ """ |
+ LogcatLine = collections.namedtuple( |
+ 'LogcatLine', |
+ ['proc_id', 'thread_id', 'log_level', 'component', 'message']) |
+ |
+ if proc_id is None: |
+ proc_id = r'\d+' |
+ if thread_id is None: |
+ thread_id = r'\d+' |
+ if log_level is None: |
+ log_level = r'[VDIWEF]' |
+ if component is None: |
+ component = r'[^\s:]+' |
+ threadtime_re = re.compile( |
+ type(self)._THREADTIME_RE_FORMAT % ( |
+ proc_id, thread_id, log_level, component, message_regex)) |
+ |
+ regexed_lines = ( |
+ re.match(threadtime_re, l) |
+ for l in self._adb.Logcat(dump=True, logcat_format='threadtime')) |
+ only_matches = (m for m in regexed_lines if m) |
+ return (LogcatLine(*m.group(1, 2, 3, 4, 5)) for m in only_matches) |
+ |
+ def Start(self): |
+ """Starts the logcat monitor. |
+ |
+ Clears the logcat if |clear| was set in |__init__|. |
+ """ |
+ if self._clear: |
+ self._adb.Logcat(clear=True) |
+ |
+ def __enter__(self): |
+ """Starts the logcat monitor.""" |
+ self.Start() |
+ return self |
+ |
+ def __exit__(self, exc_type, exc_val, exc_tb): |
+ """Stops the logcat monitor.""" |
+ pass |