Skip to content

Commit 75d7ab6

Browse files
committed
fix windows testes folder
1 parent cc97a32 commit 75d7ab6

4 files changed

Lines changed: 94 additions & 0 deletions

File tree

src/lfx/src/lfx/log/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Logging module for lfx package."""
22

3+
from lfx.log._streams import make_streams_resilient
34
from lfx.log.logger import configure, logger
45

6+
make_streams_resilient()
7+
58
__all__ = ["configure", "logger"]

src/lfx/src/lfx/log/_streams.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Make process stdout/stderr tolerate characters their console codec cannot encode."""
2+
3+
import contextlib
4+
import sys
5+
from typing import Any
6+
7+
8+
def _reconfigure(stream: Any) -> None:
9+
reconfigure = getattr(stream, "reconfigure", None)
10+
if reconfigure is None:
11+
return
12+
with contextlib.suppress(Exception):
13+
reconfigure(errors="backslashreplace")
14+
15+
16+
def make_streams_resilient() -> None:
17+
"""Escape unencodable characters on write instead of raising.
18+
19+
Windows consoles default to a legacy code page (cp1252) whose codec raises
20+
UnicodeEncodeError on the box-drawing glyphs structlog's ConsoleRenderer
21+
emits when formatting a traceback. That second exception is raised inside
22+
the logging call itself, so it masks the original error and aborts the
23+
request that logged it. ``errors="backslashreplace"`` keeps the stream
24+
encoding but escapes anything the code page can't represent, so logging
25+
never crashes the caller. The underlying streams are reconfigured too
26+
because colorama wraps those same objects, regardless of wrap order.
27+
"""
28+
for stream in (sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__):
29+
_reconfigure(stream)

src/lfx/tests/unit/log/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Test suite for LFX logging."""
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Regression tests for stdout/stderr encoding resilience on Windows consoles."""
2+
3+
import importlib
4+
import io
5+
6+
import structlog
7+
from lfx.log._streams import make_streams_resilient
8+
9+
10+
def _cp1252_stream() -> tuple[io.TextIOWrapper, io.BytesIO]:
11+
"""A text stream that behaves like a strict Windows cp1252 console."""
12+
raw = io.BytesIO()
13+
return io.TextIOWrapper(raw, encoding="cp1252"), raw
14+
15+
16+
def test_make_streams_resilient_sets_backslashreplace(monkeypatch):
17+
out, _ = _cp1252_stream()
18+
err, _ = _cp1252_stream()
19+
monkeypatch.setattr("sys.stdout", out)
20+
monkeypatch.setattr("sys.stderr", err)
21+
22+
make_streams_resilient()
23+
24+
assert out.errors == "backslashreplace"
25+
assert err.errors == "backslashreplace"
26+
27+
28+
def test_logging_exception_to_cp1252_stdout_does_not_crash(monkeypatch):
29+
"""A logged traceback must never raise UnicodeEncodeError on a cp1252 console.
30+
31+
structlog's ConsoleRenderer emits box-drawing glyphs that cp1252 cannot
32+
encode; without the resilience shim the encode error is raised inside the
33+
logging call, masking the original error and aborting the request.
34+
"""
35+
stream, raw = _cp1252_stream()
36+
monkeypatch.setattr("sys.stdout", stream)
37+
38+
make_streams_resilient()
39+
40+
logmod = importlib.import_module("lfx.log.logger")
41+
logmod.configure(log_level="ERROR", cache=False)
42+
log = structlog.get_logger("test")
43+
44+
msg = "THIS IS A TEST ERROR MESSAGE"
45+
try:
46+
raise ValueError(msg)
47+
except ValueError:
48+
log.exception("boom")
49+
50+
stream.flush()
51+
rendered = raw.getvalue().decode("cp1252", errors="replace")
52+
assert "THIS IS A TEST ERROR MESSAGE" in rendered
53+
54+
55+
def test_reconfigure_is_safe_when_stream_is_none(monkeypatch):
56+
monkeypatch.setattr("sys.stdout", None)
57+
monkeypatch.setattr("sys.stderr", None)
58+
monkeypatch.setattr("sys.__stdout__", None)
59+
monkeypatch.setattr("sys.__stderr__", None)
60+
61+
make_streams_resilient()

0 commit comments

Comments
 (0)