test_fixture.py
Overview
`test_fixture.py` is a test module primarily focused on validating the behavior and features of the `caplog` pytest fixture, which provides logging capture capabilities during testing. It contains a series of pytest test functions that verify how logging levels, disabling/enabling logging, filtering, and message capturing work in various scenarios.
The file also includes a cleanup fixture to ensure that global logging state is restored after tests, preventing side effects across tests due to Python's global logging configuration.
This module interacts deeply with pytest's logging plugin and the `caplog` fixture, as well as pytest's testing utilities such as `Pytester` for running isolated test sessions and inspecting outcomes.
Detailed Explanation of Entities
Fixtures
cleanup_disabled_logging()
@pytest.fixture(autouse=True)
def cleanup_disabled_logging() -> Iterator[None]:
Purpose: Automatically runs around every test to ensure that any global logging disabling (
logging.disable()) done by a test is undone after it finishes.Parameters: None
Returns: An iterator yielding once (to run test), then continues with cleanup.
Usage: This is an autouse fixture, so it is applied automatically without needing to be explicitly requested in tests.
Implementation Detail: Uses
yieldto run the test, then callslogging.disable(logging.NOTSET)to re-enable logging globally.Reason: Since
logging.disable()affects the global state, forgetting to reset it will break subsequent tests that rely on logging.
Test Functions
All test functions use pytest's standard signature, often accepting fixtures like `caplog` (pytest's LogCaptureFixture) or `pytester` (pytest's testing helper).
test_fixture_help(pytester: Pytester) -> None
Description: Verifies that the
--fixturesCLI command listscaplogamong available fixtures.Implementation: Runs
pytest --fixturesviapytesterand asserts output contains "caplog".
test_change_level(caplog: pytest.LogCaptureFixture) -> None
Description: Tests changing logging levels using
caplog.set_level()for both root logger and a child logger (sublogger).Key points:
Sets caplog level to INFO globally and CRITICAL for
sublogger.Asserts that logs below the set levels are filtered out correctly.
Usage Example:
caplog.set_level(logging.INFO)
logger.info("info message") # captured
logger.debug("debug message") # ignored
test_change_level_logging_disabled(caplog: pytest.LogCaptureFixture) -> None
Description: Tests behavior when global logging is disabled (
logging.disable(logging.CRITICAL)) butcaplogtries to capture logs at lower levels.Key points:
Demonstrates that
caplog.set_levelcan temporarily "force enable" logging even if global is disabled.
Assertions: Ensures only logs above forced level are captured.
test_change_level_undo(pytester: Pytester) -> None
Description: Verifies that changes made by
caplog.set_level()are undone after the test ends, so subsequent tests do not inherit altered logging levels.Implementation: Creates two tests, one that sets a level and fails, another that doesn't set a level, ensuring the second doesn't show logs from the first test's level change.
test_change_disabled_level_undo(pytester: Pytester) -> None
Description: Similar to above but focuses on undoing the "force enable" behavior when logging was globally disabled.
Ensures: The disabled logging state is restored after the test.
test_change_level_undoes_handler_level(pytester: Pytester) -> None
Description: Tests that handler-level changes by
caplog.set_level()are undone after the test.Context: Addresses issue #7569 related to handler level not resetting.
test_with_statement_at_level(caplog: pytest.LogCaptureFixture) -> None
Description: Tests the context manager
caplog.at_level()for temporarily changing logging levels.Example Usage:
with caplog.at_level(logging.INFO):
logger.info("info") # captured
logger.debug("debug") # not captured
Nested usage: Also tests nested
at_levelwith different loggers.
test_with_statement_at_level_logging_disabled(caplog: pytest.LogCaptureFixture) -> None
Description: Tests
caplog.at_level()behavior when logging is globally disabled.Ensures: Logging is properly force-enabled within context and restored afterwards.
test_with_statement_filtering(caplog: pytest.LogCaptureFixture) -> None
Description: Tests
caplog.filtering()context manager which temporarily adds a logging filter.Implementation: Defines a custom filter that changes the message content.
Verifies: Filter modifies messages only inside the context manager.
test_force_enable_logging_level_string(...)
Description: Tests internal
_force_enable_loggingmethod ofcaplogwhen passed a level as a string.Checks: That the effective disabled level is one below the requested enabled level.
Parameters:
level_str: string name of logging level.expected_disable_level: int logging level expected after forcing enable.
test_log_access(caplog: pytest.LogCaptureFixture) -> None
Description: Verifies access to
caplog.recordsand content formatting.Checks: The original message template and the formatted message are available.
test_messages(caplog: pytest.LogCaptureFixture) -> None
Description: Checks
caplog.messagesproperty returns formatted messages.Also tests: Logging of exceptions and presence of tracebacks.
test_record_tuples(caplog: pytest.LogCaptureFixture) -> None
Description: Ensures
caplog.record_tuplesreturns a list of tuples(logger_name, level, message).
test_unicode(caplog: pytest.LogCaptureFixture) -> None
Description: Tests logging and capturing of unicode characters.
test_clear(caplog: pytest.LogCaptureFixture) -> None
Description: Verifies that
caplog.clear()empties captured records and text.
logging_during_setup_and_teardown(caplog: pytest.LogCaptureFixture) -> Iterator[None]
Fixture: Logs messages during setup and teardown phases to test capturing across test lifecycle.
Usage: Used in tests verifying stage-specific capture.
test_captures_for_all_stages(caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None) -> None
Description: Tests that captured logs are correctly stored and accessible for "setup" and "call" stages of a test.
test_clear_for_call_stage(caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None) -> None
Description: Tests that clearing captured logs only clears the "call" stage logs and preserves "setup" logs.
test_ini_controls_global_log_level(pytester: Pytester) -> None
Description: Verifies that setting
log_levelin pytest.ini affects global logging level during tests.
test_can_override_global_log_level(pytester: Pytester) -> None
Description: Tests that
caplog.set_level()andcaplog.at_level()can override global log level set by pytest config.
test_captures_despite_exception(pytester: Pytester) -> None
Description: Ensures caplog captures log messages even if an exception is raised inside
at_level()context.
test_log_report_captures_according_to_config_option_upon_failure(pytester: Pytester) -> None
Description: Tests that log messages are reported in test failure output according to
--log-leveloption.
Important Implementation Details and Algorithms
The module heavily relies on pytest's
caplogfixture which internally captures Python's logging output during tests.Mechanisms tested include:
Temporarily changing logging levels for handlers and loggers.
Forcing logging to be enabled when it was globally disabled.
Filtering logs dynamically.
Capturing logs across different test phases (setup, call, teardown).
Integration with pytest configuration options (
log_level).
Use of
pytesterfixture allows creating and running isolated pytest sessions programmatically to verify behavior of logging capture in real test runs.The
cleanup_disabled_loggingfixture uses theyieldpattern to guarantee restoration of logging state after each test, ensuring isolation.
Interaction with Other Parts of the System
Interacts with pytest's logging plugin (
logging-plugin), especially thecaplogfixture.Uses
_pytest.logging.caplog_records_keyinternal key to access private stash storage of captured logs.Uses
Pytesterfrom_pytest.pytesterto run subprocess pytest tests.Works with Python's standard
loggingmodule to manipulate loggers and handler levels.Relies on pytest configuration (e.g.,
log_levelfrom ini files or CLI options) to verify global log level behavior.
Visual Diagram
The following Mermaid class diagram summarizes the main entities and their relationships in this test file, focusing on the fixtures and test functions and their usage of the `caplog` fixture and Python logging objects.
classDiagram
class cleanup_disabled_logging {
<<fixture>>
+__call__(): Iterator[None]
}
class logging_during_setup_and_teardown {
<<fixture>>
+__call__(): Iterator[None]
}
class TestFilter {
+filter(record: LogRecord): bool
}
class caplog {
<<pytest Fixture>>
+set_level(level: int | str, logger: Optional[str] = None)
+at_level(level: int, logger: Optional[str] = None)
+filtering(filter: logging.Filter)
+clear()
+records: List[LogRecord]
+messages: List[str]
+record_tuples: List[Tuple[str, int, str]]
-_force_enable_logging(level, logger)
}
class logger {
<<logging.Logger>>
+debug(msg)
+info(msg)
+warning(msg)
+error(msg)
+critical(msg)
}
cleanup_disabled_logging ..> logging : "restores state"
logging_during_setup_and_teardown ..> caplog : "uses"
TestFilter ..> logging : "inherits"
caplog ..> logger : "captures logs from"
test_change_level ..> caplog : "uses"
test_with_statement_at_level ..> caplog : "uses"
test_with_statement_filtering ..> caplog : "uses filtering"
Summary
This file is a comprehensive test suite for pytest's `caplog` fixture that captures logging output during tests. It ensures robust and isolated logging capture behavior, correct handling of logging levels, filtering, and integration with pytest configuration. It also safeguards against global logging state pollution by resetting logging after tests. The tests verify both simple and complex scenarios including nested contexts, exceptions, and different test phases.
Example Usage Snippet
def test_logging_at_info_level(caplog):
caplog.set_level(logging.INFO)
logger.info("Info message")
logger.debug("Debug message") # should not be captured
assert "Info message" in caplog.text
assert "Debug message" not in caplog.text
def test_temporary_log_level(caplog):
with caplog.at_level(logging.WARNING):
logger.info("Info message") # not captured
logger.warning("Warning message") # captured
assert "Warning message" in caplog.text
assert "Info message" not in caplog.text