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