Merge pull request #7772 from pylbrecht/logfilter

Don't crash on --logfilter at startup
This commit is contained in:
Florian Bruhin 2023-07-21 14:52:24 +02:00 committed by GitHub
commit 1387b0598b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 369 additions and 278 deletions

View File

@ -28,7 +28,7 @@ from qutebrowser.qt.network import (QNetworkProxy, QNetworkRequest, QHostInfo,
QHostAddress)
from qutebrowser.qt.qml import QJSEngine, QJSValue
from qutebrowser.utils import log, utils, qtutils, resources, urlutils
from qutebrowser.utils import log, qtlog, utils, qtutils, resources, urlutils
class ParseProxyError(Exception):
@ -258,7 +258,7 @@ class PACFetcher(QObject):
url.setScheme(url.scheme()[len(pac_prefix):])
self._pac_url = url
with log.disable_qt_msghandler():
with qtlog.disable_qt_msghandler():
# WORKAROUND for a hang when messages are printed, see our
# NetworkAccessManager subclass for details.
self._manager: Optional[QNetworkAccessManager] = QNetworkAccessManager()

View File

@ -29,7 +29,7 @@ from qutebrowser.qt.widgets import QApplication
from qutebrowser.qt.network import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from qutebrowser.config import config, websettings
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg, qtlog
from qutebrowser.misc import quitter
from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import http
@ -121,7 +121,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
self._reply.errorOccurred.disconnect()
self._reply.readyRead.disconnect()
with log.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
with qtlog.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
'problem, this method must only be called '
'once.'):
# See https://codereview.qt-project.org/#/c/107863/

View File

@ -28,7 +28,7 @@ from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkReply, QSslCo
from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg,
urlutils, debug)
urlutils, debug, qtlog)
from qutebrowser.browser import shared
from qutebrowser.browser.network import proxy as proxymod
from qutebrowser.extensions import interceptors
@ -156,7 +156,7 @@ class NetworkManager(QNetworkAccessManager):
def __init__(self, *, win_id, tab_id, private, parent=None):
log.init.debug("Initializing NetworkManager")
with log.disable_qt_msghandler():
with qtlog.disable_qt_msghandler():
# WORKAROUND for a hang when a message is printed - See:
# https://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html
#

View File

