|  | # 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): | 
|  |  | 
|  | _THREADTIME_RE_FORMAT = ( | 
|  | r'(?P<date>\S*) +(?P<time>\S*) +(?P<proc_id>%s) +(?P<thread_id>%s) +' | 
|  | r'(?P<log_level>%s) +(?P<component>%s) *: +(?P<message>%s)$') | 
|  |  | 
|  | def __init__(self, adb, clear=True, filter_specs=None): | 
|  | """Create a LogcatMonitor instance. | 
|  |  | 
|  | Args: | 
|  | adb: An instance of adb_wrapper.AdbWrapper. | 
|  | clear: If True, clear the logcat when monitoring starts. | 
|  | filter_specs: An optional list of '<tag>[:priority]' strings. | 
|  | """ | 
|  | if isinstance(adb, adb_wrapper.AdbWrapper): | 
|  | self._adb = adb | 
|  | else: | 
|  | raise ValueError('Unsupported type passed for argument "device"') | 
|  | self._clear = clear | 
|  | self._filter_specs = filter_specs | 
|  | 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(filter_specs=self._filter_specs): | 
|  | 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. | 
|  |  | 
|  | Yields: | 
|  | A match object for each matching line in the logcat. The match object | 
|  | will always contain, in addition to groups defined in |message_regex|, | 
|  | the following named groups: 'date', 'time', 'proc_id', 'thread_id', | 
|  | 'log_level', 'component', and '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)) | 
|  |  | 
|  | for line in self._adb.Logcat(dump=True, logcat_format='threadtime'): | 
|  | m = re.match(threadtime_re, line) | 
|  | if m: | 
|  | yield m | 
|  |  | 
|  | 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 |