Files
AnyPS5/tools/tests/test_pr_overlap_encoding.py
T
Rujul Talekar e991645c46 fix(tools): simulate cp1252 default encoding on Python 3.10 in pr_overlap tests
command_output patched subprocess._text_encoding, which does not exist on Python 3.10, so all eight tests errored before their assertions. Use subprocess._text_encoding where available and map io.TextIOWrapper locale to cp1252 on older interpreters, so a subprocess with no explicit encoding decodes as cp1252 on both. Add a behavioral check that default decodes as cp1252 while explicit utf-8 stays correct.

Authored by Rujul Talekar, with Muse Spark 1.3 assistance.
2026-10-12 13:02:14 +05:30

140 lines
5.6 KiB
Python

from contextlib import contextmanager
import io
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 simulated_cp1252():
if hasattr(subprocess, "_text_encoding"):
with patch.object(subprocess, "_text_encoding", return_value="cp1252"):
yield
else:
wrapper = io.TextIOWrapper
def fake_wrapper(buffer, encoding=None, *args, **kwargs):
if encoding == "locale":
encoding = "cp1252"
return wrapper(buffer, encoding=encoding, *args, **kwargs)
with patch.object(io, "TextIOWrapper", new=fake_wrapper):
yield
@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 simulated_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_default_encoding_uses_simulated_cp1252(self):
code = "import os;os.write(1," + repr(UNICODE.encode("utf-8")) + ")"
with simulated_cp1252():
default = RUN([sys.executable, "-c", code], capture_output=True, text=True, errors="replace")
self.assertEqual(default.stdout, UNICODE.encode("utf-8").decode("cp1252", errors="replace"))
explicit = RUN([sys.executable, "-c", code], capture_output=True, text=True, encoding="utf-8")
self.assertEqual(explicit.stdout, UNICODE)
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()