Skip to content

Commit 32f3831

Browse files
committed
Add support for qt_log_ignore ini option
1 parent 9c85def commit 32f3831

File tree

2 files changed

+94
-13
lines changed

2 files changed

+94
-13
lines changed

pytestqt/_tests/test_logging.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,61 @@ def test_1():
174174
)
175175
res = testdir.inline_run()
176176
res.assertoutcome(failed=1)
177+
178+
179+
def test_logging_fails_ignore(testdir):
180+
"""
181+
Test qt_log_ignore config option.
182+
183+
:type testdir: _pytest.pytester.TmpTestdir
184+
"""
185+
testdir.makeini(
186+
"""
187+
[pytest]
188+
qt_log_level_fail = CRITICAL
189+
qt_log_ignore = WM_DESTROY.*sent
190+
"""
191+
)
192+
testdir.makepyfile(
193+
"""
194+
from pytestqt.qt_compat import qWarning, qCritical
195+
import pytest
196+
197+
def test1():
198+
qCritical('a critical message')
199+
def test2():
200+
qCritical('WM_DESTROY was sent')
201+
def test3():
202+
qCritical('WM_DESTROY was sent')
203+
assert 0
204+
def test4():
205+
qCritical('WM_DESTROY was sent')
206+
qCritical('another critical message')
207+
"""
208+
)
209+
res = testdir.runpytest()
210+
lines = [
211+
# test1 fails because it has emitted a CRITICAL message and that message
212+
# does not match any regex in qt_log_ignore
213+
'*_ test1 _*',
214+
'*Failure: Qt messages with level CRITICAL or above emitted*',
215+
'*QtCriticalMsg: a critical message*',
216+
217+
# test2 succeeds because its message matches qt_log_ignore
218+
219+
# test3 fails because of an assert, but the ignored message should
220+
# still appear in the failure message
221+
'*_ test3 _*',
222+
'*AssertionError*',
223+
'*QtCriticalMsg: WM_DESTROY was sent*(IGNORED)*',
224+
225+
# test4 fails because one message is ignored but the other isn't
226+
'*_ test4 _*',
227+
'*Failure: Qt messages with level CRITICAL or above emitted*',
228+
'*QtCriticalMsg: WM_DESTROY was sent*(IGNORED)*',
229+
'*QtCriticalMsg: another critical message*',
230+
231+
# summary
232+
'*3 failed, 1 passed*',
233+
]
234+
res.stdout.fnmatch_lines(lines)

pytestqt/plugin.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from py._code.code import ReprFileLocation
1010

1111
import pytest
12+
import re
1213

1314
from pytestqt.qt_compat import QtCore, QtTest, QApplication, QT_API, \
1415
qInstallMsgHandler, QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg
@@ -561,6 +562,9 @@ def pytest_addoption(parser):
561562
'log level in which tests can fail: {0} (default: "{1}")'
562563
.format(QtLoggingPlugin.LOG_FAIL_OPTIONS, default_log_fail),
563564
default=default_log_fail)
565+
parser.addini('qt_log_ignore',
566+
'list of regexes for messages that should not cause a tests '
567+
'to fails', type='linelist')
564568

565569
parser.addoption('--no-qt-log', dest='qt_log', action='store_false',
566570
default=True)
@@ -604,7 +608,7 @@ def qtlog(request):
604608
if hasattr(request._pyfuncitem, 'qt_log_capture'):
605609
return request._pyfuncitem.qt_log_capture
606610
else:
607-
return _QtMessageCapture() # pragma: no cover
611+
return _QtMessageCapture([]) # pragma: no cover
608612

609613

610614
class QtLoggingPlugin(object):
@@ -619,7 +623,8 @@ def __init__(self, config):
619623
self.config = config
620624

621625
def pytest_runtest_setup(self, item):
622-
item.qt_log_capture = _QtMessageCapture()
626+
item.qt_log_capture = _QtMessageCapture(
627+
item.config.getini('qt_log_ignore'))
623628
previous_handler = qInstallMsgHandler(item.qt_log_capture._handle)
624629
item.qt_previous_handler = previous_handler
625630

@@ -630,8 +635,6 @@ def pytest_runtest_makereport(self, item, call):
630635
outcome = yield
631636
report = outcome.result
632637

633-
log_format = self.config.getoption('qt_log_format')
634-
635638
m = item.get_marker('qt_log_level_fail')
636639
if m:
637640
log_fail_level = m.args[0]
@@ -641,21 +644,27 @@ def pytest_runtest_makereport(self, item, call):
641644

