Add testprocess.wait_for to react to messages.

This commit is contained in:
Florian Bruhin 2015-10-22 22:23:55 +02:00
parent 70decdc2c8
commit dfc1782bbf
2 changed files with 74 additions and 28 deletions

View File

@ -61,12 +61,16 @@ class LogLine:
""", re.VERBOSE)
def __init__(self, line):
self._line = line
match = self.LOG_RE.match(line)
if match is None:
raise NoLineMatch(line)
self.__dict__.update(match.groupdict())
self.expected = False
def __repr__(self):
return 'LogLine({!r})'.format(self._line)
class QuteProc(testprocess.Process):
@ -77,9 +81,6 @@ class QuteProc(testprocess.Process):
_httpbin: The HTTPBin webserver.
"""
executing_command = pyqtSignal()
setting_done = pyqtSignal()
url_loaded = pyqtSignal()
got_error = pyqtSignal()
def __init__(self, httpbin, parent=None):
@ -107,26 +108,12 @@ class QuteProc(testprocess.Process):
"<qutebrowser.browser.webview.WebView tab_id=0 "
"url='about:blank'>: LoadStatus.success")
url_loaded_pattern = re.compile(
r"load status for <qutebrowser.browser.webview.WebView tab_id=\d+ "
r"url='[^']+'>: LoadStatus.success")
if (log_line.category == 'ipc' and
log_line.message.startswith("Listening as ")):
self._ipc_socket = log_line.message.split(' ', maxsplit=2)[2]
elif (log_line.category == 'webview' and
log_line.message == start_okay_message):
self.ready.emit()
elif (log_line.category == 'commands' and
log_line.module == 'command' and log_line.function == 'run' and
log_line.message.startswith('Calling ')):
self.executing_command.emit()
elif (log_line.category == 'config' and log_line.message.startswith(
'Config option changed: ')):
self.setting_done.emit()
elif (log_line.category == 'webview' and
url_loaded_pattern.match(log_line.message)):
self.url_loaded.emit()
elif log_line.loglevel in ['WARNING', 'ERROR']:
self.got_error.emit()
@ -156,23 +143,29 @@ class QuteProc(testprocess.Process):
def send_cmd(self, command):
assert self._ipc_socket is not None
with self._wait_signal(self.executing_command):
ipc.send_to_running_instance(self._ipc_socket, [command],
target_arg='')
ipc.send_to_running_instance(self._ipc_socket, [command],
target_arg='')
self.wait_for(category='commands', module='command', function='run',
message='Calling *')
# Wait a bit in cause the command triggers any error.
time.sleep(0.5)
def set_setting(self, sect, opt, value):
with self._wait_signal(self.setting_done):
self.send_cmd(':set "{}" "{}" "{}"'.format(sect, opt, value))
self.send_cmd(':set "{}" "{}" "{}"'.format(sect, opt, value))
self.wait_for(category='config', message='Config option changed: *')
def open_path(self, path, new_tab=False):
url_loaded_pattern = re.compile(
r"load status for <qutebrowser.browser.webview.WebView tab_id=\d+ "
r"url='[^']+'>: LoadStatus.success")
url = 'http://localhost:{}/{}'.format(self._httpbin.port, path)
with self._wait_signal(self.url_loaded):
if new_tab:
self.send_cmd(':open -t ' + url)
else:
self.send_cmd(':open ' + url)
if new_tab:
self.send_cmd(':open -t ' + url)
else:
self.send_cmd(':open ' + url)
self.wait_for(category='webview', message=url_loaded_pattern)
def mark_expected(self, category=None, loglevel=None, msg=None):
"""Mark a given logging message as expected."""

View File

@ -19,8 +19,12 @@
"""Base class for a subprocess run for tests.."""
import re
import fnmatch
import pytestqt.plugin # pylint: disable=import-error
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QProcess, QObject
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QProcess, QObject, QElapsedTimer
from PyQt5.QtTest import QSignalSpy
class InvalidLine(Exception):
@ -37,6 +41,11 @@ class ProcessExited(Exception):
pass
class WaitForTimeout(Exception):
"""Raised when wait_for didn't get the expected message."""
class Process(QObject):
"""Abstraction over a running test subprocess process.
@ -147,3 +156,47 @@ class Process(QObject):
def is_running(self):
"""Check if the process is currently running."""
return self.proc.state() == QProcess.Running
def wait_for(self, timeout=5000, **kwargs):
"""Wait until a given value is found in the data.
Keyword arguments to this function get interpreted as attributes of the
searched data. Every given argument is treated as a pattern which
the attribute has to match against.
If a string is passed, the patterns are treated as a fnmatch glob
pattern. Alternatively, a compiled regex can be passed to match against
that.
"""
# FIXME make this a context manager which inserts a marker in
# self._data in __enter__ and checks if the signal already did arrive
# after marker in __exit__, and if not, waits?
regex_type = type(re.compile(''))
spy = QSignalSpy(self.new_data)
elapsed_timer = QElapsedTimer()
elapsed_timer.start()
while True:
got_signal = spy.wait(timeout)
if not got_signal or elapsed_timer.hasExpired(timeout):
raise WaitForTimeout
for args in spy:
assert len(args) == 1
line = args[0]
matches = []
for key, expected in kwargs.items():
value = getattr(line, key)
if isinstance(expected, regex_type):
matches.append(expected.match(value))
else:
matches.append(fnmatch.fnmatchcase(value, expected))
if all(matches):
return