diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 7db0e56f8..0d11efffa 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -47,12 +47,12 @@ class GUIProcess(QObject): Args: cmd: The command which was started. args: A list of arguments which gets passed. + verbose: Whether to show more messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _win_id: The window ID this process is used in. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. - _verbose: Whether to show more messages. Signals: error/finished/started signals proxied from QProcess. @@ -67,7 +67,7 @@ class GUIProcess(QObject): super().__init__(parent) self._win_id = win_id self._what = what - self._verbose = verbose + self.verbose = verbose self._started = False self.cmd = None self.args = None @@ -104,7 +104,7 @@ class GUIProcess(QObject): "{} crashed!".format(self._what.capitalize()), immediately=True) elif status == QProcess.NormalExit and code == 0: - if self._verbose: + if self.verbose: message.info(self._win_id, "{} exited successfully.".format( self._what.capitalize())) else: @@ -125,7 +125,7 @@ class GUIProcess(QObject): raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args - if self._verbose: + if self.verbose: fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) message.info(self._win_id, 'Executing: ' + fake_cmdline) diff --git a/tests/helpers/messagemock.py b/tests/helpers/messagemock.py index 9bcfc1e1c..ede0fa10d 100644 --- a/tests/helpers/messagemock.py +++ b/tests/helpers/messagemock.py @@ -19,43 +19,58 @@ """pytest helper to monkeypatch the message module.""" -import pytest - +import logging import collections +import pytest + from qutebrowser.utils import usertypes +Message = collections.namedtuple('Message', ['level', 'win_id', 'text', + 'immediate']) + + +Level = usertypes.enum('Level', ('error', 'info', 'warning')) + + class MessageMock: """Helper object for message_mock. Attributes: _monkeypatch: The pytest monkeypatch fixture. - MessageLevel: An enum with possible message levels. Message: A namedtuple representing a message. messages: A list of Message tuples. + caplog: The pytest-capturelog fixture. """ - Message = collections.namedtuple('Message', ['level', 'win_id', 'text', - 'immediate']) - MessageLevel = usertypes.enum('Level', ('error', 'info', 'warning')) - - def __init__(self, monkeypatch): + def __init__(self, monkeypatch, caplog): self._monkeypatch = monkeypatch + self._caplog = caplog self.messages = [] 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): - self._handle(self.MessageLevel.error, *args, **kwargs) + self._handle(Level.error, *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): - self._handle(self.MessageLevel.warning, *args, **kwargs) + self._handle(Level.warning, *args, **kwargs) def getmsg(self): """Get the only message in self.messages. @@ -76,6 +91,6 @@ class MessageMock: @pytest.fixture -def message_mock(monkeypatch): +def message_mock(monkeypatch, caplog): """Fixture to get a MessageMock.""" - return MessageMock(monkeypatch) + return MessageMock(monkeypatch, caplog) diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 26aa05745..cf0ee05a9 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -21,8 +21,6 @@ """Fake objects/stubs.""" -import logging - from unittest import mock from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject @@ -276,25 +274,6 @@ class FakeTimer(QObject): 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: """Stub for the config module. diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 274e720d2..a1f08babd 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -96,9 +96,8 @@ class TestFileHandling: """ @pytest.fixture(autouse=True) - def setup(self, monkeypatch, stubs, config_stub): - monkeypatch.setattr('qutebrowser.misc.editor.message', - stubs.MessageModule()) + def setup(self, monkeypatch, stubs, config_stub, message_mock): + message_mock.patch('qutebrowser.misc.editor.message') monkeypatch.setattr('qutebrowser.misc.editor.guiprocess.QProcess', stubs.fake_qprocess()) config_stub.data = {'general': {'editor': [''], @@ -217,11 +216,10 @@ class TestErrorMessage: """ @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', stubs.fake_qprocess()) - monkeypatch.setattr('qutebrowser.misc.editor.message', - stubs.MessageModule()) + message_mock.patch('qutebrowser.misc.editor.message') config_stub.data = {'general': {'editor': [''], 'editor-encoding': 'utf-8'}} monkeypatch.setattr('qutebrowser.misc.editor.config', config_stub) diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 5f78c0d7a..956a1d7af 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -27,6 +27,7 @@ import logging import pytest from PyQt5.QtCore import QProcess +from helpers import messagemock # pylint: disable=import-error from qutebrowser.misc import guiprocess @@ -39,9 +40,9 @@ def _py_proc(code): @pytest.fixture(autouse=True) -def mock_modules(monkeypatch, stubs): - monkeypatch.setattr('qutebrowser.misc.guiprocess.message', - stubs.MessageModule()) +def guiprocess_message_mock(message_mock): + message_mock.patch('qutebrowser.misc.guiprocess.message') + return message_mock @pytest.yield_fixture() @@ -68,13 +69,32 @@ def fake_proc(monkeypatch, stubs): @pytest.mark.not_frozen -def test_start(proc, qtbot): +def test_start(proc, qtbot, guiprocess_message_mock): """Test simply starting a process.""" with qtbot.waitSignals([proc.started, proc.finished], raising=True, timeout=10000): argv = _py_proc("import sys; print('test'); sys.exit(0)") 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' @@ -120,14 +140,23 @@ def test_cmd_args(fake_proc): 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.""" with caplog.atLevel(logging.ERROR, 'message'): with qtbot.waitSignal(proc.error, raising=True): 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 -def test_exit_unsuccessful(qtbot, proc): +def test_exit_unsuccessful(qtbot, proc, guiprocess_message_mock): 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."