tests: Improve MessageMock and use it.

This commit is contained in:
Florian Bruhin 2015-08-18 21:28:22 +02:00
parent 6d1b0ba260
commit e4a0f1972f
5 changed files with 73 additions and 52 deletions

View File

@ -47,12 +47,12 @@ class GUIProcess(QObject):
Args: Args:
cmd: The command which was started. cmd: The command which was started.
args: A list of arguments which gets passed. args: A list of arguments which gets passed.
verbose: Whether to show more messages.
_started: Whether the underlying process is started. _started: Whether the underlying process is started.
_proc: The underlying QProcess. _proc: The underlying QProcess.
_win_id: The window ID this process is used in. _win_id: The window ID this process is used in.
_what: What kind of thing is spawned (process/editor/userscript/...). _what: What kind of thing is spawned (process/editor/userscript/...).
Used in messages. Used in messages.
_verbose: Whether to show more messages.
Signals: Signals:
error/finished/started signals proxied from QProcess. error/finished/started signals proxied from QProcess.
@ -67,7 +67,7 @@ class GUIProcess(QObject):
super().__init__(parent) super().__init__(parent)
self._win_id = win_id self._win_id = win_id
self._what = what self._what = what
self._verbose = verbose self.verbose = verbose
self._started = False self._started = False
self.cmd = None self.cmd = None
self.args = None self.args = None
@ -104,7 +104,7 @@ class GUIProcess(QObject):
"{} crashed!".format(self._what.capitalize()), "{} crashed!".format(self._what.capitalize()),
immediately=True) immediately=True)
elif status == QProcess.NormalExit and code == 0: elif status == QProcess.NormalExit and code == 0:
if self._verbose: if self.verbose:
message.info(self._win_id, "{} exited successfully.".format( message.info(self._win_id, "{} exited successfully.".format(
self._what.capitalize())) self._what.capitalize()))
else: else:
@ -125,7 +125,7 @@ class GUIProcess(QObject):
raise ValueError("Trying to start a running QProcess!") raise ValueError("Trying to start a running QProcess!")
self.cmd = cmd self.cmd = cmd
self.args = args self.args = args
if self._verbose: if self.verbose:
fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args))
message.info(self._win_id, 'Executing: ' + fake_cmdline) message.info(self._win_id, 'Executing: ' + fake_cmdline)

View File

@ -19,43 +19,58 @@
"""pytest helper to monkeypatch the message module.""" """pytest helper to monkeypatch the message module."""
import pytest import logging
import collections import collections
import pytest
from qutebrowser.utils import usertypes from qutebrowser.utils import usertypes
Message = collections.namedtuple('Message', ['level', 'win_id', 'text',
'immediate'])
Level = usertypes.enum('Level', ('error', 'info', 'warning'))
class MessageMock: class MessageMock:
"""Helper object for message_mock. """Helper object for message_mock.
Attributes: Attributes:
_monkeypatch: The pytest monkeypatch fixture. _monkeypatch: The pytest monkeypatch fixture.
MessageLevel: An enum with possible message levels.
Message: A namedtuple representing a message. Message: A namedtuple representing a message.
messages: A list of Message tuples. messages: A list of Message tuples.
caplog: The pytest-capturelog fixture.
""" """
Message = collections.namedtuple('Message', ['level', 'win_id', 'text', def __init__(self, monkeypatch, caplog):
'immediate'])
MessageLevel = usertypes.enum('Level', ('error', 'info', 'warning'))
def __init__(self, monkeypatch):
self._monkeypatch = monkeypatch self._monkeypatch = monkeypatch
self._caplog = caplog
self.messages = [] self.messages = []
def _handle(self, level, win_id, text, immediately=False): def _handle(self, level, win_id, text, immediately=False):
self.messages.append(self.Message(level, win_id, text, immediately)) log_levels = {
Level.error: logging.ERROR,
Level.info: logging.INFO,
Level.warning: logging.WARNING
}
log_level = log_levels[level]
with self._caplog.atLevel(log_level): # needed so we don't fail
logging.getLogger('message').log(log_level, text)
self.messages.append(Message(level, win_id, text, immediately))
def _handle_error(self, *args, **kwargs): def _handle_error(self, *args, **kwargs):
self._handle(self.MessageLevel.error, *args, **kwargs) self._handle(Level.error, *args, **kwargs)
def _handle_info(self, *args, **kwargs): def _handle_info(self, *args, **kwargs):
self._handle(self.MessageLevel.info, *args, **kwargs) self._handle(Level.info, *args, **kwargs)
def _handle_warning(self, *args, **kwargs): def _handle_warning(self, *args, **kwargs):
self._handle(self.MessageLevel.warning, *args, **kwargs) self._handle(Level.warning, *args, **kwargs)
def getmsg(self): def getmsg(self):
"""Get the only message in self.messages. """Get the only message in self.messages.
@ -76,6 +91,6 @@ class MessageMock:
@pytest.fixture @pytest.fixture
def message_mock(monkeypatch): def message_mock(monkeypatch, caplog):
"""Fixture to get a MessageMock.""" """Fixture to get a MessageMock."""
return MessageMock(monkeypatch) return MessageMock(monkeypatch, caplog)

View File

@ -21,8 +21,6 @@
"""Fake objects/stubs.""" """Fake objects/stubs."""
import logging
from unittest import mock from unittest import mock
from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject
@ -276,25 +274,6 @@ class FakeTimer(QObject):
return self._started return self._started
class MessageModule:
"""A drop-in replacement for qutebrowser.utils.message."""
# pylint: disable=unused-argument
def error(self, _win_id, message, immediately=False):
"""Log an error to the message logger."""
logging.getLogger('message').error(message)
def warning(self, _win_id, message, immediately=False):
"""Log a warning to the message logger."""
logging.getLogger('message').warning(message)
def info(self, _win_id, message, immediately=True):
"""Log an info message to the message logger."""
logging.getLogger('message').info(message)
class ConfigStub: class ConfigStub:
"""Stub for the config module. """Stub for the config module.