@ -298,7 +298,17 @@ def init_log(args):
from qutebrowser.utils import log
log.init_log(args)
log.init.debug("Log initialized.")
log.init.debug(str(machinery.INFO))
def init_qtlog(args):
"""Initialize Qt logging.
Args:
args: The argparse namespace.
"""
from qutebrowser.utils import log, qtlog
qtlog.init(args)
log.init.debug("Qt log initialized.")
def check_optimize_flag():
@ -333,16 +343,18 @@ def early_init(args):
Args:
args: The argparse namespace.
"""
# Init logging as early as possible
init_log(args)
# First we initialize the faulthandler as early as possible, so we
# theoretically could catch segfaults occurring later during earlyinit.
init_faulthandler()
# Then we configure the selected Qt wrapper
info = machinery.init(args)
# Init Qt logging after machinery is initialized
init_qtlog(args)
# Here we check if QtCore is available, and if not, print a message to the
# console or via Tk.
check_qt_available(info)
# Init logging as early as possible
init_log(args)
# Now we can be sure QtCore is available, so we can print dialogs on
# errors, so people only using the GUI notice them as well.
check_libraries()

View File

@ -25,7 +25,7 @@ from qutebrowser.qt.core import pyqtSignal, QObject, QTimer
from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkRequest,
QNetworkReply)
from qutebrowser.utils import log
from qutebrowser.utils import qtlog
class HTTPRequest(QNetworkRequest):
@ -59,7 +59,7 @@ class HTTPClient(QObject):
def __init__(self, parent=None):
super().__init__(parent)
with log.disable_qt_msghandler():
with qtlog.disable_qt_msghandler():
# WORKAROUND for a hang when messages are printed, see our
# NetworkAccessManager subclass for details.
self._nam = QNetworkAccessManager(self)

View File

@ -37,7 +37,7 @@ except ImportError:
import qutebrowser
from qutebrowser.api import cmdutils
from qutebrowser.utils import log
from qutebrowser.utils import log, qtlog
from qutebrowser.misc import sessions, ipc, objects
from qutebrowser.mainwindow import prompt
from qutebrowser.completion.models import miscmodels
@ -304,5 +304,5 @@ def init(args: argparse.Namespace) -> None:
"""Initialize the global Quitter instance."""
global instance
instance = Quitter(args=args, parent=objects.qapp)
instance.shutting_down.connect(log.shutdown_log)
instance.shutting_down.connect(qtlog.shutdown_log)
objects.qapp.lastWindowClosed.connect(instance.on_last_window_closed)

View File

@ -18,6 +18,8 @@ import importlib
import dataclasses
from typing import Optional, Dict
from qutebrowser.utils import log
# Packagers: Patch the line below to change the default wrapper for Qt 6 packages, e.g.:
# sed -i 's/_DEFAULT_WRAPPER = "PyQt5"/_DEFAULT_WRAPPER = "PyQt6"/' qutebrowser/qt/machinery.py
#
@ -280,6 +282,7 @@ def init(args: argparse.Namespace) -> SelectionInfo:
info = _select_wrapper(args)
if info.wrapper is not None:
_set_globals(info)
log.init.debug(str(info))
# If info is None here (no Qt wrapper available), we'll show an error later
# in earlyinit.py.

View File

@ -24,17 +24,16 @@ import logging
import contextlib
import collections
import copy
import faulthandler
import traceback
import warnings
import json
import inspect
import argparse
from typing import (TYPE_CHECKING, Any, Iterator, Mapping, MutableSequence,
Optional, Set, Tuple, Union, TextIO, Literal, cast, Callable)
Optional, Set, Tuple, Union, TextIO, Literal, cast)
# NOTE: This is a Qt-free zone! All imports related to Qt logging should be done in
# qutebrowser.utils.qtlog (see https://github.com/qutebrowser/qutebrowser/issues/7769).
from qutebrowser.qt import core as qtcore
from qutebrowser.qt import machinery
# Optional imports
try:
import colorama
@ -209,15 +208,9 @@ def init_log(args: argparse.Namespace) -> None:
root.setLevel(logging.NOTSET)
logging.captureWarnings(True)
_init_py_warnings()
qtcore.qInstallMessageHandler(qt_message_handler)
_log_inited = True
@qtcore.pyqtSlot()
def shutdown_log() -> None:
qtcore.qInstallMessageHandler(None)
def _init_py_warnings() -> None:
"""Initialize Python warning handling."""
assert _args is not None
@ -231,29 +224,6 @@ def _init_py_warnings() -> None:
r"is deprecated.*")
@contextlib.contextmanager
def disable_qt_msghandler() -> Iterator[None]:
"""Contextmanager which temporarily disables the Qt message handler."""
old_handler = qtcore.qInstallMessageHandler(None)
if machinery.IS_QT6:
# cast str to Optional[str] to be compatible with PyQt6 type hints for
# qInstallMessageHandler
old_handler = cast(
Optional[
Callable[
[qtcore.QtMsgType, qtcore.QMessageLogContext, Optional[str]],
None
]
],
old_handler,
)
try:
yield
finally:
qtcore.qInstallMessageHandler(old_handler)
@contextlib.contextmanager
def py_warning_filter(
action:
@ -391,163 +361,6 @@ def change_console_formatter(level: int) -> None:
assert isinstance(old_formatter, JSONFormatter), old_formatter
def qt_message_handler(msg_type: qtcore.QtMsgType,
context: qtcore.QMessageLogContext,
msg: Optional[str]) -> None:
"""Qt message handler to redirect qWarning etc. to the logging system.
Args:
msg_type: The level of the message.
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 = {
qtcore.QtMsgType.QtDebugMsg: logging.DEBUG,
qtcore.QtMsgType.QtWarningMsg: logging.WARNING,
qtcore.QtMsgType.QtCriticalMsg: logging.ERROR,
qtcore.QtMsgType.QtFatalMsg: logging.CRITICAL,
qtcore.QtMsgType.QtInfoMsg: logging.INFO,
}
# Change levels of some well-known messages to debug so they don't get
# shown to the user.
#
# If a message starts with any text in suppressed_msgs, it's not logged as
# error.
suppressed_msgs = [
# PNGs in Qt with broken color profile
# https://bugreports.qt.io/browse/QTBUG-39788
('libpng warning: iCCP: Not recognizing known sRGB profile that has '
'been edited'),
'libpng warning: iCCP: known incorrect sRGB profile',
# Hopefully harmless warning
'OpenType support missing for script ',
# Error if a QNetworkReply gets two different errors set. Harmless Qt
# bug on some pages.
# https://bugreports.qt.io/browse/QTBUG-30298
('QNetworkReplyImplPrivate::error: Internal problem, this method must '
'only be called once.'),
# Sometimes indicates missing text, but most of the time harmless
'load glyph failed ',
# Harmless, see https://bugreports.qt.io/browse/QTBUG-42479
('content-type missing in HTTP POST, defaulting to '
'application/x-www-form-urlencoded. '
'Use QNetworkRequest::setHeader() to fix this problem.'),
# https://bugreports.qt.io/browse/QTBUG-43118
'Using blocking call!',
# Hopefully harmless
('"Method "GetAll" with signature "s" on interface '
'"org.freedesktop.DBus.Properties" doesn\'t exist'),
('"Method \\"GetAll\\" with signature \\"s\\" on interface '
'\\"org.freedesktop.DBus.Properties\\" doesn\'t exist\\n"'),
'WOFF support requires QtWebKit to be built with zlib support.',
# Weird Enlightment/GTK X extensions
'QXcbWindow: Unhandled client message: "_E_',
'QXcbWindow: Unhandled client message: "_ECORE_',
'QXcbWindow: Unhandled client message: "_GTK_',
# Happens on AppVeyor CI
'SetProcessDpiAwareness failed:',
# https://bugreports.qt.io/browse/QTBUG-49174
('QObject::connect: Cannot connect (null)::stateChanged('
'QNetworkSession::State) to '
'QNetworkReplyHttpImpl::_q_networkSessionStateChanged('
'QNetworkSession::State)'),
# https://bugreports.qt.io/browse/QTBUG-53989
("Image of format '' blocked because it is not considered safe. If "
"you are sure it is safe to do so, you can white-list the format by "
"setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST="),
# Installing Qt from the installer may cause it looking for SSL3 or
# OpenSSL 1.0 which may not be available on the system
"QSslSocket: cannot resolve ",
"QSslSocket: cannot call unresolved function ",
# When enabling debugging with QtWebEngine
("Remote debugging server started successfully. Try pointing a "
"Chromium-based browser to "),
# https://github.com/qutebrowser/qutebrowser/issues/1287
"QXcbClipboard: SelectionRequest too old",
# https://github.com/qutebrowser/qutebrowser/issues/2071
'QXcbWindow: Unhandled client message: ""',
# https://codereview.qt-project.org/176831
"QObject::disconnect: Unexpected null parameter",
# https://bugreports.qt.io/browse/QTBUG-76391
"Attribute Qt::AA_ShareOpenGLContexts must be set before "
"QCoreApplication is created.",
# Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
"GL format 0 is not supported",
]
# not using utils.is_mac here, because we can't be sure we can successfully
# import the utils module here.
if sys.platform == 'darwin':
suppressed_msgs += [
# https://bugreports.qt.io/browse/QTBUG-47154
('virtual void QSslSocketBackendPrivate::transmit() SSLRead '
'failed with: -9805'),
]
if not msg:
msg = "Logged empty message!"
if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs):
level = logging.DEBUG
elif context.category == "qt.webenginecontext" and (
msg.strip().startswith("GL Type: ") or # Qt 6.3
msg.strip().startswith("GLImplementation:") # Qt 6.2
):
level = logging.DEBUG
else:
level = qt_to_logging[msg_type]
if context.line is None:
lineno = -1 # type: ignore[unreachable]
else:
lineno = context.line
if context.function is None:
func = 'none' # type: ignore[unreachable]
elif ':' in context.function:
func = '"{}"'.format(context.function)
else:
func = context.function
if context.category is None or context.category == 'default':
name = 'qt'
else:
name = 'qt-' + context.category
if msg.splitlines()[0] == ('This application failed to start because it '
'could not find or load the Qt platform plugin '
'"xcb".'):
# Handle this message specially.
msg += ("\n\nOn Archlinux, this should fix the problem:\n"
" pacman -S libxkbcommon-x11")
faulthandler.disable()
assert _args is not None
if _args.debug:
stack: Optional[str] = ''.join(traceback.format_stack())
else:
stack = None
record = qt.makeRecord(name=name, level=level, fn=context.file, lno=lineno,
msg=msg, args=(), exc_info=None, func=func,
sinfo=stack)
qt.handle(record)
@contextlib.contextmanager
def hide_qt_warning(pattern: str, logger: str = 'qt') -> Iterator[None]:
"""Hide Qt warnings matching the given regex."""
log_filter = QtWarningFilter(pattern)
logger_obj = logging.getLogger(logger)
logger_obj.addFilter(log_filter)
try:
yield
finally:
logger_obj.removeFilter(log_filter)
def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
"""Initialize logging settings from the config.
@ -578,24 +391,6 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
change_console_formatter(level)
class QtWarningFilter(logging.Filter):
"""Filter to filter Qt warnings.
Attributes:
_pattern: The start of the message.
"""
def __init__(self, pattern: str) -> None:
super().__init__()
self._pattern = pattern
def filter(self, record: logging.LogRecord) -> bool:
"""Determine if the specified record is to be logged."""
do_log = not record.msg.strip().startswith(self._pattern)
return do_log
class InvalidLogFilterError(Exception):
"""Raised when an invalid filter string is passed to LogFilter.parse()."""

241
qutebrowser/utils/qtlog.py Normal file
View File

@ -0,0 +1,241 @@
# Copyright 2014-2023 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <https://www.gnu.org/licenses/>.
"""Loggers and utilities related to Qt logging."""
import argparse
import contextlib
import faulthandler
import logging
import sys
import traceback
from typing import Iterator, Optional, Callable, cast
from qutebrowser.qt import core as qtcore, machinery
from qutebrowser.utils import log
_args = None
def init(args: argparse.Namespace) -> None:
"""Install Qt message handler based on the argparse namespace passed."""
global _args
_args = args
qtcore.qInstallMessageHandler(qt_message_handler)
@qtcore.pyqtSlot()
def shutdown_log() -> None:
qtcore.qInstallMessageHandler(None)
@contextlib.contextmanager
def disable_qt_msghandler() -> Iterator[None]:
"""Contextmanager which temporarily disables the Qt message handler."""
old_handler = qtcore.qInstallMessageHandler(None)
if machinery.IS_QT6:
# cast str to Optional[str] to be compatible with PyQt6 type hints for
# qInstallMessageHandler
old_handler = cast(
Optional[
Callable[
[qtcore.QtMsgType, qtcore.QMessageLogContext, Optional[str]],
None
]
],
old_handler,
)
try:
yield
finally:
qtcore.qInstallMessageHandler(old_handler)
def qt_message_handler(msg_type: qtcore.QtMsgType,
context: qtcore.QMessageLogContext,
msg: Optional[str]) -> None:
"""Qt message handler to redirect qWarning etc. to the logging system.
Args:
msg_type: The level of the message.
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 = {
qtcore.QtMsgType.QtDebugMsg: logging.DEBUG,
qtcore.QtMsgType.QtWarningMsg: logging.WARNING,
qtcore.QtMsgType.QtCriticalMsg: logging.ERROR,
qtcore.QtMsgType.QtFatalMsg: logging.CRITICAL,
qtcore.QtMsgType.QtInfoMsg: logging.INFO,
}
# Change levels of some well-known messages to debug so they don't get
# shown to the user.
#
# If a message starts with any text in suppressed_msgs, it's not logged as
# error.
suppressed_msgs = [
# PNGs in Qt with broken color profile
# https://bugreports.qt.io/browse/QTBUG-39788
('libpng warning: iCCP: Not recognizing known sRGB profile that has '
'been edited'),
'libpng warning: iCCP: known incorrect sRGB profile',
# Hopefully harmless warning
'OpenType support missing for script ',
# Error if a QNetworkReply gets two different errors set. Harmless Qt
# bug on some pages.
# https://bugreports.qt.io/browse/QTBUG-30298
('QNetworkReplyImplPrivate::error: Internal problem, this method must '
'only be called once.'),
# Sometimes indicates missing text, but most of the time harmless
'load glyph failed ',
# Harmless, see https://bugreports.qt.io/browse/QTBUG-42479
('content-type missing in HTTP POST, defaulting to '
'application/x-www-form-urlencoded. '
'Use QNetworkRequest::setHeader() to fix this problem.'),
# https://bugreports.qt.io/browse/QTBUG-43118
'Using blocking call!',
# Hopefully harmless
('"Method "GetAll" with signature "s" on interface '
'"org.freedesktop.DBus.Properties" doesn\'t exist'),
('"Method \\"GetAll\\" with signature \\"s\\" on interface '
'\\"org.freedesktop.DBus.Properties\\" doesn\'t exist\\n"'),
'WOFF support requires QtWebKit to be built with zlib support.',
# Weird Enlightment/GTK X extensions
'QXcbWindow: Unhandled client message: "_E_',
'QXcbWindow: Unhandled client message: "_ECORE_',
'QXcbWindow: Unhandled client message: "_GTK_',
# Happens on AppVeyor CI
'SetProcessDpiAwareness failed:',
# https://bugreports.qt.io/browse/QTBUG-49174
('QObject::connect: Cannot connect (null)::stateChanged('
'QNetworkSession::State) to '
'QNetworkReplyHttpImpl::_q_networkSessionStateChanged('
'QNetworkSession::State)'),
# https://bugreports.qt.io/browse/QTBUG-53989
("Image of format '' blocked because it is not considered safe. If "
"you are sure it is safe to do so, you can white-list the format by "
"setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST="),
# Installing Qt from the installer may cause it looking for SSL3 or
# OpenSSL 1.0 which may not be available on the system
"QSslSocket: cannot resolve ",
"QSslSocket: cannot call unresolved function ",
# When enabling debugging with QtWebEngine
("Remote debugging server started successfully. Try pointing a "
"Chromium-based browser to "),
# https://github.com/qutebrowser/qutebrowser/issues/1287
"QXcbClipboard: SelectionRequest too old",
# https://github.com/qutebrowser/qutebrowser/issues/2071
'QXcbWindow: Unhandled client message: ""',
# https://codereview.qt-project.org/176831
"QObject::disconnect: Unexpected null parameter",
# https://bugreports.qt.io/browse/QTBUG-76391
"Attribute Qt::AA_ShareOpenGLContexts must be set before "
"QCoreApplication is created.",
# Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
"GL format 0 is not supported",
]
# not using utils.is_mac here, because we can't be sure we can successfully
# import the utils module here.
if sys.platform == 'darwin':
suppressed_msgs += [
# https://bugreports.qt.io/browse/QTBUG-47154
('virtual void QSslSocketBackendPrivate::transmit() SSLRead '
'failed with: -9805'),
]
if not msg:
msg = "Logged empty message!"
if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs):
level = logging.DEBUG
elif context.category == "qt.webenginecontext" and (
msg.strip().startswith("GL Type: ") or # Qt 6.3
msg.strip().startswith("GLImplementation:") # Qt 6.2
):
level = logging.DEBUG
else:
level = qt_to_logging[msg_type]
if context.line is None:
lineno = -1 # type: ignore[unreachable]
else:
lineno = context.line
if context.function is None:
func = 'none' # type: ignore[unreachable]
elif ':' in context.function:
func = '"{}"'.format(context.function)
else:
func = context.function
if context.category is None or context.category == 'default':
name = 'qt'
else:
name = 'qt-' + context.category
if msg.splitlines()[0] == ('This application failed to start because it '
'could not find or load the Qt platform plugin '
'"xcb".'):
# Handle this message specially.
msg += ("\n\nOn Archlinux, this should fix the problem:\n"
" pacman -S libxkbcommon-x11")
faulthandler.disable()
assert _args is not None
if _args.debug:
stack: Optional[str] = ''.join(traceback.format_stack())
else:
stack = None
record = log.qt.makeRecord(name=name, level=level, fn=context.file, lno=lineno,
msg=msg, args=(), exc_info=None, func=func,
sinfo=stack)
log.qt.handle(record)
class QtWarningFilter(logging.Filter):
"""Filter to filter Qt warnings.
Attributes:
_pattern: The start of the message.
"""
def __init__(self, pattern: str) -> None:
super().__init__()
self._pattern = pattern
def filter(self, record: logging.LogRecord) -> bool:
"""Determine if the specified record is to be logged."""
do_log = not record.msg.strip().startswith(self._pattern)
return do_log
@contextlib.contextmanager
def hide_qt_warning(pattern: str, logger: str = 'qt') -> Iterator[None]:
"""Hide Qt warnings matching the given regex."""
log_filter = QtWarningFilter(pattern)
logger_obj = logging.getLogger(logger)
logger_obj.addFilter(log_filter)
try:
yield
finally:
logger_obj.removeFilter(log_filter)

