Files
AnyPS5/tools/tests/test_pr_overlap_encoding.py
T
GalaxyGamingFRandClaude Fable 5.1 15f34d3043 fix(tools): decode pr_overlap git output with errors=replace
One PR diff containing a byte that is not valid UTF-8 made git() and
merge() raise, which aborted the hourly run and dropped the overlap
comment on every PR. Both subprocess calls now pass errors="replace",
matching check_conventions.py. A test commits a Latin-1 byte and checks
the export is still found.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
2026-10-11 02:42:08 -07:00

114 lines
4.6 KiB
Python

from contextlib import contextmanager
import json
import os
from pathlib import Path
import subprocess
import sys
from tempfile import TemporaryDirectory
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import pr_overlap
RUN = subprocess.run
UNICODE = "Quoted \u201cmetadata\u201d, Y\u014dtei and \U0001f3ae"
TREE = "a" * 40
@contextmanager
def command_output(stdout, stderr="", returncode=0):
code = ("import os, sys\n"
f"os.write(1, {stdout.encode('utf-8')!r})\n"
f"os.write(2, {stderr.encode('utf-8')!r})\n"
f"sys.exit({returncode})\n")
def run(command, **kwargs):
return RUN([sys.executable, "-c", code], **kwargs)
with patch.object(subprocess, "_text_encoding", return_value="cp1252"), \
patch.object(subprocess, "run", side_effect=run):
yield
class EncodingTests(unittest.TestCase):
def test_gh_json_preserves_unicode(self):
response = [{"number": 1, "body": UNICODE}]
with command_output(json.dumps(response, ensure_ascii=False)):
self.assertEqual(json.loads(pr_overlap.gh("pr", "list", "--json", "number,body")), response)
def test_ascii_gh_json(self):
response = [{"number": 1, "body": "Ordinary metadata"}]
with command_output(json.dumps(response)):
self.assertEqual(json.loads(pr_overlap.gh("pr", "list", "--json", "number,body")), response)
def test_gh_error_preserves_unicode(self):
with command_output("", UNICODE, 1):
with self.assertRaises(subprocess.CalledProcessError) as raised:
pr_overlap.gh("pr", "list")
self.assertEqual(raised.exception.returncode, 1)
self.assertEqual(raised.exception.stderr, UNICODE)
def test_git_diff_preserves_unicode(self):
diff = "@@ -1 +1 @@\n-old\n+" + UNICODE + "\n"
with command_output(diff):
self.assertEqual(pr_overlap.git("diff", "--no-color"), diff)
def test_git_error_preserves_unicode(self):
with command_output("", UNICODE, 128):
with self.assertRaises(subprocess.CalledProcessError) as raised:
pr_overlap.git("show", "HEAD")
self.assertEqual(raised.exception.returncode, 128)
self.assertEqual(raised.exception.stderr, UNICODE)
def test_merge_conflict_preserves_unicode_paths(self):
path = UNICODE + ".txt"
with command_output(TREE + "\n" + path + "\n", returncode=1):
self.assertEqual(pr_overlap.merge("main", "topic"), (TREE, {path}))
def test_merge_success(self):
with command_output(TREE + "\n"):
self.assertEqual(pr_overlap.merge("main", "topic"), (TREE, set()))
def test_merge_error_preserves_unicode(self):
with command_output("", UNICODE, 128):
with self.assertRaisesRegex(RuntimeError, UNICODE):
pr_overlap.merge("main", "topic")
def test_git_diff_survives_invalid_utf8(self):
with TemporaryDirectory() as directory:
repo = Path(directory)
source = repo / "core" / "libs" / "prx" / "x" / "a.cpp"
source.parent.mkdir(parents=True)
ident = ["-c", "user.name=t", "-c", "user.email=t@example.test"]
for args in (["init", "-q"], ["commit", "-q", "--allow-empty", "-m", "base"]):
subprocess.run(["git", *ident, *args], cwd=repo, check=True, capture_output=True)
source.write_bytes(b"int APS5_VABI f() {} // caf\xe9\n")
for args in (["add", "."], ["commit", "-q", "-m", "add"]):
subprocess.run(["git", *ident, *args], cwd=repo, check=True, capture_output=True)
old = os.getcwd()
os.chdir(repo)
try:
diff = pr_overlap.git("diff", "-U0", "--no-color", "HEAD~1", "HEAD", "--", "core/libs/prx/*.cpp")
finally:
os.chdir(old)
self.assertEqual(pr_overlap.exports(diff), {"f"})
def test_report_is_written_as_utf8(self):
with TemporaryDirectory() as directory:
path = Path(directory) / "1.md"
write_text = Path.write_text
def write_with_utf8(path, text, *args, **kwargs):
self.assertEqual(kwargs.get("encoding"), "utf-8")
return write_text(path, text, *args, **kwargs)
with patch.object(Path, "write_text", new=write_with_utf8), \
patch.object(pr_overlap, "report", return_value=UNICODE):
pr_overlap.write_reports(Path(directory), {1: {}}, "https://example.test")
self.assertEqual(path.read_bytes(), UNICODE.encode("utf-8"))
if __name__ == "__main__":
unittest.main()