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._code import filter_traceback
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest.compat import final 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 fail
from _pytest.outcomes import Skipped from _pytest.outcomes import Skipped
from _pytest.pathlib import absolutepath from _pytest.pathlib import absolutepath

View File

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

View File

@ -9,6 +9,19 @@ logger = logging.getLogger(__name__)
sublogger = logging.getLogger(__name__ + ".baz") 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: def test_fixture_help(pytester: Pytester) -> None:
result = pytester.runpytest("--fixtures") result = pytester.runpytest("--fixtures")
result.stdout.fnmatch_lines(["*caplog*"]) result.stdout.fnmatch_lines(["*caplog*"])
@ -29,7 +42,7 @@ def test_change_level(caplog):
assert "CRITICAL" in caplog.text 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) logging.disable(logging.CRITICAL)
assert logging.root.manager.disable == logging.CRITICAL assert logging.root.manager.disable == logging.CRITICAL
caplog.set_level(logging.WARNING) 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_WARNING" not in caplog.text
assert "SUB_CRITICAL" 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: def test_change_level_undo(pytester: Pytester) -> None:
"""Ensure that 'set_level' is undone after the end of the test. """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*") 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. """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). 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.fnmatch_lines(["*log from test1*", "*2 failed in *"])
result.stdout.no_fnmatch_line("*log from test2*") 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: def test_change_level_undos_handler_level(pytester: Pytester) -> None:
"""Ensure that 'set_level' is undone after the end of the test (handler). """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 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) logging.disable(logging.CRITICAL)
assert logging.root.manager.disable == logging.CRITICAL assert logging.root.manager.disable == logging.CRITICAL
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
@ -175,9 +184,6 @@ def test_with_statement_logging_disabled(caplog):
assert "SUB_CRITICAL" in caplog.text assert "SUB_CRITICAL" in caplog.text
assert logging.root.manager.disable == logging.CRITICAL 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): def test_log_access(caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)