mirror of https://github.com/razor-ai/soup.git
122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
"""UTF-8 stdio bootstrap tests (v0.40.1 Part A).
|
|
|
|
Closes QA findings C1, C4, H1, N5, N8, G5 — all surface as Windows codepage
|
|
(cp1251 / cp1252) UnicodeEncodeError when Rich/Typer prints non-ASCII (β, ✓,
|
|
box-drawing, etc.).
|
|
|
|
The fix is a single CLI-bootstrap call that reconfigures stdout/stderr to
|
|
UTF-8 on Windows. POSIX is already UTF-8 so this is a no-op there.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
from soup_cli.utils import encoding as enc
|
|
|
|
|
|
def test_force_utf8_stdio_noop_on_posix():
|
|
"""POSIX terminals are UTF-8 already; bootstrap should not raise."""
|
|
with patch.object(enc.sys, "platform", "linux"):
|
|
# Should not raise even when stdout has no reconfigure.
|
|
enc.force_utf8_stdio()
|
|
|
|
|
|
def test_force_utf8_stdio_sets_pythonioencoding_on_windows(monkeypatch):
|
|
monkeypatch.delenv("PYTHONIOENCODING", raising=False)
|
|
monkeypatch.setattr(enc.sys, "platform", "win32")
|
|
|
|
# Provide stdout/stderr with .reconfigure that records the call.
|
|
calls: list[dict] = []
|
|
|
|
class FakeStream:
|
|
def reconfigure(self, **kwargs):
|
|
calls.append(kwargs)
|
|
|
|
monkeypatch.setattr(enc.sys, "stdout", FakeStream())
|
|
monkeypatch.setattr(enc.sys, "stderr", FakeStream())
|
|
|
|
enc.force_utf8_stdio()
|
|
|
|
assert os.environ.get("PYTHONIOENCODING") == "utf-8"
|
|
assert any(c.get("encoding") == "utf-8" for c in calls)
|
|
|
|
|
|
def test_force_utf8_stdio_does_not_override_existing_pythonioencoding(monkeypatch):
|
|
"""User-set PYTHONIOENCODING is preserved (we use setdefault)."""
|
|
monkeypatch.setenv("PYTHONIOENCODING", "latin-1")
|
|
monkeypatch.setattr(enc.sys, "platform", "win32")
|
|
|
|
class FakeStream:
|
|
def reconfigure(self, **kwargs):
|
|
pass
|
|
|
|
monkeypatch.setattr(enc.sys, "stdout", FakeStream())
|
|
monkeypatch.setattr(enc.sys, "stderr", FakeStream())
|
|
|
|
enc.force_utf8_stdio()
|
|
assert os.environ.get("PYTHONIOENCODING") == "latin-1"
|
|
|
|
|
|
def test_force_utf8_stdio_swallows_reconfigure_errors(monkeypatch):
|
|
"""Some streams (redirected to file objects without reconfigure) must not crash."""
|
|
monkeypatch.setattr(enc.sys, "platform", "win32")
|
|
|
|
class BadStream:
|
|
def reconfigure(self, **kwargs):
|
|
raise OSError("not a tty")
|
|
|
|
monkeypatch.setattr(enc.sys, "stdout", BadStream())
|
|
monkeypatch.setattr(enc.sys, "stderr", BadStream())
|
|
|
|
# Must not raise.
|
|
enc.force_utf8_stdio()
|
|
|
|
|
|
def test_force_utf8_stdio_handles_missing_reconfigure(monkeypatch):
|
|
"""Older Python or non-TextIO streams without .reconfigure must not crash."""
|
|
monkeypatch.setattr(enc.sys, "platform", "win32")
|
|
monkeypatch.setattr(enc.sys, "stdout", io.BytesIO()) # no reconfigure attr
|
|
monkeypatch.setattr(enc.sys, "stderr", io.BytesIO())
|
|
|
|
enc.force_utf8_stdio()
|
|
|
|
|
|
def test_cli_bootstrap_calls_force_utf8(monkeypatch):
|
|
"""soup_cli.cli imports `force_utf8_stdio` so it runs at module load."""
|
|
import soup_cli.cli as cli_mod
|
|
|
|
# Module-level guarantee: the symbol is imported.
|
|
assert hasattr(cli_mod, "_utf8_bootstrap_done") or hasattr(
|
|
cli_mod, "force_utf8_stdio"
|
|
) or "force_utf8_stdio" in dir(enc)
|
|
|
|
|
|
def test_writers_use_utf8_encoding():
|
|
"""Static check that hot-path writers pass encoding='utf-8' to open()."""
|
|
import pathlib
|
|
|
|
audited_files = [
|
|
"src/soup_cli/commands/quickstart.py",
|
|
"src/soup_cli/commands/init.py",
|
|
"src/soup_cli/commands/migrate.py",
|
|
]
|
|
repo_root = pathlib.Path(__file__).resolve().parent.parent
|
|
for rel in audited_files:
|
|
path = repo_root / rel
|
|
if not path.exists():
|
|
continue
|
|
text = path.read_text(encoding="utf-8")
|
|
# Every text-mode write should specify encoding.
|
|
# We grep for `open(...,"w"` patterns and assert utf-8 nearby.
|
|
# Permissive check: if file contains `open(` with `"w"` or `'w'`, it must
|
|
# also reference encoding="utf-8" in the same call (rough heuristic — full
|
|
# static verification is left to ruff + lint hooks).
|
|
# Just ensure the file at least mentions utf-8 if it contains text writes.
|
|
if 'open(' in text and (', "w"' in text or ", 'w'" in text):
|
|
assert 'encoding="utf-8"' in text or "encoding='utf-8'" in text, (
|
|
f"{rel} writes text without explicit utf-8 encoding"
|
|
)
|