mirror of
https://github.com/soxoj/maigret.git
synced 2026-09-22 17:54:55 +02:00
* fix: keep result lines from crashing on a non-UTF-8 stdout #3050 stopped the startup banners from raising UnicodeEncodeError on a Windows console, but the banner was not the only line the ANSI codepage can break. Two ordinary things a run prints still went through a bare print(): - the --enrich notifications built in checking.py carry U+2192, which cp1252 has no slot for (checking.py:1847, 1851, 1859, 1899); - any profile field maigret extracts carries the script the page was written in, so a Cyrillic or CJK fullname breaks the result line that reports it. Both arrive mid-scan, after the network work is done, so the crash discarded results the run had already collected -- worse than failing at startup. Route every write in notify.py through the existing _print_encodable helper. Verified on a real cp1252 stdout, not a mock: both cases raise before this change and print with replacement characters after it. * fix: keep the AI spinner alive on a stderr that cannot encode braille Second half of #3055. The spinner frames are braille, which cp1252 -- the ANSI codepage of a stock Windows install -- cannot encode, so the first frame raised UnicodeEncodeError inside the daemon thread: the thread died with a traceback printed over the output and the animation stopped for the rest of the run. Encoding with replacement would only leave a row of '?' spinning, so pick the frame set from what the stream can carry instead. A UTF-8 terminal keeps the braille; anything that cannot hold it gets |/-\ and still animates. print_streaming() in the same module writes model output the same way and would raise on any non-Latin reply, but it has no callers anywhere in the tree, so it is left alone rather than fixed as dead code.
81 lines
3.0 KiB
Python
81 lines
3.0 KiB
Python
"""Tests for maigret.ai terminal output."""
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def _run_probe(tmp_path, name, lines, encoding):
|
|
"""Run a probe in a child process whose streams really use `encoding`.
|
|
|
|
Forcing the encoding rather than mocking the failure is deliberate: this then
|
|
fails on any machine if the guard goes, the same way the banner test does.
|
|
"""
|
|
probe = tmp_path / name
|
|
probe.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
env = dict(os.environ)
|
|
env["PYTHONIOENCODING"] = encoding
|
|
return subprocess.run(
|
|
[sys.executable, str(probe)],
|
|
capture_output=True,
|
|
encoding=encoding,
|
|
errors="replace",
|
|
env=env,
|
|
cwd=str(tmp_path),
|
|
)
|
|
|
|
|
|
def test_spinner_renders_on_a_stderr_that_cannot_encode_braille(tmp_path):
|
|
"""The spinner frames are braille, which cp1252 -- the ANSI codepage of a
|
|
stock Windows install -- cannot encode. Writing one raised UnicodeEncodeError
|
|
inside the spinner's daemon thread: the thread died with a traceback printed
|
|
over the output, and the animation stopped for the rest of the run.
|
|
"""
|
|
result = _run_probe(
|
|
tmp_path,
|
|
"spinner_probe.py",
|
|
[
|
|
"import sys, time",
|
|
"from maigret.ai import _Spinner",
|
|
"",
|
|
"spinner = _Spinner('probe')",
|
|
"spinner.start()",
|
|
"time.sleep(0.3)",
|
|
"spinner.stop()",
|
|
"sys.stdout.write('FRAMES=' + ''.join(spinner._frames) + chr(10))",
|
|
"sys.stdout.write('STILL_RUNNING=' + str(spinner._thread.is_alive()) + chr(10))",
|
|
"sys.stdout.write('REACHED_END' + chr(10))",
|
|
],
|
|
"cp1252",
|
|
)
|
|
|
|
assert result.returncode == 0, f"the spinner must not crash the run: stderr={result.stderr!r}"
|
|
assert "REACHED_END" in result.stdout, f"stdout={result.stdout!r}"
|
|
# The daemon thread swallows nothing: an encode failure surfaces as a
|
|
# traceback on stderr and leaves the spinner dead for the rest of the run.
|
|
assert "UnicodeEncodeError" not in result.stderr, f"stderr={result.stderr!r}"
|
|
assert "Exception in thread" not in result.stderr, f"stderr={result.stderr!r}"
|
|
# It still animates, using frames the stream can carry.
|
|
assert "FRAMES=|/-\\" in result.stdout, f"stdout={result.stdout!r}"
|
|
|
|
|
|
def test_spinner_keeps_its_braille_when_the_stream_can_encode_it(tmp_path):
|
|
"""The frame set is chosen from the stream, not swapped unconditionally: a
|
|
UTF-8 terminal must still get the original animation."""
|
|
result = _run_probe(
|
|
tmp_path,
|
|
"spinner_utf8_probe.py",
|
|
[
|
|
"import sys",
|
|
"from maigret.ai import _Spinner",
|
|
"",
|
|
"sys.stdout.write('FRAMES=' + ''.join(_Spinner('probe')._frames) + chr(10))",
|
|
],
|
|
"utf-8",
|
|
)
|
|
|
|
assert result.returncode == 0, f"stderr={result.stderr!r}"
|
|
braille = "".join(["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
|
|
assert f"FRAMES={braille}" in result.stdout, f"stdout={result.stdout!r}"
|