Qt Logging Capture¶
New in version 1.4.
Qt features its own logging mechanism through qInstallMsgHandler
(qInstallMessageHandler
on Qt5) 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 qWarning
def do_something():
qWarning('this is a WARNING message')
def test_foo(qtlog):
do_something()
assert 0
$ py.test 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 bahavior of printing
emitted messages directly to stderr
:
py.test 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
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:
$ py.test 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()
>py.test 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
py.test 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()
py.test 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 qt_log_level_fail
and qt_log_ignore
settins
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()