From ee1c719d2fd45aaa804bfbcf4c202c2f976df8f0 Mon Sep 17 00:00:00 2001 From: Quinn O'Donnell <90010678+Quinnod345@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:21:48 -0500 Subject: [PATCH] Preserve falsey supplied loggers and output files Choose the default logger and output stream only for None. Conforming objects with an empty record buffer or a false truth value must retain their identity and receive the requested debug and trace output. Add regressions for a falsey logger and both trace modes of a falsey writable stream. All three fail before the repair. The 69 configuration tests and 1687-test suite pass afterward on CPython3.13.11/macOS, with 100% line and branch coverage, source Ruff and strict-bytes Mypy. --- src/h2/config.py | 4 ++-- tests/test_config.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/h2/config.py b/src/h2/config.py index cbc3b1eaa..10b3d3e40 100644 --- a/src/h2/config.py +++ b/src/h2/config.py @@ -66,7 +66,7 @@ class OutputLogger: def __init__(self, file=None, trace_level=False) -> None: # type: ignore super().__init__() - self.file = file or sys.stderr + self.file = sys.stderr if file is None else file self.trace_level = trace_level def debug(self, fmtstr, *args) -> None: # type: ignore @@ -184,7 +184,7 @@ def __init__(self, self.split_outbound_cookies = split_outbound_cookies self.validate_inbound_headers = validate_inbound_headers self.normalize_inbound_headers = normalize_inbound_headers - self.logger = logger or DummyLogger(__name__) + self.logger = DummyLogger(__name__) if logger is None else logger @property def header_encoding(self) -> bool | str | None: diff --git a/tests/test_config.py b/tests/test_config.py index 5651f7bab..4533c97c1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,6 +3,7 @@ """ from __future__ import annotations +import io import logging import pytest @@ -129,6 +130,46 @@ def test_logger_instance_is_reflected(self) -> None: config.logger = logger assert config.logger is logger + def test_falsey_logger_is_used(self) -> None: + messages: list[str] = [] + + class EmptyLogger(h2.config.DummyLogger): + def __len__(self) -> int: + return len(messages) + + def debug(self, *args: object, **kwargs: object) -> None: + messages.append("debug") + + def trace(self, *args: object, **kwargs: object) -> None: + messages.append("trace") + + logger = EmptyLogger() + assert not logger + config = h2.config.H2Configuration(logger=logger) + + assert config.logger is logger + config.logger.debug("owned debug") + config.logger.trace("owned trace") + assert messages == ["debug", "trace"] + + @pytest.mark.parametrize("trace_level", [False, True]) + def test_falsey_file_is_used(self, capsys, trace_level) -> None: + class FalseyFile(io.StringIO): + def __bool__(self) -> bool: + return False + + with FalseyFile() as output: + logger = h2.config.OutputLogger(file=output, trace_level=trace_level) + logger.debug("owned debug") + logger.trace("owned trace") + + expected = "h2 (debug): owned debug\n" + if trace_level: + expected += "h2 (trace): owned trace\n" + assert output.getvalue() == expected + assert not output.closed + assert capsys.readouterr().err == "" + @pytest.mark.parametrize("trace_level", [False, True]) def test_output_logger(self, capsys, trace_level) -> None: logger = h2.config.OutputLogger(trace_level=trace_level)