View File

@ -25,6 +25,7 @@ import importlib
import re
import json
import platform
from contextlib import nullcontext as does_not_raise
import pytest
from qutebrowser.qt.core import QProcess, QPoint
@ -916,3 +917,15 @@ def test_sandboxing(
status = dict(line.split("\t") for line in lines)
assert status == expected_status
@pytest.mark.not_frozen
def test_logfilter_arg_does_not_crash(request, quteproc_new):
args = ['--temp-basedir', '--debug', '--logfilter', 'commands, init, ipc, webview']
with does_not_raise():
quteproc_new.start(args=args + _base_args(request.config))
# Waiting for quit to make sure no other warning is emitted
quteproc_new.send_cmd(':quit')
quteproc_new.wait_for_quit()

View File

@ -22,11 +22,9 @@ import argparse
import itertools
import sys
import warnings
import dataclasses
import pytest
import _pytest.logging # pylint: disable=import-private-name
from qutebrowser.qt import core as qtcore
from qutebrowser import qutebrowser
from qutebrowser.utils import log
@ -241,7 +239,7 @@ class TestInitLog:
@pytest.fixture(autouse=True)
def setup(self, mocker):
mocker.patch('qutebrowser.utils.log.qtcore.qInstallMessageHandler',
mocker.patch('qutebrowser.utils.qtlog.qtcore.qInstallMessageHandler',
autospec=True)
yield
# Make sure logging is in a sensible default state
@ -342,35 +340,6 @@ class TestInitLog:
assert log.console_filter.names == {'misc'}
class TestHideQtWarning:
"""Tests for hide_qt_warning/QtWarningFilter."""
@pytest.fixture
def qt_logger(self):
return logging.getLogger('qt-tests')
def test_unfiltered(self, qt_logger, caplog):
with log.hide_qt_warning("World", 'qt-tests'):
with caplog.at_level(logging.WARNING, 'qt-tests'):
qt_logger.warning("Hello World")
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.levelname == 'WARNING'
assert record.message == "Hello World"
@pytest.mark.parametrize('line', [
"Hello", # exact match
"Hello World", # match at start of line
" Hello World ", # match with spaces
])
def test_filtered(self, qt_logger, caplog, line):
with log.hide_qt_warning("Hello", 'qt-tests'):
with caplog.at_level(logging.WARNING, 'qt-tests'):
qt_logger.warning(line)
assert not caplog.records
@pytest.mark.parametrize('suffix, expected', [
('', 'STUB: test_stub'),
('foo', 'STUB: test_stub (foo)'),
@ -405,27 +374,3 @@ def test_warning_still_errors():
# Mainly a sanity check after the tests messing with warnings above.
with pytest.raises(UserWarning):
warnings.warn("error", UserWarning)
class TestQtMessageHandler:
@dataclasses.dataclass
class Context:
"""Fake QMessageLogContext."""
function: str = None
category: str = None
file: str = None
line: int = None
@pytest.fixture(autouse=True)
def init_args(self):
parser = qutebrowser.get_argparser()
args = parser.parse_args([])
log.init_log(args)
def test_empty_message(self, caplog):
"""Make sure there's no crash with an empty message."""
log.qt_message_handler(qtcore.QtMsgType.QtDebugMsg, self.Context(), "")
assert caplog.messages == ["Logged empty message!"]

View File

@ -0,0 +1,82 @@
# Copyright 2014-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <https://www.gnu.org/licenses/>.
"""Tests for qutebrowser.utils.qtlog."""
import dataclasses
import logging
import pytest
from qutebrowser import qutebrowser
from qutebrowser.utils import qtlog
from qutebrowser.qt import core as qtcore
class TestQtMessageHandler:
@dataclasses.dataclass
class Context:
"""Fake QMessageLogContext."""
function: str = None
category: str = None
file: str = None
line: int = None
@pytest.fixture(autouse=True)
def init_args(self):
parser = qutebrowser.get_argparser()
args = parser.parse_args([])
qtlog.init(args)
def test_empty_message(self, caplog):
"""Make sure there's no crash with an empty message."""
qtlog.qt_message_handler(qtcore.QtMsgType.QtDebugMsg, self.Context(), "")
assert caplog.messages == ["Logged empty message!"]
class TestHideQtWarning:
"""Tests for hide_qt_warning/QtWarningFilter."""
@pytest.fixture
def qt_logger(self):
return logging.getLogger('qt-tests')
def test_unfiltered(self, qt_logger, caplog):
with qtlog.hide_qt_warning("World", 'qt-tests'):
with caplog.at_level(logging.WARNING, 'qt-tests'):
qt_logger.warning("Hello World")
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.levelname == 'WARNING'
assert record.message == "Hello World"
@pytest.mark.parametrize('line', [
"Hello", # exact match
"Hello World", # match at start of line
" Hello World ", # match with spaces
])
def test_filtered(self, qt_logger, caplog, line):
with qtlog.hide_qt_warning("Hello", 'qt-tests'):
with caplog.at_level(logging.WARNING, 'qt-tests'):
qt_logger.warning(line)
assert not caplog.records