642645
if call.when == 'call':
643646

647+
# make test fail if any records were captured which match
648+
# log_fail_level
644649
if log_fail_level != 'NO' and report.outcome != 'failed':
645650
for rec in item.qt_log_capture.records:
646-
if rec.matches_level(log_fail_level):
651+
if rec.matches_level(log_fail_level) and not rec.ignored:
647652
report.outcome = 'failed'
648653
if report.longrepr is None:
649654
report.longrepr = \
650655
_QtLogLevelErrorRepr(item, log_fail_level)
651656
break
652657

658+
# if test has failed, add recorded messages to its terminal
659+
# representation
653660
if not report.passed:
654661
long_repr = getattr(report, 'longrepr', None)
655662
if hasattr(long_repr, 'addsection'): # pragma: no cover
663+
log_format = self.config.getoption('qt_log_format')
656664
lines = []
657665
for rec in item.qt_log_capture.records:
658-
lines.append(log_format.format(rec=rec))
666+
suffix = ' (IGNORED)' if rec.ignored else ''
667+
lines.append(log_format.format(rec=rec) + suffix)
659668
if lines:
660669
long_repr.addsection('Captured Qt messages',
661670
'\n'.join(lines))
@@ -670,11 +679,14 @@ class _QtMessageCapture(object):
670679
Captures Qt messages when its `handle` method is installed using
671680
qInstallMsgHandler, and stores them into `messages` attribute.
672681
673-
:attr messages: list of Record named-tuples.
682+
:attr _records: list of Record instances.
683+
:attr _ignore_regexes: list of regexes (as strings) that define if a record
684+
should be ignored.
674685
"""
675686

676-
def __init__(self):
687+
def __init__(self, ignore_regexes):
677688
self._records = []
689+
self._ignore_regexes = ignore_regexes or []
678690

679691
def _handle(self, msg_type, message):
680692
"""
@@ -683,7 +695,14 @@ def _handle(self, msg_type, message):
683695
"""
684696
if isinstance(message, bytes):
685697
message = message.decode('utf-8', 'replace')
686-
self._records.append(Record(msg_type, message))
698+
699+
ignored = False
700+
for regex in self._ignore_regexes:
701+
if re.search(regex, message) is not None:
702+
ignored = True
703+
break
704+
705+
self._records.append(Record(msg_type, message, ignored))
687706

688707
@property
689708
def records(self):
@@ -704,20 +723,24 @@ class Record(object):
704723
type name similar to the logging package, for example ``DEBUG``,
705724
``WARNING``, etc.
706725
:attr datetime.datetime when: when the message was sent
726+
:attr ignored: If this record matches a regex from the "qt_log_ignore"
727+
option.
707728
"""
708729

709-
def __init__(self, msg_type, message):
730+
def __init__(self, msg_type, message, ignored):
710731
self._type = msg_type
711732
self._message = message
712733
self._type_name = self._get_msg_type_name(msg_type)
713734
self._log_type_name = self._get_log_type_name(msg_type)
714735
self._when = datetime.datetime.now()
736+
self._ignored = ignored
715737

716738
message = property(lambda self: self._message)
717739
type = property(lambda self: self._type)
718740
type_name = property(lambda self: self._type_name)
719741
log_type_name = property(lambda self: self._log_type_name)
720742
when = property(lambda self: self._when)
743+
ignored = property(lambda self: self._ignored)
721744

722745
@classmethod
723746
def _get_msg_type_name(cls, msg_type):
@@ -751,11 +774,11 @@ def _get_log_type_name(cls, msg_type):
751774

752775
def matches_level(self, level):
753776
if level == 'DEBUG':
754-
return self.log_type_name in set(['DEBUG', 'WARNING', 'CRITICAL'])
777+
return self.log_type_name in ('DEBUG', 'WARNING', 'CRITICAL')
755778
elif level == 'WARNING':
756-
return self.log_type_name in set(['WARNING', 'CRITICAL'])
779+
return self.log_type_name in ('WARNING', 'CRITICAL')
757780
elif level == 'CRITICAL':
758-
return self.log_type_name in set(['CRITICAL'])
781+
return self.log_type_name in ('CRITICAL',)
759782
else:
760783
raise ValueError('log_fail_level unknown: {0}'.format(level))
761784

0 commit comments

Comments
 (0)