View File

@ -96,9 +96,8 @@ class TestFileHandling:
""" """
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup(self, monkeypatch, stubs, config_stub): def setup(self, monkeypatch, stubs, config_stub, message_mock):
monkeypatch.setattr('qutebrowser.misc.editor.message', message_mock.patch('qutebrowser.misc.editor.message')
stubs.MessageModule())
monkeypatch.setattr('qutebrowser.misc.editor.guiprocess.QProcess', monkeypatch.setattr('qutebrowser.misc.editor.guiprocess.QProcess',
stubs.fake_qprocess()) stubs.fake_qprocess())
config_stub.data = {'general': {'editor': [''], config_stub.data = {'general': {'editor': [''],
@ -217,11 +216,10 @@ class TestErrorMessage:
""" """
@pytest.yield_fixture(autouse=True) @pytest.yield_fixture(autouse=True)
def setup(self, monkeypatch, stubs, config_stub): def setup(self, monkeypatch, stubs, config_stub, message_mock):
monkeypatch.setattr('qutebrowser.misc.editor.guiprocess.QProcess', monkeypatch.setattr('qutebrowser.misc.editor.guiprocess.QProcess',
stubs.fake_qprocess()) stubs.fake_qprocess())
monkeypatch.setattr('qutebrowser.misc.editor.message', message_mock.patch('qutebrowser.misc.editor.message')
stubs.MessageModule())
config_stub.data = {'general': {'editor': [''], config_stub.data = {'general': {'editor': [''],
'editor-encoding': 'utf-8'}} 'editor-encoding': 'utf-8'}}
monkeypatch.setattr('qutebrowser.misc.editor.config', config_stub) monkeypatch.setattr('qutebrowser.misc.editor.config', config_stub)

View File

@ -27,6 +27,7 @@ import logging
import pytest import pytest
from PyQt5.QtCore import QProcess from PyQt5.QtCore import QProcess
from helpers import messagemock # pylint: disable=import-error
from qutebrowser.misc import guiprocess from qutebrowser.misc import guiprocess
@ -39,9 +40,9 @@ def _py_proc(code):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_modules(monkeypatch, stubs): def guiprocess_message_mock(message_mock):
monkeypatch.setattr('qutebrowser.misc.guiprocess.message', message_mock.patch('qutebrowser.misc.guiprocess.message')
stubs.MessageModule()) return message_mock
@pytest.yield_fixture() @pytest.yield_fixture()
@ -68,13 +69,32 @@ def fake_proc(monkeypatch, stubs):
@pytest.mark.not_frozen @pytest.mark.not_frozen
def test_start(proc, qtbot): def test_start(proc, qtbot, guiprocess_message_mock):
"""Test simply starting a process.""" """Test simply starting a process."""
with qtbot.waitSignals([proc.started, proc.finished], raising=True, with qtbot.waitSignals([proc.started, proc.finished], raising=True,
timeout=10000): timeout=10000):
argv = _py_proc("import sys; print('test'); sys.exit(0)") argv = _py_proc("import sys; print('test'); sys.exit(0)")
proc.start(*argv) proc.start(*argv)
assert not guiprocess_message_mock.messages
assert bytes(proc._proc.readAll()).rstrip() == b'test'
@pytest.mark.not_frozen
def test_start_verbose(proc, qtbot, guiprocess_message_mock):
"""Test starting a process verbosely."""
proc.verbose = True
with qtbot.waitSignals([proc.started, proc.finished], raising=True,
timeout=10000):
argv = _py_proc("import sys; print('test'); sys.exit(0)")
proc.start(*argv)
msgs = guiprocess_message_mock.messages
assert msgs[0].level == messagemock.Level.info
assert msgs[1].level == messagemock.Level.info
assert msgs[0].text.startswith("Executing:")
assert msgs[1].text == "Test exited successfully."
assert bytes(proc._proc.readAll()).rstrip() == b'test' assert bytes(proc._proc.readAll()).rstrip() == b'test'
@ -120,14 +140,23 @@ def test_cmd_args(fake_proc):
assert (fake_proc.cmd, fake_proc.args) == (cmd, args) assert (fake_proc.cmd, fake_proc.args) == (cmd, args)
def test_error(qtbot, proc, caplog): def test_error(qtbot, proc, caplog, guiprocess_message_mock):
"""Test the process emitting an error.""" """Test the process emitting an error."""
with caplog.atLevel(logging.ERROR, 'message'): with caplog.atLevel(logging.ERROR, 'message'):
with qtbot.waitSignal(proc.error, raising=True): with qtbot.waitSignal(proc.error, raising=True):
proc.start('this_does_not_exist_either', []) proc.start('this_does_not_exist_either', [])
msg = guiprocess_message_mock.getmsg()
assert msg.level == messagemock.Level.error
expected_msg = "Error while spawning test: The process failed to start."
assert msg.text == expected_msg
@pytest.mark.not_frozen @pytest.mark.not_frozen
def test_exit_unsuccessful(qtbot, proc): def test_exit_unsuccessful(qtbot, proc, guiprocess_message_mock):
with qtbot.waitSignal(proc.finished, raising=True, timeout=10000): with qtbot.waitSignal(proc.finished, raising=True, timeout=10000):
proc.start(*_py_proc('import sys; sys.exit(0)')) proc.start(*_py_proc('import sys; sys.exit(1)'))
msg = guiprocess_message_mock.getmsg()
assert msg.level == messagemock.Level.error
assert msg.text == "Test exited with status 1."