| 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
|
|
|