Skip to content

Commit 594e537

Browse files
committed
Merge pull request #58 from pytest-dev/disable-qtlog
Disable qtlog
2 parents cb74e93 + 59f9396 commit 594e537

File tree

4 files changed

+173
-40
lines changed

4 files changed

+173
-40
lines changed

Diff for: docs/logging.rst

+27-5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ For example:
4343
1 failed in 0.01 seconds
4444
4545
46+
**Disabling Logging Capture**
47+
4648
Qt logging capture can be disabled altogether by passing the ``--no-qt-log``
4749
to the command line, which will fallback to the default Qt bahavior of printing
4850
emitted messages directly to ``stderr``:
@@ -64,7 +66,7 @@ emitted messages directly to ``stderr``:
6466
this is a WARNING message
6567
6668
67-
``pytest-qt`` also provides a ``qtlog`` fixture, which tests can use
69+
``pytest-qt`` also provides a ``qtlog`` fixture that can used
6870
to check if certain messages were emitted during a test::
6971

7072
def do_something():
@@ -75,10 +77,30 @@ to check if certain messages were emitted during a test::
7577
emitted = [(m.type, m.message.strip()) for m in qtlog.records]
7678
assert emitted == [(QtWarningMsg, 'this is a WARNING message')]
7779

78-
Keep in mind that when ``--no-qt-log`` is passed in the command line,
79-
``qtlog.records`` will always be an empty list. See
80-
:class:`Record <pytestqt.plugin.Record>` for reference documentation on
81-
``Record`` objects.
80+
81+
``qtlog.records`` is a list of :class:`Record <pytestqt.plugin.Record>`
82+
instances.
83+
84+
Logging can also be disabled on a block of code using the ``qtlog.disabled()``
85+
context manager, or with the ``pytest.mark.no_qt_log`` mark:
86+
87+
.. code-block:: python
88+
89+
def test_foo(qtlog):
90+
with qtlog.disabled():
91+
# logging is disabled within the context manager
92+
do_something()
93+
94+
@pytest.mark.no_qt_log
95+
def test_bar():
96+
# logging is disabled for the entire test
97+
do_something()
98+
99+
100+
Keep in mind that when logging is disabled,
101+
``qtlog.records`` will always be an empty list.
102+
103+
**Log Formatting**
82104

83105
The output format of the messages can also be controlled by using the
84106
``--qt-log-format`` command line option, which accepts a string with standard

Diff for: pytestqt/_tests/test_logging.py

+67-5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
pytest_plugins = 'pytester'
99

1010

