Qt Logging Capture
New in version 1.4.
Qt features its own logging mechanism through qInstallMessageHandler
and
qDebug
, qWarning
, qCritical
functions. These are used by Qt to
print warning messages when internal errors occur.
pytest-qt
automatically captures these messages and displays them when a
test fails, similar to what pytest
does for stderr
and stdout
and
the pytest-catchlog plugin.
For example:
from pytestqt.qt_compat import qt_api
def do_something():
qt_api.qWarning("this is a WARNING message")
def test_foo():
do_something()
assert 0
$ pytest test.py -q
F
================================== FAILURES ===================================
_________________________________ test_types __________________________________
def test_foo():
do_something()
> assert 0
E assert 0
test.py:8: AssertionError
---------------------------- Captured Qt messages -----------------------------
QtWarningMsg: this is a WARNING message
1 failed in 0.01 seconds
Disabling Logging Capture
Qt logging capture can be disabled altogether by passing the --no-qt-log
to the command line, which will fallback to the default Qt behavior of printing
emitted messages directly to stderr
:
pytest test.py -q --no-qt-log
F
================================== FAILURES ===================================
_________________________________ test_types __________________________________
def test_foo():
do_something()
> assert 0
E assert 0
test.py:8: AssertionError
---------------------------- Captured stderr call -----------------------------
this is a WARNING message
Using pytest’s -s
(--capture=no
) option will also disable Qt log capturing.
qtlog fixture
pytest-qt
also provides a qtlog
fixture that can used
to check if certain messages were emitted during a test:
def do_something():
qWarning('this is a WARNING message')
def test_foo(qtlog):
do_something()
emitted = [(m.type, m.message.strip()) for m in qtlog.records]
assert emitted == [(QtWarningMsg, 'this is a WARNING message')]
qtlog.records
is a list of Record
instances.
Logging can also be disabled on a block of code using the qtlog.disabled()
context manager, or with the pytest.mark.no_qt_log
mark:
def test_foo(qtlog):
with qtlog.disabled():
# logging is disabled within the context manager
do_something()
@pytest.mark.no_qt_log
def test_bar():
# logging is disabled for the entire test
do_something()
Keep in mind that when logging is disabled,
qtlog.records
will always be an empty list.
Log Formatting
The output format of the messages can also be controlled by using the
--qt-log-format
command line option, which accepts a string with standard
{}
formatting which can make use of attribute interpolation of the record
objects:
$ pytest test.py --qt-log-format="{rec.when} {rec.type_name}: {rec.message}"
Keep in mind that you can make any of the options above the default
for your project by using pytest’s standard addopts
option in you
pytest.ini
file:
[pytest]
qt_log_format = {rec.when} {rec.type_name}: {rec.message}
Automatically failing tests when logging messages are emitted
Printing messages to stderr
is not the best solution to notice that
something might not be working as expected, specially when running in a
continuous integration server where errors in logs are rarely noticed.
You can configure pytest-qt
to automatically fail a test if it emits
a message of a certain level or above using the qt_log_level_fail
ini
option:
[pytest]
qt_log_level_fail = CRITICAL
With this configuration, any test which emits a CRITICAL message or above will fail, even if no actual asserts fail within the test:
from pytestqt.qt_compat import qCritical
def do_something():
qCritical("WM_PAINT failed")
def test_foo(qtlog):
do_something()
>pytest test.py --color=no -q
F
================================== FAILURES ===================================
__________________________________ test_foo ___________________________________
test.py:5: Failure: Qt messages with level CRITICAL or above emitted
---------------------------- Captured Qt messages -----------------------------
QtCriticalMsg: WM_PAINT failed
The possible values for qt_log_level_fail
are:
NO
: disables test failure by log messages.DEBUG
: messages emitted byqDebug
function or above.WARNING
: messages emitted byqWarning
function or above.CRITICAL
: messages emitted byqCritical
function only.
If some failures are known to happen and considered harmless, they can
be ignored by using the qt_log_ignore
ini option, which
is a list of regular expressions matched using re.search
:
[pytest]
qt_log_level_fail = CRITICAL
qt_log_ignore =
WM_DESTROY.*sent
WM_PAINT failed
pytest test.py --color=no -q
.
1 passed in 0.01 seconds
Messages which do not match any of the regular expressions
defined by qt_log_ignore
make tests fail as usual:
def do_something():
qCritical("WM_PAINT not handled")
qCritical("QObject: widget destroyed in another thread")
def test_foo(qtlog):
do_something()
pytest test.py --color=no -q
F
================================== FAILURES ===================================
__________________________________ test_foo ___________________________________
test.py:6: Failure: Qt messages with level CRITICAL or above emitted
---------------------------- Captured Qt messages -----------------------------
QtCriticalMsg: WM_PAINT not handled (IGNORED)
QtCriticalMsg: QObject: widget destroyed in another thread
You can also override the qt_log_level_fail
setting and extend
qt_log_ignore
patterns from pytest.ini
in some tests by using a mark
with the same name:
def do_something():
qCritical("WM_PAINT not handled")
qCritical("QObject: widget destroyed in another thread")
@pytest.mark.qt_log_level_fail("CRITICAL")
@pytest.mark.qt_log_ignore("WM_DESTROY.*sent", "WM_PAINT failed")
def test_foo(qtlog):
do_something()
If you would like to override the list of ignored patterns instead, pass
extend=False
to the qt_log_ignore
mark:
@pytest.mark.qt_log_ignore("WM_DESTROY.*sent", extend=False)
def test_foo(qtlog):
do_something()