caplog un-disable logging, feedback per review #8711

- Resolves issues from review provided by https://github.com/andrewdotn

- Fixed spelling errors

- Improved logic in `force_enable_logging`

- Make a fixture to un-disable logging after a test so that logging will be un-disabled even if the test fails due to an assertion error.
The fixture is in `test_fixture.py` because we can't `import logging` in `conftest.py` because there is a module called `logging` in the same folder.

- Mypy implicit import error. I can't commit without this.

Issue: https://github.com/pytest-dev/pytest/issues/8711
PR: https://github.com/pytest-dev/pytest/pull/8758
This commit is contained in:
Alex Lambson 2021-11-05 20:52:43 -06:00 committed by Alex Lambson
parent e480dfe48b
commit bc5a27f208
3 changed files with 27 additions and 20 deletions

View File

@ -49,7 +49,7 @@ from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback
from _pytest._io import TerminalWriter
from _pytest.compat import final
from _pytest.compat import importlib_metadata
from _pytest.compat import importlib_metadata # type: ignore[attr-defined]
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import absolutepath

View File

@ -463,7 +463,7 @@ class LogCaptureFixture:
) -> int:
"""Enable the desired logging level if the level was disabled.
Only enables logging levels equal too and higher than the requested ``level``.
Only enables logging levels greater than or equal to the requested ``level``.
Does nothing if the desired ``level`` wasn't disabled.
@ -478,14 +478,15 @@ class LogCaptureFixture:
original_disable_level: int = logger_obj.manager.disable # type: ignore[attr-defined]
if isinstance(level, str):
# Try to translate the level string to an int for `logging.disable()`
level = logging.getLevelName(level)
if not isinstance(level, int):
# The level provided was not valid, so just un-disable all logging.
logging.disable(logging.NOTSET)
return original_disable_level
if logger_obj.isEnabledFor(level):
return original_disable_level
elif not logger_obj.isEnabledFor(level):
# Each level is `10` away from other levels.
# https://docs.python.org/3/library/logging.html#logging-levels
disable_level = max(level - 10, logging.NOTSET)
logging.disable(disable_level)

View File

@ -9,6 +9,19 @@ logger = logging.getLogger(__name__)
sublogger = logging.getLogger(__name__ + ".baz")
@pytest.fixture
def cleanup_disabled_logging():
"""Simple fixture that ensures that a test doesn't disable logging.
This is necessary because ``logging.disable()`` is global, so a test disabling logging
and not cleaning up after will break every test that runs after it.
This behavior was moved to a fixture so that logging will be un-disabled even if the test fails an assertion.
"""
yield
logging.disable(logging.NOTSET)
def test_fixture_help(pytester: Pytester) -> None:
result = pytester.runpytest("--fixtures")
result.stdout.fnmatch_lines(["*caplog*"])
@ -29,7 +42,7 @@ def test_change_level(caplog):
assert "CRITICAL" in caplog.text
def test_change_level_logging_disabled(caplog):
def test_change_level_logging_disabled(caplog, cleanup_disabled_logging):
logging.disable(logging.CRITICAL)
assert logging.root.manager.disable == logging.CRITICAL
caplog.set_level(logging.WARNING)
@ -45,9 +58,6 @@ def test_change_level_logging_disabled(caplog):
assert "SUB_WARNING" not in caplog.text
assert "SUB_CRITICAL" in caplog.text
# logging.disable needs to be reset because it's global and causes future tests will break.
logging.disable(logging.NOTSET)
def test_change_level_undo(pytester: Pytester) -> None:
"""Ensure that 'set_level' is undone after the end of the test.
@ -75,7 +85,9 @@ def test_change_level_undo(pytester: Pytester) -> None:
result.stdout.no_fnmatch_line("*log from test2*")
def test_change_disabled_level_undo(pytester: Pytester) -> None:
def test_change_disabled_level_undo(
pytester: Pytester, cleanup_disabled_logging
) -> None:
"""Ensure that 'force_enable_logging' in 'set_level' is undone after the end of the test.
Tests the logging output themselves (affected by disabled logging level).
@ -103,9 +115,6 @@ def test_change_disabled_level_undo(pytester: Pytester) -> None:
result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"])
result.stdout.no_fnmatch_line("*log from test2*")
# logging.disable needs to be reset because it's global and causes future tests will break.
logging.disable(logging.NOTSET)
def test_change_level_undos_handler_level(pytester: Pytester) -> None:
"""Ensure that 'set_level' is undone after the end of the test (handler).
@ -150,7 +159,7 @@ def test_with_statement(caplog):
assert "CRITICAL" in caplog.text
def test_with_statement_logging_disabled(caplog):
def test_with_statement_logging_disabled(caplog, cleanup_disabled_logging):
logging.disable(logging.CRITICAL)
assert logging.root.manager.disable == logging.CRITICAL
with caplog.at_level(logging.WARNING):
@ -175,9 +184,6 @@ def test_with_statement_logging_disabled(caplog):
assert "SUB_CRITICAL" in caplog.text
assert logging.root.manager.disable == logging.CRITICAL
# logging.disable needs to be reset because it's global and causes future tests will break.
logging.disable(logging.NOTSET)
def test_log_access(caplog):
caplog.set_level(logging.INFO)