Skip to content

Commit 3e719fe

Browse files
committed
Add qt_log_level_fail option
1 parent 1c12eac commit 3e719fe

File tree

2 files changed

+100
-8
lines changed

2 files changed

+100
-8
lines changed

pytestqt/_tests/test_logging.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import datetime
2+
23
import pytest
34

45
from pytestqt.qt_compat import qDebug, qWarning, qCritical, QtDebugMsg, \
56
QtWarningMsg, QtCriticalMsg
67

7-
88
pytest_plugins = 'pytester'
99

1010

@@ -107,4 +107,45 @@ def test_types():
107107
res.stdout.fnmatch_lines([
108108
'*-- Captured Qt messages --*',
109109
'QtWarningMsg WARNING {0}: this is a WARNING message*'.format(today),
110-
])
110+
])
111+
112+
113+
@pytest.mark.parametrize('level, expect_passes',
114+
[('DEBUG', 1), ('WARNING', 2), ('CRITICAL', 3),
115+
('NO', 4)],
116+
)
117+
def test_logging_fails_tests(testdir, level, expect_passes):
118+
"""
119+
Test qt_log_level_fail ini option.
120+
121+
:type testdir: _pytest.pytester.TmpTestdir
122+
"""
123+
testdir.makeini(
124+
"""
125+
[pytest]
126+
qt_log_level_fail = {level}
127+
""".format(level=level)
128+
)
129+
testdir.makepyfile(
130+
"""
131+
from pytestqt.qt_compat import qWarning, qCritical, qDebug
132+
def test_1():
133+
qDebug('this is a DEBUG message')
134+
def test_2():
135+
qWarning('this is a WARNING message')
136+
def test_3():
137+
qCritical('this is a CRITICAL message')
138+
def test_4():
139+
assert 1
140+
"""
141+
)
142+
res = testdir.runpytest()
143+
lines = []
144+
if level != 'NO':
145+
lines.extend([
146+
'*Failure: Qt messages with level {0} or above emitted*'.format(
147+
level.upper()),
148+
'*-- Captured Qt messages --*',
149+
])
150+
lines.append('*{0} passed*'.format(expect_passes))
151+
res.stdout.fnmatch_lines(lines)

pytestqt/plugin.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import collections
21
from contextlib import contextmanager
32
import functools
43
import sys
54
import traceback
65
import weakref
76
import datetime
87

8+
from py._code.code import TerminalRepr
9+
from py._code.code import ReprFileLocation
10+
911
import pytest
1012

1113
from pytestqt.qt_compat import QtCore, QtTest, QApplication, QT_API, \
@@ -58,7 +60,6 @@ def result(*args, **kwargs):
5860

5961
@_inject_qtest_methods
6062
class QtBot(object):
61-
6263
"""
6364
Instances of this class are responsible for sending events to `Qt` objects (usually widgets),
6465
simulating user input.
@@ -206,10 +207,10 @@ def waitForWindowShown(self, widget):
206207
.. note:: In Qt5, the actual method called is qWaitForWindowExposed,
207208
but this name is kept for backward compatibility
208209
"""
209-
if hasattr(QtTest.QTest, 'qWaitForWindowShown'): # pragma: no cover
210+
if hasattr(QtTest.QTest, 'qWaitForWindowShown'): # pragma: no cover
210211
# PyQt4 and PySide
211212
QtTest.QTest.qWaitForWindowShown(widget)
212-
else: # pragma: no cover
213+
else: # pragma: no cover
213214
# PyQt5
214215
QtTest.QTest.qWaitForWindowExposed(widget)
215216

@@ -281,7 +282,6 @@ def waitSignal(self, signal=None, timeout=1000):
281282

282283

283284
class SignalBlocker(object):
284-
285285
"""
286286
Returned by :meth:`QtBot.waitSignal` method.
287287
@@ -417,6 +417,12 @@ def pytest_addoption(parser):
417417
parser.addini('qt_no_exception_capture',
418418
'disable automatic exception capture')
419419

420+
default_log_fail = QtLoggingPlugin.LOG_FAIL_OPTIONS[0]
421+
parser.addini('qt_log_level_fail',
422+
'log level in which tests can fail: {0} (default: "{1}")'
423+
.format(QtLoggingPlugin.LOG_FAIL_OPTIONS, default_log_fail),
424+
default=default_log_fail)
425+
420426
parser.addoption('--no-qt-log', dest='qt_log', action='store_false',
421427
default=True)
422428
parser.addoption('--qt-log-format', dest='qt_log_format',
@@ -452,6 +458,8 @@ class QtLoggingPlugin(object):
452458
test and augment reporting if the test failed with the messages captured.
453459
"""
454460

461+
LOG_FAIL_OPTIONS = ['NO', 'CRITICAL', 'WARNING', 'DEBUG']
462+
455463
def __init__(self, config):
456464
self.config = config
457465

@@ -467,14 +475,25 @@ def pytest_runtest_makereport(self, item, call):
467475
outcome = yield
468476
report = outcome.result
469477

478+
log_format = self.config.getoption('qt_log_format')
479+
log_fail_level = self.config.getini('qt_log_level_fail')
480+
470481
if call.when == 'call':
471482

483+
if log_fail_level != 'NO' and report.outcome != 'failed':
484+
for rec in item.qt_log_capture.records:
485+
if rec.matches_level(log_fail_level):
486+
report.outcome = 'failed'
487+
if report.longrepr is None:
488+
report.longrepr = \
489+
_QtLogLevelErrorRepr(item, log_fail_level)
490+
break
491+
472492
if not report.passed:
473493
long_repr = getattr(report, 'longrepr', None)
474494
if hasattr(long_repr, 'addsection'): # pragma: no cover
475495
lines = []
476496
for rec in item.qt_log_capture.records:
477-
log_format = self.config.getoption('qt_log_format')
478497
lines.append(log_format.format(rec=rec))
479498
if lines:
480499
long_repr.addsection('Captured Qt messages',
@@ -568,3 +587,35 @@ def _get_log_type_name(cls, msg_type):
568587
QtFatalMsg: 'FATAL',
569588
}
570589
return cls._log_type_name_map[msg_type]
590+
591+
def matches_level(self, level):
592+
if level == 'DEBUG':
593+
return self.log_type_name in {'DEBUG', 'WARNING', 'CRITICAL'}
594+
elif level == 'WARNING':
595+
return self.log_type_name in {'WARNING', 'CRITICAL'}
596+
elif level == 'CRITICAL':
597+
return self.log_type_name in {'CRITICAL'}
598+
else:
599+
raise ValueError('log_fail_level unknown: {0}'.format(level))
600+
601+
602+
class _QtLogLevelErrorRepr(TerminalRepr):
603+
"""
604+
TerminalRepr of a test which didn't fail by normal means, but emitted
605+
messages at or above the allowed level.
606+
"""
607+
608+
def __init__(self, item, level):
609+
msg = 'Failure: Qt messages with level {0} or above emitted'
610+
path, line, _ = item.location
611+
self.fileloc = ReprFileLocation(path, line, msg.format(level.upper()))
612+
self.sections = []
613+
614+
def addsection(self, name, content, sep="-"):
615+
self.sections.append((name, content, sep))
616+
617+
def toterminal(self, out):
618+
self.fileloc.toterminal(out)
619+
for name, content, sep in self.sections:
620+
out.sep(sep, name)
621+
out.line(content)

0 commit comments

Comments
 (0)