diff --git a/tests/browser/http/test_content_disposition.py b/tests/browser/http/test_content_disposition.py index 09dc1f828..dcb5a096a 100644 --- a/tests/browser/http/test_content_disposition.py +++ b/tests/browser/http/test_content_disposition.py @@ -24,7 +24,6 @@ import logging import pytest from qutebrowser.browser import http -from qutebrowser.utils import log DEFAULT_NAME = 'qutebrowser-download' @@ -56,7 +55,7 @@ class HeaderChecker: """Check if the passed header is ignored.""" reply = self.stubs.FakeNetworkReply( headers={'Content-Disposition': header}) - with self.caplog.atLevel(logging.ERROR, logger=log.rfc6266.name): + with self.caplog.atLevel(logging.ERROR, 'rfc6266'): # with self.assertLogs(log.rfc6266, logging.ERROR): cd_inline, cd_filename = http.parse_content_disposition(reply) assert cd_filename == DEFAULT_NAME diff --git a/tests/browser/http/test_http_hypothesis.py b/tests/browser/http/test_http_hypothesis.py index 174509011..38178b392 100644 --- a/tests/browser/http/test_http_hypothesis.py +++ b/tests/browser/http/test_http_hypothesis.py @@ -19,6 +19,8 @@ """Hypothesis tests for qutebrowser.browser.http.""" +import logging + import pytest import hypothesis from hypothesis import strategies @@ -35,11 +37,12 @@ from qutebrowser.browser import http, rfc6266 'attachment; filename*={}', ]) @hypothesis.given(strategies.text(alphabet=[chr(x) for x in range(255)])) -def test_parse_content_disposition(template, stubs, s): +def test_parse_content_disposition(caplog, template, stubs, s): """Test parsing headers based on templates which hypothesis completes.""" header = template.format(s) reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) - http.parse_content_disposition(reply) + with caplog.atLevel(logging.ERROR, 'rfc6266'): + http.parse_content_disposition(reply) @hypothesis.given(strategies.binary()) diff --git a/tests/conftest.py b/tests/conftest.py index 1d6aa8b68..6c22a2cd9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,22 +23,24 @@ import os import sys import collections import itertools +import logging import pytest import stubs as stubsmod +import logfail from qutebrowser.config import configexc from qutebrowser.utils import objreg, usertypes -@pytest.fixture(scope='session', autouse=True) -def app_and_logging(qapp): - """Initialize a QApplication and logging. - This ensures that a QApplication is created and used by all tests. - """ - from log import init - init() +@pytest.yield_fixture(scope='session', autouse=True) +def fail_on_logging(): + handler = logfail.LogFailHandler() + logging.getLogger().addHandler(handler) + yield + logging.getLogger().removeHandler(handler) + handler.close() @pytest.fixture(scope='session') @@ -58,7 +60,7 @@ def unicode_encode_err(): @pytest.fixture(scope='session') -def qnam(): +def qnam(qapp): """Session-wide QNetworkAccessManager.""" from PyQt5.QtNetwork import QNetworkAccessManager nam = QNetworkAccessManager() diff --git a/tests/keyinput/test_basekeyparser.py b/tests/keyinput/test_basekeyparser.py index e8f7f9325..d068e040e 100644 --- a/tests/keyinput/test_basekeyparser.py +++ b/tests/keyinput/test_basekeyparser.py @@ -29,7 +29,6 @@ from PyQt5.QtCore import Qt import pytest from qutebrowser.keyinput import basekeyparser -from qutebrowser.utils import log CONFIG = {'input': {'timeout': 100}} @@ -107,7 +106,7 @@ class TestSpecialKeys: def setup(self, caplog, fake_keyconfig): self.kp = basekeyparser.BaseKeyParser(0) self.kp.execute = mock.Mock() - with caplog.atLevel(logging.WARNING, log.keyboard.name): + with caplog.atLevel(logging.WARNING, 'keyboard'): # Ignoring keychain 'ccc' in mode 'test' because keychains are not # supported there. self.kp.read_config('test') diff --git a/tests/log.py b/tests/log.py deleted file mode 100644 index 37148be0e..000000000 --- a/tests/log.py +++ /dev/null @@ -1,68 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2015 Florian Bruhin (The Compiler) -# -# This file is part of qutebrowser. -# -# qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# qutebrowser is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with qutebrowser. If not, see . - -"""Logging setup for the tests.""" - -import logging - -from PyQt5.QtCore import (QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg, - qInstallMessageHandler) - - -def init(): - """Initialize logging for the tests.""" - logging.basicConfig(format='\nLOG %(levelname)s %(name)s ' - '%(module)s:%(funcName)s:%(lineno)d %(message)s', - level=logging.WARNING) - logging.captureWarnings(True) - qInstallMessageHandler(qt_message_handler) - - -def qt_message_handler(msg_type, context, msg): - """Qt message handler to redirect qWarning etc. to the logging system. - - Args: - QtMsgType msg_type: The level of the message. - QMessageLogContext context: The source code location of the message. - msg: The message text. - """ - # Mapping from Qt logging levels to the matching logging module levels. - # Note we map critical to ERROR as it's actually "just" an error, and fatal - # to critical. - qt_to_logging = { - QtDebugMsg: logging.DEBUG, - QtWarningMsg: logging.WARNING, - QtCriticalMsg: logging.ERROR, - QtFatalMsg: logging.CRITICAL, - } - level = qt_to_logging[msg_type] - # There's very similar code in utils.log, but we want it duplicated here - # for the tests. - if context.function is None: - func = 'none' - else: - func = context.function - if context.category is None or context.category == 'default': - name = 'qt' - else: - name = 'qt-' + context.category - logger = logging.getLogger('qt-tests') - record = logger.makeRecord(name, level, context.file, context.line, msg, - None, None, func) - logger.handle(record) diff --git a/tests/logfail.py b/tests/logfail.py new file mode 100644 index 000000000..b4a6afda5 --- /dev/null +++ b/tests/logfail.py @@ -0,0 +1,67 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2015 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Logging handling for the tests.""" + +import logging + +import pytest + +try: + import pytest_capturelog +except ImportError: + # When using pytest for pyflakes/pep8/..., the plugin won't be available + # but conftest.py will still be loaded. + # + # However, LogFailHandler.emit will never be used in that case, so we just + # ignore the ImportError. + pass + + +class LogFailHandler(logging.Handler): + + """A logging handler which makes tests fail on unexpected messages.""" + + def __init__(self, level=logging.NOTSET, min_level=logging.WARNING): + self._min_level = min_level + super().__init__(level) + + def emit(self, record): + logger = logging.getLogger(record.name) + root_logger = logging.getLogger() + + for h in root_logger.handlers: + if isinstance(h, pytest_capturelog.CaptureLogHandler): + caplog_handler = h + break + else: + # The CaptureLogHandler is not available anymore during fixture + # teardown, so we ignore logging messages emitted there.. + return + + if (logger.level == record.levelno or + caplog_handler.level == record.levelno): + # caplog.atLevel(...) was used with the level of this message, i.e. + # it was expected. + return + if record.levelno < self._min_level: + return + pytest.fail("Got logging message on logger {} with level {}: " + "{}!".format(record.name, record.levelname, + record.getMessage())) diff --git a/tests/misc/test_editor.py b/tests/misc/test_editor.py index 773769f80..586bbc2c9 100644 --- a/tests/misc/test_editor.py +++ b/tests/misc/test_editor.py @@ -234,13 +234,13 @@ class TestErrorMessage: def test_proc_error(self, caplog): """Test on_proc_error.""" self.editor.edit("") - with caplog.atLevel(logging.ERROR, 'message'): + with caplog.atLevel(logging.ERROR): self.editor.on_proc_error(QProcess.Crashed) assert len(caplog.records()) == 2 def test_proc_return(self, caplog): """Test on_proc_finished with a bad exit status.""" self.editor.edit("") - with caplog.atLevel(logging.ERROR, 'message'): + with caplog.atLevel(logging.ERROR): self.editor.on_proc_closed(1, QProcess.NormalExit) - assert len(caplog.records()) == 3 + assert len(caplog.records()) == 2 diff --git a/tests/misc/test_guiprocess.py b/tests/misc/test_guiprocess.py index 99788c39a..ac0d40405 100644 --- a/tests/misc/test_guiprocess.py +++ b/tests/misc/test_guiprocess.py @@ -23,6 +23,7 @@ import sys import textwrap +import logging import pytest from PyQt5.QtCore import QProcess @@ -117,10 +118,11 @@ def test_cmd_args(fake_proc): assert (fake_proc.cmd, fake_proc.args) == (cmd, args) -def test_error(qtbot, proc): +def test_error(qtbot, proc, caplog): """Test the process emitting an error.""" - with qtbot.waitSignal(proc.error, raising=True): - proc.start('this_does_not_exist_either', []) + with caplog.atLevel(logging.ERROR, 'message'): + with qtbot.waitSignal(proc.error, raising=True): + proc.start('this_does_not_exist_either', []) @pytest.mark.not_frozen diff --git a/tests/test_logfail.py b/tests/test_logfail.py new file mode 100644 index 000000000..b869c8cae --- /dev/null +++ b/tests/test_logfail.py @@ -0,0 +1,65 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2015 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Tests for the LogFailHandler test helper.""" + + +import logging + +import pytest + + +def test_log_debug(): + logging.debug('foo') + + +def test_log_warning(): + with pytest.raises(pytest.fail.Exception): + logging.warning('foo') + + +def test_log_expected(caplog): + with caplog.atLevel(logging.ERROR): + logging.error('foo') + + +def test_log_expected_logger(caplog): + logger = 'logfail_test_logger' + with caplog.atLevel(logging.ERROR, logger): + logging.getLogger(logger).error('foo') + + +def test_log_expected_wrong_level(caplog): + with pytest.raises(pytest.fail.Exception): + with caplog.atLevel(logging.ERROR): + logging.critical('foo') + + +def test_log_expected_logger_wrong_level(caplog): + logger = 'logfail_test_logger' + with pytest.raises(pytest.fail.Exception): + with caplog.atLevel(logging.ERROR, logger): + logging.getLogger(logger).critical('foo') + + +def test_log_expected_wrong_logger(caplog): + logger = 'logfail_test_logger' + with pytest.raises(pytest.fail.Exception): + with caplog.atLevel(logging.ERROR, logger): + logging.error('foo') diff --git a/tests/utils/debug/test_log_time.py b/tests/utils/debug/test_log_time.py index 3769cb6a2..95bafaf92 100644 --- a/tests/utils/debug/test_log_time.py +++ b/tests/utils/debug/test_log_time.py @@ -30,7 +30,7 @@ def test_log_time(caplog): """Test if log_time logs properly.""" logger_name = 'qt-tests' - with caplog.atLevel(logging.DEBUG, logger=logger_name): + with caplog.atLevel(logging.DEBUG, logger_name): with debug.log_time(logging.getLogger(logger_name), action='foobar'): time.sleep(0.1) diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py index c9786f9e0..790c20fe0 100644 --- a/tests/utils/test_log.py +++ b/tests/utils/test_log.py @@ -238,8 +238,8 @@ class TestHideQtWarning: def test_unfiltered(self, logger, caplog): """Test a message which is not filtered.""" - with log.hide_qt_warning("World", logger='qt-tests'): - with caplog.atLevel(logging.WARNING, logger='qt-tests'): + with log.hide_qt_warning("World", 'qt-tests'): + with caplog.atLevel(logging.WARNING, 'qt-tests'): logger.warning("Hello World") assert len(caplog.records()) == 1 record = caplog.records()[0] @@ -248,21 +248,21 @@ class TestHideQtWarning: def test_filtered_exact(self, logger, caplog): """Test a message which is filtered (exact match).""" - with log.hide_qt_warning("Hello", logger='qt-tests'): - with caplog.atLevel(logging.WARNING, logger='qt-tests'): + with log.hide_qt_warning("Hello", 'qt-tests'): + with caplog.atLevel(logging.WARNING, 'qt-tests'): logger.warning("Hello") assert not caplog.records() def test_filtered_start(self, logger, caplog): """Test a message which is filtered (match at line start).""" - with log.hide_qt_warning("Hello", logger='qt-tests'): - with caplog.atLevel(logging.WARNING, logger='qt-tests'): + with log.hide_qt_warning("Hello", 'qt-tests'): + with caplog.atLevel(logging.WARNING, 'qt-tests'): logger.warning("Hello World") assert not caplog.records() def test_filtered_whitespace(self, logger, caplog): """Test a message which is filtered (match with whitespace).""" - with log.hide_qt_warning("Hello", logger='qt-tests'): - with caplog.atLevel(logging.WARNING, logger='qt-tests'): + with log.hide_qt_warning("Hello", 'qt-tests'): + with caplog.atLevel(logging.WARNING, 'qt-tests'): logger.warning(" Hello World ") assert not caplog.records() diff --git a/tests/utils/test_standarddir.py b/tests/utils/test_standarddir.py index 3df3ebf88..704c57c0f 100644 --- a/tests/utils/test_standarddir.py +++ b/tests/utils/test_standarddir.py @@ -271,7 +271,7 @@ class TestInitCacheDirTag: monkeypatch.setattr('qutebrowser.utils.standarddir.cache', lambda: str(tmpdir)) mocker.patch('builtins.open', side_effect=OSError) - with caplog.atLevel(logging.ERROR, 'misc'): + with caplog.atLevel(logging.ERROR, 'init'): standarddir._init_cachedir_tag() assert len(caplog.records()) == 1 assert caplog.records()[0].message == 'Failed to create CACHEDIR.TAG' diff --git a/tests/utils/test_version.py b/tests/utils/test_version.py index f59b6339a..3804c2784 100644 --- a/tests/utils/test_version.py +++ b/tests/utils/test_version.py @@ -105,12 +105,13 @@ class TestGitStr: commit_file_mock.return_value = 'deadbeef' assert version._git_str() == 'deadbeef' - def test_frozen_oserror(self, commit_file_mock, monkeypatch): + def test_frozen_oserror(self, caplog, commit_file_mock, monkeypatch): """Test with sys.frozen=True and OSError when reading git-commit-id.""" monkeypatch.setattr(qutebrowser.utils.version.sys, 'frozen', True, raising=False) commit_file_mock.side_effect = OSError - assert version._git_str() is None + with caplog.atLevel(logging.ERROR, 'misc'): + assert version._git_str() is None @pytest.mark.not_frozen def test_normal_successful(self, git_str_subprocess_fake): @@ -130,13 +131,15 @@ class TestGitStr: commit_file_mock.return_value = '1b4d1dea' assert version._git_str() == '1b4d1dea' - def test_normal_path_oserror(self, mocker, git_str_subprocess_fake): + def test_normal_path_oserror(self, mocker, git_str_subprocess_fake, + caplog): """Test with things raising OSError.""" m = mocker.patch('qutebrowser.utils.version.os') m.path.join.side_effect = OSError mocker.patch('qutebrowser.utils.version.utils.read_file', side_effect=OSError) - assert version._git_str() is None + with caplog.atLevel(logging.ERROR, 'misc'): + assert version._git_str() is None @pytest.mark.not_frozen def test_normal_path_nofile(self, monkeypatch, caplog, diff --git a/tests/utils/usertypes/test_question.py b/tests/utils/usertypes/test_question.py index b63106513..b5977cc40 100644 --- a/tests/utils/usertypes/test_question.py +++ b/tests/utils/usertypes/test_question.py @@ -86,6 +86,6 @@ def test_abort_typeerror(question, qtbot, mocker, caplog): """Test Question.abort() with .emit() raising a TypeError.""" signal_mock = mocker.patch('qutebrowser.utils.usertypes.Question.aborted') signal_mock.emit.side_effect = TypeError - with caplog.atLevel(logging.ERROR): + with caplog.atLevel(logging.ERROR, 'misc'): question.abort() assert caplog.records()[0].message == 'Error while aborting question'