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, filters=None): | |
Sami
2015/02/03 14:24:22
Could you document these parameters?
jbudorick
2015/02/03 15:38:02
Done. Removed filters, since it's not used.
| |
26 if isinstance(adb, adb_wrapper.AdbWrapper): | |
27 self._adb = adb | |
28 else: | |
29 raise ValueError('Unsupported type passed for argument "device"') | |
30 self._clear = clear | |
31 self._filters = filters | |
32 self._logcat_out = None | |
33 self._logcat_out_file = None | |
34 self._logcat_proc = None | |
35 | |
36 @decorators.WithTimeoutAndRetriesDefaults(10, 0) | |
37 def WaitFor(self, success_regex, failure_regex, timeout=None, retries=None): | |
perezju
2015/02/03 09:18:44
maybe failure_regex=None should be a default?
jbudorick
2015/02/03 15:38:02
Done.
| |
38 """Wait for a matching logcat line or until a timeout occurs. | |
39 | |
40 This will attempt to match lines in the logcat against both |success_regex| | |
41 and |failure_regex| (if provided). Note that this calls re.search on each | |
42 logcat line, not re.match, so the provided regular expressions don't have | |
43 to match an entire line. | |
44 | |
45 Args: | |
46 success_regex: The regular expression to search for. | |
47 failure_regex: An optional regular expression that, if hit, causes this | |
48 to stop looking for a match. Can be None. | |
49 timeout: timeout in seconds | |
50 retries: number of retries | |
51 | |
52 Returns: | |
53 A match object if |success_regex| matches a part of a logcat line, or | |
54 None if |failure_regex| matches a part of a logcat line. | |
55 Raises: | |
56 CommandFailedError on logcat failure (NOT on a |failure_regex| match). | |
57 CommandTimeoutError if no logcat line matching either |success_regex| or | |
58 |failure_regex| is found in |timeout| seconds. | |
59 DeviceUnreachableError if the device becomes unreachable. | |
60 """ | |
61 if isinstance(success_regex, basestring): | |
62 success_regex = re.compile(success_regex) | |
63 if isinstance(failure_regex, basestring): | |
64 failure_regex = re.compile(failure_regex) | |
65 | |
66 logging.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern) | |
67 | |
68 # NOTE This will continue looping until: | |
69 # - success_regex matches a line, in which case the match object is | |
70 # returned. | |
71 # - failure_regex matches a line, in which case None is returned | |
72 # - the timeout is hit, in which case a CommandTimeoutError is raised. | |
73 for l in self._adb.Logcat(): | |
74 m = success_regex.search(l) | |
75 if m: | |
76 return m | |
77 if failure_regex and failure_regex.search(l): | |
78 return None | |
79 | |
80 def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None, | |
81 component=None): | |
82 """Finds all lines in the logcat that match the provided constraints. | |
83 | |
84 Args: | |
85 message_regex: The regular expression that the <message> section must | |
86 match. | |
87 proc_id: The process ID to match. If None, matches any process ID. | |
88 thread_id: The thread ID to match. If None, matches any thread ID. | |
89 log_level: The log level to match. If None, matches any log level. | |
90 component: The component to match. If None, matches any component. | |
91 | |
92 Returns: | |
93 An iterable containing objects with five attributes: | |
94 |proc_id|: the process ID | |
95 |thread_id|: the thread ID | |
96 |log_level|: the log level | |
97 |component|: the component | |
98 |message|: the logcat message | |
99 """ | |
100 LogcatLine = collections.namedtuple( | |
101 'LogcatLine', | |
102 ['proc_id', 'thread_id', 'log_level', 'component', 'message']) | |
103 | |
104 if proc_id is None: | |
105 proc_id = r'\d+' | |
106 if thread_id is None: | |
107 thread_id = r'\d+' | |
108 if log_level is None: | |
109 log_level = r'[VDIWEF]' | |
110 if component is None: | |
111 component = r'[^\s:]+' | |
112 threadtime_re = re.compile( | |
113 type(self)._THREADTIME_RE_FORMAT % ( | |
114 proc_id, thread_id, log_level, component, message_regex)) | |
115 | |
116 regexed_lines = ( | |
117 re.match(threadtime_re, l) | |
118 for l in self._adb.Logcat(dump=True, logcat_format='threadtime')) | |
119 only_matches = (m for m in regexed_lines if m) | |
120 return (LogcatLine(*m.group(1, 2, 3, 4, 5)) for m in only_matches) | |
121 | |
122 def Start(self): | |
123 """Starts the logcat monitor.""" | |
Sami
2015/02/03 14:24:22
It might be good to mention that this is the point
jbudorick
2015/02/03 15:38:01
Done.
| |
124 if self._clear: | |
125 self._adb.Logcat(clear=True) | |
126 | |
127 def Stop(self): | |
Sami
2015/02/03 14:24:22
Do we need to keep this around?
jbudorick
2015/02/03 15:38:02
No. I had thought it was aesthetically cleaner for
| |
128 """Stops the logcat monitor.""" | |
129 pass | |
130 | |
131 def __enter__(self): | |
132 """Starts the logcat monitor.""" | |
133 self.Start() | |
134 return self | |
135 | |
136 def __exit__(self, exc_type, exc_val, exc_tb): | |
137 """Stops the logcat monitor.""" | |
138 self.Stop() | |
OLD | NEW |