11-
@pytest.mark.parametrize('test_succeds, qt_log',
11+
@pytest.mark.parametrize('test_succeeds, qt_log',
1212
[(True, True), (True, False), (False, False),
1313
(False, True)])
14-
def test_basic_logging(testdir, test_succeds, qt_log):
14+
def test_basic_logging(testdir, test_succeeds, qt_log):
1515
"""
1616
Test Qt logging capture output.
1717
@@ -26,10 +26,10 @@ def test_types():
2626
qWarning('this is a WARNING message')
2727
qCritical('this is a CRITICAL message')
2828
assert {0}
29-
""".format(test_succeds)
29+
""".format(test_succeeds)
3030
)
3131
res = testdir.runpytest(*(['--no-qt-log'] if not qt_log else []))
32-
if test_succeds:
32+
if test_succeeds:
3333
assert 'Captured Qt messages' not in res.stdout.str()
3434
assert 'Captured stderr call' not in res.stdout.str()
3535
else:
@@ -87,6 +87,67 @@ def test_types(qtlog):
8787
res.stdout.fnmatch_lines('*1 passed*')
8888

8989

90+
@pytest.mark.parametrize('use_context_manager', [True, False])
91+
def test_disable_qtlog_context_manager(testdir, use_context_manager):
92+
"""
93+
Test qtlog.disabled() context manager.
94+
95+
:type testdir: _pytest.pytester.TmpTestdir
96+
"""
97+
testdir.makeini(
98+
"""
99+
[pytest]
100+
qt_log_level_fail = CRITICAL
101+
"""
102+
)
103+
104+
if use_context_manager:
105+
code = 'with qtlog.disabled():'
106+
else:
107+
code = 'if 1:'
108+
109+
testdir.makepyfile(
110+
"""
111+
from pytestqt.qt_compat import qCritical
112+
def test_1(qtlog):
113+
{code}
114+
qCritical('message')
115+
""".format(code=code)
116+
)
117+
res = testdir.inline_run()
118+
passed = 1 if use_context_manager else 0
119+
res.assertoutcome(passed=passed, failed=int(not passed))
120+
121+
122+
@pytest.mark.parametrize('use_mark', [True, False])
123+
def test_disable_qtlog_mark(testdir, use_mark):
124+
"""
125+
Test mark which disables logging capture for a test.
126+
127+
:type testdir: _pytest.pytester.TmpTestdir
128+
"""
129+
testdir.makeini(
130+
"""
131+
[pytest]
132+
qt_log_level_fail = CRITICAL
133+
"""
134+
)
135+
mark = '@pytest.mark.no_qt_log' if use_mark else ''
136+
137+
testdir.makepyfile(
138+
"""
139+
from pytestqt.qt_compat import qCritical
140+
import pytest
141+
{mark}
142+
def test_1():
143+
qCritical('message')
144+
""".format(mark=mark)
145+
)
146+
res = testdir.inline_run()
147+
passed = 1 if use_mark else 0
148+
res.assertoutcome(passed=passed, failed=int(not passed))
149+
150+
90151
def test_logging_formatting(testdir):
91152
"""
92153
Test custom formatting for logging messages.
@@ -345,7 +406,8 @@ def test_context_none(testdir):
345406
def test_foo(request):
346407
log_capture = request.node.qt_log_capture
347408
context = log_capture._Context(None, None, None)
348-
log_capture._handle(QtWarningMsg, "WARNING message", context)
409+
log_capture._handle_with_context(QtWarningMsg,
410+
context, "WARNING message")
349411
assert 0
350412
"""
351413
)

Diff for: pytestqt/plugin.py

+72-17
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import re
1414

1515
from pytestqt.qt_compat import QtCore, QtTest, QApplication, QT_API, \
16-
qInstallMsgHandler, QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg
16+
qInstallMsgHandler, qInstallMessageHandler, QtDebugMsg, QtWarningMsg, \
17+
QtCriticalMsg, QtFatalMsg
1718

1819

1920
def _inject_qtest_methods(cls):
@@ -633,30 +634,33 @@ def __init__(self, config):
633634
self.config = config
634635

635636
def pytest_runtest_setup(self, item):
637+
if item.get_marker('no_qt_log'):
638+
return
636639
m = item.get_marker('qt_log_ignore')
637640
if m:
638641
ignore_regexes = m.args
639642
else:
640643
ignore_regexes = self.config.getini('qt_log_ignore')
641644
item.qt_log_capture = _QtMessageCapture(ignore_regexes)
642-
previous_handler = qInstallMsgHandler(item.qt_log_capture._handle)
643-
item.qt_previous_handler = previous_handler
645+
item.qt_log_capture._start()
644646

645647
@pytest.mark.hookwrapper
646648
def pytest_runtest_makereport(self, item, call):
647649
"""Add captured Qt messages to test item report if the call failed."""
648650

649651
outcome = yield
650-
report = outcome.result
651-
652-
m = item.get_marker('qt_log_level_fail')
653-
if m:
654-
log_fail_level = m.args[0]
655-
else:
656-
log_fail_level = self.config.getini('qt_log_level_fail')
657-
assert log_fail_level in QtLoggingPlugin.LOG_FAIL_OPTIONS
652+
if not hasattr(item, 'qt_log_capture'):
653+
return
658654

659655
if call.when == 'call':
656+
report = outcome.result
657+
658+
m = item.get_marker('qt_log_level_fail')
659+
if m:
660+
log_fail_level = m.args[0]
661+
else:
662+
log_fail_level = self.config.getini('qt_log_level_fail')
663+
assert log_fail_level in QtLoggingPlugin.LOG_FAIL_OPTIONS
660664

661665
# make test fail if any records were captured which match
662666
# log_fail_level
@@ -684,15 +688,14 @@ def pytest_runtest_makereport(self, item, call):
684688
long_repr.addsection('Captured Qt messages',
685689
'\n'.join(lines))
686690

687-
qInstallMsgHandler(item.qt_previous_handler)
688-
del item.qt_previous_handler
691+
item.qt_log_capture._stop()
689692
del item.qt_log_capture
690693

691694

692695
class _QtMessageCapture(object):
693696
"""
694697
Captures Qt messages when its `handle` method is installed using
695-
qInstallMsgHandler, and stores them into `messages` attribute.
698+
qInstallMsgHandler, and stores them into `records` attribute.
696699
697700
:attr _records: list of Record instances.
698701
:attr _ignore_regexes: list of regexes (as strings) that define if a record
@@ -702,13 +705,51 @@ class _QtMessageCapture(object):
702705
def __init__(self, ignore_regexes):
703706
self._records = []
704707
self._ignore_regexes = ignore_regexes or []
708+
self._previous_handler = None
709+
710+
def _start(self):
711+
"""
712+
Start receiving messages from Qt.
713+
"""
714+
if qInstallMsgHandler:
715+
previous_handler = qInstallMsgHandler(self._handle_no_context)
716+
else:
717+
assert qInstallMessageHandler
718+
previous_handler = qInstallMessageHandler(self._handle_with_context)
719+
self._previous_handler = previous_handler
720+
721+
def _stop(self):
722+
"""
723+
Stop receiving messages from Qt, restoring the previously installed
724+
handler.
725+
"""
726+
if qInstallMsgHandler:
727+
qInstallMsgHandler(self._previous_handler)
728+
else:
729+
assert qInstallMessageHandler
730+
qInstallMessageHandler(self._previous_handler)
731+
732+
@contextmanager
733+
def disabled(self):
734+
"""
735+
Context manager that temporarily disables logging capture while
736+
inside it.
737+
"""
738+
self._stop()
739+
try:
740+
yield
741+
finally:
742+
self._start()
705743

706744
_Context = namedtuple('_Context', 'file function line')
707745

708-
def _handle(self, msg_type, message, context=None):
746+
def _append_new_record(self, msg_type, message, context):
709747
"""
710-
Method to be installed using qInstallMsgHandler, stores each message
711-
into the `messages` attribute.
748+
Creates a new Record instance and stores it.
749+
750+
:param msg_type: Qt message typ
751+
:param message: message string, if bytes it will be converted to str.
752+
:param context: QMessageLogContext object or None
712753
"""
713754
def to_unicode(s):
714755
if isinstance(s, bytes):
@@ -732,6 +773,20 @@ def to_unicode(s):
732773

733774
self._records.append(Record(msg_type, message, ignored, context))
734775

776+
def _handle_no_context(self, msg_type, message):
777+
"""
778+
Method to be installed using qInstallMsgHandler (Qt4),
779+
stores each message into the `_records` attribute.
780+
"""
781+
self._append_new_record(msg_type, message, context=None)
782+
783+
def _handle_with_context(self, msg_type, context, message):
784+
"""
785+
Method to be installed using qInstallMessageHandler (Qt5),
786+
stores each message into the `_records` attribute.
787+
"""
788+
self._append_new_record(msg_type, message, context=context)
789+
735790
@property
736791
def records(self):
737792
"""Access messages captured so far.

Diff for: pytestqt/qt_compat.py

+7-13
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ def _import_module(module_name):
7676
QtCriticalMsg = QtCore.QtCriticalMsg
7777
QtFatalMsg = QtCore.QtFatalMsg
7878

79+
# Qt4 and Qt5 have different functions to install a message handler;
80+
# the plugin will try to use the one that is not None
81+
qInstallMsgHandler = None
82+
qInstallMessageHandler = None
83+
7984
if QT_API == 'pyside':
8085
Signal = QtCore.Signal
8186
Slot = QtCore.Slot
@@ -93,19 +98,7 @@ def _import_module(module_name):
9398
_QtWidgets = _import_module('QtWidgets')
9499
QApplication = _QtWidgets.QApplication
95100
QWidget = _QtWidgets.QWidget
96-
97-
def qInstallMsgHandler(handler):
98-
"""
99-
Installs the given function as a message handler. This
100-
will adapt Qt5 message handler signature into Qt4
101-
message handler's signature.
102-
"""
103-
def _Qt5MessageHandler(msg_type, context, msg):
104-
handler(msg_type, msg, context)
105-
if handler is not None:
106-
return QtCore.qInstallMessageHandler(_Qt5MessageHandler)
107-
else:
108-
return QtCore.qInstallMessageHandler(None)
101+
qInstallMessageHandler = QtCore.qInstallMessageHandler
109102
else:
110103
QApplication = QtGui.QApplication
111104
QWidget = QtGui.QWidget
@@ -141,6 +134,7 @@ def __getattr__(cls, name):
141134
QApplication = Mock()
142135
QWidget = Mock()
143136
qInstallMsgHandler = Mock()
137+
qInstallMessageHandler = Mock()
144138
qDebug = Mock()
145139
qWarning = Mock()
146140
qCritical = Mock()

0 commit comments

Comments
 (0)