1be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik# Copyright 2015 The Chromium Authors. All rights reserved.
2be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik# Use of this source code is governed by a BSD-style license that can be
3be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik# found in the LICENSE file.
4be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
5be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik# pylint: disable=unused-argument
6be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
7be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport errno
8be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport logging
9be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport os
10be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport re
11be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport shutil
12be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport tempfile
13be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport threading
14be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikimport time
15be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
16be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikfrom devil.android import decorators
17be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikfrom devil.android import device_errors
18be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikfrom devil.android.sdk import adb_wrapper
19be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikfrom devil.utils import reraiser_thread
20be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
21eeb03f366d148f92337cfd7577087ade44ab9285John Recklogger = logging.getLogger(__name__)
22eeb03f366d148f92337cfd7577087ade44ab9285John Reck
23be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
24be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikclass LogcatMonitor(object):
25be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
26b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang  _RECORD_ITER_TIMEOUT = 0.2
27d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik  _RECORD_THREAD_JOIN_WAIT = 5.0
28be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  _WAIT_TIME = 0.2
29b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang  THREADTIME_RE_FORMAT = (
30be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      r'(?P<date>\S*) +(?P<time>\S*) +(?P<proc_id>%s) +(?P<thread_id>%s) +'
31be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      r'(?P<log_level>%s) +(?P<component>%s) *: +(?P<message>%s)$')
32be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
33b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang  def __init__(self, adb, clear=True, filter_specs=None, output_file=None,
34b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang               transform_func=None):
35be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Create a LogcatMonitor instance.
36be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
37be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Args:
38be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      adb: An instance of adb_wrapper.AdbWrapper.
39be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      clear: If True, clear the logcat when monitoring starts.
40be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      filter_specs: An optional list of '<tag>[:priority]' strings.
41be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      output_file: File path to save recorded logcat.
42b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang      transform_func: An optional unary callable that takes and returns
43b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang        a list of lines, possibly transforming them in the process.
44be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
45be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if isinstance(adb, adb_wrapper.AdbWrapper):
46be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._adb = adb
47be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    else:
48be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      raise ValueError('Unsupported type passed for argument "device"')
49be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._clear = clear
50be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._filter_specs = filter_specs
51be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._output_file = output_file
52be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._record_file = None
53be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._record_file_lock = threading.Lock()
54be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._record_thread = None
55be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._stop_recording_event = threading.Event()
56b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang    self._transform_func = transform_func
57be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
58be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  @property
59be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def output_file(self):
60be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    return self._output_file
61be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
62be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  @decorators.WithTimeoutAndRetriesDefaults(10, 0)
63be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def WaitFor(self, success_regex, failure_regex=None, timeout=None,
64be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik              retries=None):
65be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Wait for a matching logcat line or until a timeout occurs.
66be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
67be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    This will attempt to match lines in the logcat against both |success_regex|
68be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    and |failure_regex| (if provided). Note that this calls re.search on each
69be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    logcat line, not re.match, so the provided regular expressions don't have
70be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    to match an entire line.
71be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
72be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Args:
73be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      success_regex: The regular expression to search for.
74be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      failure_regex: An optional regular expression that, if hit, causes this
75be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        to stop looking for a match. Can be None.
76be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      timeout: timeout in seconds
77be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      retries: number of retries
78be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
79be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Returns:
80be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      A match object if |success_regex| matches a part of a logcat line, or
81be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      None if |failure_regex| matches a part of a logcat line.
82be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Raises:
83be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      CommandFailedError on logcat failure (NOT on a |failure_regex| match).
84be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      CommandTimeoutError if no logcat line matching either |success_regex| or
85be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        |failure_regex| is found in |timeout| seconds.
86be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      DeviceUnreachableError if the device becomes unreachable.
87be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      LogcatMonitorCommandError when calling |WaitFor| while not recording
88be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        logcat.
89be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
90be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if self._record_thread is None:
91be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      raise LogcatMonitorCommandError(
92be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          'Must be recording logcat when calling |WaitFor|',
93be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          device_serial=str(self._adb))
94be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if isinstance(success_regex, basestring):
95be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      success_regex = re.compile(success_regex)
96be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if isinstance(failure_regex, basestring):
97be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      failure_regex = re.compile(failure_regex)
98be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
99eeb03f366d148f92337cfd7577087ade44ab9285John Reck    logger.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern)
100be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
101be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    # NOTE This will continue looping until:
102be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    #  - success_regex matches a line, in which case the match object is
103be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    #    returned.
104be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    #  - failure_regex matches a line, in which case None is returned
105be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    #  - the timeout is hit, in which case a CommandTimeoutError is raised.
106be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    with open(self._record_file.name, 'r') as f:
107be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      while True:
108be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        line = f.readline()
109be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        if line:
110be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          m = success_regex.search(line)
111be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          if m:
112be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik            return m
113be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          if failure_regex and failure_regex.search(line):
114be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik            return None
115be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        else:
116be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          time.sleep(self._WAIT_TIME)
117be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
118be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None,
119be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik              component=None):
120be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Finds all lines in the logcat that match the provided constraints.
121be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
122be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Args:
123be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      message_regex: The regular expression that the <message> section must
124be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        match.
125be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      proc_id: The process ID to match. If None, matches any process ID.
126be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      thread_id: The thread ID to match. If None, matches any thread ID.
127be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      log_level: The log level to match. If None, matches any log level.
128be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      component: The component to match. If None, matches any component.
129be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
130be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Raises:
131be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      LogcatMonitorCommandError when calling |FindAll| before recording logcat.
132be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
133be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Yields:
134be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      A match object for each matching line in the logcat. The match object
135be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      will always contain, in addition to groups defined in |message_regex|,
136be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      the following named groups: 'date', 'time', 'proc_id', 'thread_id',
137be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      'log_level', 'component', and 'message'.
138be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
139be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if self._record_file is None:
140be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      raise LogcatMonitorCommandError(
141be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          'Must have recorded or be recording a logcat to call |FindAll|',
142be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          device_serial=str(self._adb))
143be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if proc_id is None:
144be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      proc_id = r'\d+'
145be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if thread_id is None:
146be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      thread_id = r'\d+'
147be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if log_level is None:
148be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      log_level = r'[VDIWEF]'
149be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if component is None:
150be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      component = r'[^\s:]+'
151be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    # pylint: disable=protected-access
152be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    threadtime_re = re.compile(
153b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang        type(self).THREADTIME_RE_FORMAT % (
154be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik            proc_id, thread_id, log_level, component, message_regex))
155be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
156be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    with open(self._record_file.name, 'r') as f:
157be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      for line in f:
158be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        m = re.match(threadtime_re, line)
159be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        if m:
160be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          yield m
161be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
162be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def _StartRecording(self):
163be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Starts recording logcat to file.
164be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
165be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Function spawns a thread that records logcat to file and will not die
166be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    until |StopRecording| is called.
167be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
168be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    def record_to_file():
169be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      # Write the log with line buffering so the consumer sees each individual
170be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      # line.
171be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      for data in self._adb.Logcat(filter_specs=self._filter_specs,
172d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik                                   logcat_format='threadtime',
173d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik                                   iter_timeout=self._RECORD_ITER_TIMEOUT):
174d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik        if self._stop_recording_event.isSet():
175d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik          return
176d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik
177d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik        if data is None:
178d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik          # Logcat can yield None if the iter_timeout is hit.
179d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik          continue
180d0ebf633155e2d637d289933ef7dbc5d86f73881Chris Craik
181be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        with self._record_file_lock:
182be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          if self._record_file and not self._record_file.closed:
183b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang            if self._transform_func:
184b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang              data = '\n'.join(self._transform_func([data]))
185be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik            self._record_file.write(data + '\n')
186be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
187be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._stop_recording_event.clear()
188be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if not self._record_thread:
189be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._record_thread = reraiser_thread.ReraiserThread(record_to_file)
190be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._record_thread.start()
191be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
192be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def _StopRecording(self):
193be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Finish recording logcat."""
194be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if self._record_thread:
195be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._stop_recording_event.set()
196be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._record_thread.join(timeout=self._RECORD_THREAD_JOIN_WAIT)
197be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._record_thread.ReraiseIfException()
198be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._record_thread = None
199be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
200be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def Start(self):
201be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Starts the logcat monitor.
202be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
203be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Clears the logcat if |clear| was set in |__init__|.
204be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
205be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if self._clear:
206be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._adb.Logcat(clear=True)
207be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    if not self._record_file:
208be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      self._record_file = tempfile.NamedTemporaryFile(mode='a', bufsize=1)
209be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._StartRecording()
210be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
211be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def Stop(self):
212be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Stops the logcat monitor.
213be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
214be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Stops recording the logcat. Copies currently recorded logcat to
215be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    |self._output_file|.
216be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
217be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self._StopRecording()
218be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    with self._record_file_lock:
219be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      if self._record_file and self._output_file:
220be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        try:
221be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          os.makedirs(os.path.dirname(self._output_file))
222be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        except OSError as e:
223be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik          if e.errno != errno.EEXIST:
224be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik            raise
225be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        shutil.copy(self._record_file.name, self._output_file)
226be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
227be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def Close(self):
228be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Closes logcat recording file.
229be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
230be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    Should be called when finished using the logcat monitor.
231be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """
232be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    with self._record_file_lock:
233be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      if self._record_file:
234be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        self._record_file.close()
235be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        self._record_file = None
236be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
237b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang  def close(self):
238b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang    """An alias for Close.
239b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang
240b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang    Allows LogcatMonitors to be used with contextlib.closing.
241b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang    """
242b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang    self.Close()
243b2cf025c7d5cebd43084f38c6c7ff9cc17da428aWei Wang
244be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def __enter__(self):
245be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Starts the logcat monitor."""
246be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self.Start()
247be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    return self
248be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
249be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def __exit__(self, exc_type, exc_val, exc_tb):
250be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Stops the logcat monitor."""
251be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    self.Stop()
252be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
253be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  def __del__(self):
254be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    """Closes logcat recording file in case |Close| was never called."""
255be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik    with self._record_file_lock:
256be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik      if self._record_file:
257eeb03f366d148f92337cfd7577087ade44ab9285John Reck        logger.warning(
258be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik            'Need to call |Close| on the logcat monitor when done!')
259be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik        self._record_file.close()
260be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
261a23c9e9f6fc22fe5611def685e1984062b13b560Chris Craik  @property
262a23c9e9f6fc22fe5611def685e1984062b13b560Chris Craik  def adb(self):
263a23c9e9f6fc22fe5611def685e1984062b13b560Chris Craik    return self._adb
264a23c9e9f6fc22fe5611def685e1984062b13b560Chris Craik
265be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik
266be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craikclass LogcatMonitorCommandError(device_errors.CommandFailedError):
267be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  """Exception for errors with logcat monitor commands."""
268be1f909aea58dd8b153538c9fa19cb0bf50bdb17Chris Craik  pass
269