From e4aaef72ae104e95fc00bc8ac6373261d8485d4e Mon Sep 17 00:00:00 2001 From: Visio Vanitas Date: Fri, 11 Sep 2026 21:47:17 +0800 Subject: [PATCH] fix(tools): read test metadata as UTF-8 with path-aware decode errors Signed-off-by: Visio Vanitas --- tools/test_contracts.py | 4 +-- tools/test_test_contracts.py | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/tools/test_contracts.py b/tools/test_contracts.py index 7ebe3c0b..09b1c088 100644 --- a/tools/test_contracts.py +++ b/tools/test_contracts.py @@ -104,8 +104,8 @@ def parse_test_metadata(path: Path, display_path: str | None = None) -> TestMeta marker = "// wave-test:" try: - lines = path.read_text().splitlines() - except OSError as error: + lines = path.read_text(encoding="utf-8").splitlines() + except (OSError, UnicodeDecodeError) as error: raise ValueError(f"failed to read wave-test metadata from {display}: {error}") from error for line in lines: diff --git a/tools/test_test_contracts.py b/tools/test_test_contracts.py index 16b83e88..5a4232f4 100644 --- a/tools/test_test_contracts.py +++ b/tools/test_test_contracts.py @@ -10,7 +10,10 @@ # SPDX-License-Identifier: MPL-2.0 # AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. +import os from pathlib import Path +import subprocess +import sys import tempfile import unittest @@ -99,6 +102,59 @@ def test_artifact_expectations_require_matching_emit_and_architecture(self): with self.assertRaises(ValueError): self.parse(directory, body) + def test_utf8_metadata_read_with_non_ascii_comments(self): + with tempfile.TemporaryDirectory() as directory: + source = Path(directory) / "case.wave" + source.write_bytes( + "// wave-test: mode=check\n// non-ascii comment: 測試 é\nfun main() {}\n".encode( + "utf-8" + ) + ) + metadata = parse_test_metadata(source, "cases/shared/test1.wave") + self.assertEqual(metadata.mode, "check") + + def test_utf8_metadata_read_independent_of_ascii_host_locale(self): + with tempfile.TemporaryDirectory() as directory: + source = Path(directory) / "case.wave" + source.write_bytes( + "// wave-test: mode=check\n// non-ascii comment: 測試 é\nfun main() {}\n".encode( + "utf-8" + ) + ) + command = [ + sys.executable, + "-c", + "import sys; from pathlib import Path; " + "from tools.test_contracts import parse_test_metadata; " + "meta = parse_test_metadata(Path(sys.argv[1]), 'case.wave'); " + "sys.stdout.write(meta.mode)", + str(source), + ] + env = {**os.environ, "LC_ALL": "C", "PYTHONUTF8": "0"} + process = subprocess.run( + command, + capture_output=True, + text=True, + env=env, + ) + self.assertEqual(process.returncode, 0, process.stderr) + self.assertEqual(process.stdout, "check") + + def test_invalid_utf8_bytes_reports_display_path_in_value_error(self): + with tempfile.TemporaryDirectory() as directory: + source = Path(directory) / "case.wave" + source.write_bytes(b"// wave-test: mode=check\n// \xff\n") + with self.assertRaises(ValueError) as context: + parse_test_metadata(source, "shared/case.wave") + self.assertIn("failed to read wave-test metadata from shared/case.wave:", str(context.exception)) + self.assertIsInstance(context.exception.__cause__, UnicodeDecodeError) + + # Also verify fallback to str(source) when display_path is omitted + with self.assertRaises(ValueError) as context_default: + parse_test_metadata(source) + self.assertIn(f"failed to read wave-test metadata from {source}:", str(context_default.exception)) + self.assertIsInstance(context_default.exception.__cause__, UnicodeDecodeError) + class ArtifactContractTests(unittest.TestCase): def setUp(self):