diff --git a/AUTHORS b/AUTHORS index b59ebc2a2..01159f7d4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -99,6 +99,7 @@ Erik M. Bray Evan Kepner Fabien Zarifian Fabio Zadrozny +Felix Nieuwenhuizen Feng Ma Florian Bruhin Floris Bruynooghe diff --git a/changelog/6240.bugfix.rst b/changelog/6240.bugfix.rst new file mode 100644 index 000000000..b5f5844ec --- /dev/null +++ b/changelog/6240.bugfix.rst @@ -0,0 +1,2 @@ +Fixes an issue where logging during collection step caused duplication of log +messages to stderr. diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 681fdee62..8cb7b1841 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -613,7 +613,13 @@ class LoggingPlugin: with catching_logs(self.log_file_handler, level=self.log_file_level): yield else: - yield + # Add a dummy handler to ensure logging.root.handlers is not empty. + # If it were empty, then a `logging.warning()` call (and similar) during collection + # would trigger a `logging.basicConfig()` call, which would add a `StreamHandler` + # handler, which would cause all subsequent logs which reach the root to be also + # printed to stdout, which we don't want (issue #6240). + with catching_logs(logging.NullHandler()): + yield @contextmanager def _runtest_for(self, item, when): diff --git a/testing/test_capture.py b/testing/test_capture.py index 233143193..c064614d2 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1493,3 +1493,32 @@ def test__get_multicapture() -> None: pytest.raises(ValueError, _get_multicapture, "unknown").match( r"^unknown capturing method: 'unknown'" ) + + +def test_logging_while_collecting(testdir): + """Issue #6240: Calls to logging.xxx() during collection causes all logging calls to be duplicated to stderr""" + p = testdir.makepyfile( + """\ + import logging + + logging.warning("during collection") + + def test_logging(): + logging.warning("during call") + assert False + """ + ) + result = testdir.runpytest_subprocess(p) + assert result.ret == ExitCode.TESTS_FAILED + result.stdout.fnmatch_lines( + [ + "*test_*.py F*", + "====* FAILURES *====", + "____*____", + "*--- Captured log call*", + "WARNING * during call", + "*1 failed*", + ] + ) + result.stdout.no_fnmatch_line("*Captured stderr call*") + result.stdout.no_fnmatch_line("*during collection*")