1
0
mirror of https://github.com/ARM-software/devlib.git synced 2024-10-06 10:50:51 +01:00

Merge pull request #190 from bjackman/logcat-pexpect

android: Use pexpect for LogcatMonitor
This commit is contained in:
setrofim 2017-10-12 13:43:45 +01:00 committed by GitHub
commit 5421ddaae8

View File

@ -20,6 +20,7 @@ Utility functions for working with Android devices through adb.
""" """
# pylint: disable=E1103 # pylint: disable=E1103
import os import os
import pexpect
import time import time
import subprocess import subprocess
import logging import logging
@ -442,13 +443,16 @@ def adb_list_devices(adb_server=None):
return devices return devices
def adb_command(device, command, timeout=None,adb_server=None): def get_adb_command(device, command, timeout=None,adb_server=None):
_check_env() _check_env()
device_string = "" device_string = ""
if adb_server != None: if adb_server != None:
device_string = ' -H {}'.format(adb_server) device_string = ' -H {}'.format(adb_server)
device_string += ' -s {}'.format(device) if device else '' device_string += ' -s {}'.format(device) if device else ''
full_command = "adb{} {}".format(device_string, command) return "adb{} {}".format(device_string, command)
def adb_command(device, command, timeout=None,adb_server=None):
full_command = get_adb_command(device, command, timeout, adb_server)
logger.debug(full_command) logger.debug(full_command)
output, _ = check_output(full_command, timeout, shell=True) output, _ = check_output(full_command, timeout, shell=True)
return output return output
@ -540,20 +544,19 @@ def _check_env():
adb = _env.adb adb = _env.adb
aapt = _env.aapt aapt = _env.aapt
class LogcatMonitor(threading.Thread): class LogcatMonitor(object):
""" """
Helper class for monitoring Anroid's logcat Helper class for monitoring Anroid's logcat
:param target: Android target to monitor :param target: Android target to monitor
:type target: :class:`AndroidTarget` :type target: :class:`AndroidTarget`
:param regexps: List of uncompiled regular expressions to filter on the
device. Logcat entries that don't match any will not be device. Logcat entries that don't match any will not be
seen. If omitted, all entries will be sent to host. seen. If omitted, all entries will be sent to host.
:type regexps: list(str) :type regexps: list(str)
""" """
FLUSH_SIZE = 1000
@property @property
def logfile(self): def logfile(self):
return self._logfile return self._logfile
@ -562,16 +565,6 @@ class LogcatMonitor(threading.Thread):
super(LogcatMonitor, self).__init__() super(LogcatMonitor, self).__init__()
self.target = target self.target = target
self._started = threading.Event()
self._stopped = threading.Event()
self._match_found = threading.Event()
self._sought = None
self._found = None
self._lines = Queue.Queue()
self._datalock = threading.Lock()
self._regexps = regexps self._regexps = regexps
def start(self, outfile=None): def start(self, outfile=None):
@ -582,15 +575,10 @@ class LogcatMonitor(threading.Thread):
:type outfile: str :type outfile: str
""" """
if outfile: if outfile:
self._logfile = outfile self._logfile = open(outfile)
else: else:
fd, self._logfile = tempfile.mkstemp() self._logfile = tempfile.NamedTemporaryFile()
os.close(fd)
logger.debug('Logging to {}'.format(self._logfile))
super(LogcatMonitor, self).start()
def run(self):
self.target.clear_logcat() self.target.clear_logcat()
logcat_cmd = 'logcat' logcat_cmd = 'logcat'
@ -602,86 +590,32 @@ class LogcatMonitor(threading.Thread):
regexp = '({})'.format(regexp) regexp = '({})'.format(regexp)
logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp) logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp)
logcat_cmd = get_adb_command(self.target.conn.device, logcat_cmd)
logger.debug('logcat command ="{}"'.format(logcat_cmd)) logger.debug('logcat command ="{}"'.format(logcat_cmd))
self._logcat = self.target.background(logcat_cmd) self._logcat = pexpect.spawn(logcat_cmd, logfile=self._logfile)
self._started.set()
while not self._stopped.is_set():
line = self._logcat.stdout.readline(1024)
if line:
self._add_line(line)
def stop(self): def stop(self):
if not self.is_alive(): self._logcat.terminate()
logger.warning('LogcatMonitor.stop called before start') self._logfile.close()
return
# Make sure we've started before we try to kill anything
self._started.wait()
# Kill the underlying logcat process
# This will unblock self._logcat.stdout.readline()
host.kill_children(self._logcat.pid)
self._logcat.kill()
self._stopped.set()
self.join()
self._flush_lines()
def _add_line(self, line):
self._lines.put(line)
if self._sought and re.match(self._sought, line):
self._found = line
self._match_found.set()
if self._lines.qsize() >= self.FLUSH_SIZE:
self._flush_lines()
def _flush_lines(self):
with self._datalock:
with open(self._logfile, 'a') as fh:
while not self._lines.empty():
fh.write(self._lines.get())
def clear_log(self):
with self._datalock:
while not self._lines.empty():
self._lines.get()
with open(self._logfile, 'w') as fh:
pass
def get_log(self): def get_log(self):
""" """
Return the list of lines found by the monitor Return the list of lines found by the monitor
""" """
self._flush_lines() with open(self._logfile.name) as fh:
return [line for line in fh]
with self._datalock: def clear_log(self):
with open(self._logfile, 'r') as fh: with open(self._logfile, 'w') as fh:
res = [line for line in fh] pass
return res
def search(self, regexp): def search(self, regexp):
""" """
Search a line that matches a regexp in the logcat log Search a line that matches a regexp in the logcat log
Return immediatly Return immediatly
""" """
res = [] return [line for line in self.get_log() if re.match(regexp, line)]
self._flush_lines()
with self._datalock:
with open(self._logfile, 'r') as fh:
for line in fh:
if re.match(regexp, line):
res.append(line)
return res
def wait_for(self, regexp, timeout=30): def wait_for(self, regexp, timeout=30):
""" """
@ -697,20 +631,21 @@ class LogcatMonitor(threading.Thread):
:returns: List of matched strings :returns: List of matched strings
""" """
res = self.search(regexp) log = self.get_log()
res = [line for line in log if re.match(regexp, line)]
# Found some matches, return them # Found some matches, return them
# Also return if thread not running if len(res) > 0:
if len(res) > 0 or not self.is_alive():
return res return res
# Did not find any match, wait for one to pop up # Store the number of lines we've searched already, so we don't have to
self._sought = regexp # re-grep them after 'expect' returns
found = self._match_found.wait(timeout) next_line_num = len(log)
self._match_found.clear()
self._sought = None
if found: try:
return [self._found] self._logcat.expect(regexp, timeout=timeout)
else: except pexpect.TIMEOUT:
raise RuntimeError('Logcat monitor timeout ({}s)'.format(timeout)) raise RuntimeError('Logcat monitor timeout ({}s)'.format(timeout))
return [line for line in self.get_log()[next_line_num:]
if re.match(regexp, line)]