From 7ec76f730a54502be5479fbf3c46f1665912f139 Mon Sep 17 00:00:00 2001 From: DonRyuzaki <78573173+DonRyuzaki@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:24:24 +0000 Subject: [PATCH] fix(tools): preserve Unicode cache separators --- tools/configure_seed.py | 2 +- tools/tests/test_compare_configure_checks.py | 16 +++++++ tools/tests/test_configure_seed.py | 50 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/tools/configure_seed.py b/tools/configure_seed.py index 96c95b7d5f..4a7dec4c7f 100644 --- a/tools/configure_seed.py +++ b/tools/configure_seed.py @@ -15,7 +15,7 @@ QUOTED_DELIMITER = re.compile(r'":[A-Z]+=') def checks(cache): found, doc = {}, [] - for line in Path(cache).read_text(encoding="utf-8-sig").splitlines(): + for line in Path(cache).read_text(encoding="utf-8-sig").split("\n"): if line.startswith("//"): doc.append(line) continue diff --git a/tools/tests/test_compare_configure_checks.py b/tools/tests/test_compare_configure_checks.py index 2f82528a1a..d44e1bbb9d 100644 --- a/tools/tests/test_compare_configure_checks.py +++ b/tools/tests/test_compare_configure_checks.py @@ -47,6 +47,22 @@ class Tests(unittest.TestCase): self.assertEqual(r.returncode,2) self.assertEqual(r.stdout,"") + def test_value_changes_after_unicode_separators_are_reported(self): + baseline, candidate = self.root / "baseline", self.root / "candidate" + for separator in ("\u0085", "\u2028", "\u2029"): + with self.subTest(separator=separator): + before, after = f"prefix{separator}before", f"prefix{separator}after" + baseline.write_text(f"//Test FEATURE\nFEATURE:INTERNAL={before}\n", encoding="utf-8") + candidate.write_text(f"//Test FEATURE\nFEATURE:INTERNAL={after}\n", encoding="utf-8") + result = self.run_tool(baseline, candidate) + self.assertEqual(result.returncode, 1, result.stderr) + self.assertEqual(json.loads(result.stdout), { + "added": [], "removed": [], "changed": [{ + "name": "FEATURE", "before": {"value": before, "help": "Test FEATURE"}, + "after": {"value": after, "help": "Test FEATURE"}, + }], + }) + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_configure_seed.py b/tools/tests/test_configure_seed.py index dba19da350..c3bbe0f463 100644 --- a/tools/tests/test_configure_seed.py +++ b/tools/tests/test_configure_seed.py @@ -327,6 +327,56 @@ class ConfigureSeedRoundTripTests(ConfigureSeedProject): class ChecksParsingTests(unittest.TestCase): + def test_unicode_line_separators_are_preserved_in_cache_fields(self): + entries = { + f"FEATURE_{index}_{separator}_TAIL": (f"before{separator}after", f"Test help{separator}tail") + for index, separator in enumerate(("\u0085", "\u2028", "\u2029")) + } + with tempfile.TemporaryDirectory() as directory: + cache = Path(directory) / "CMakeCache.txt" + for newline in ("\n", "\r\n"): + for encoding in ("utf-8", "utf-8-sig"): + with self.subTest(newline=newline, encoding=encoding): + text = newline.join(f"//{doc}{newline}{name}:INTERNAL={value}" + for name, (value, doc) in entries.items()) + cache.write_bytes(text.encode(encoding)) + self.assertEqual(configure_seed.checks(cache), entries) + + @unittest.skipUnless(shutil.which("cmake") and shutil.which("ninja"), "CMake and Ninja are required for cache round trips") + def test_unicode_cache_fields_survive_real_cmake_seed_round_trip(self): + entries = { + f"FEATURE_{index}_{separator}_TAIL": (f"before{separator}after", f"Test help{separator}tail") + for index, separator in enumerate(("\u0085", "\u2028", "\u2029")) + } + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "source" + source.mkdir() + lines = ["cmake_minimum_required(VERSION 3.20)", "project(unicode_cache NONE)"] + for index, (name, (value, doc)) in enumerate(entries.items()): + lines.extend(("if(NOT REQUIRE_SEED)", + f"set({configure_seed.quote(name)} {configure_seed.quote(value)} CACHE INTERNAL {configure_seed.quote(doc)})", + "endif()", + f"get_property(help CACHE {configure_seed.quote(name)} PROPERTY HELPSTRING)", + f"get_property(value CACHE {configure_seed.quote(name)} PROPERTY VALUE)", + f'file(WRITE "${{CMAKE_BINARY_DIR}}/{index}.help" "${{help}}")', + f'file(WRITE "${{CMAKE_BINARY_DIR}}/{index}.value" "${{value}}")')) + (source / "CMakeLists.txt").write_text("\n".join(lines) + "\n", encoding="utf-8") + first, second = root / "first", root / "second" + configured = subprocess.run(["cmake", "-S", str(source), "-B", str(first), "-G", "Ninja"], capture_output=True) + self.assertEqual(configured.returncode, 0, configured.stderr.decode("utf-8", errors="replace")) + cache = first / "CMakeCache.txt" + self.assertEqual(configure_seed.checks(cache), entries) + seed = root / "seed.cmake" + subprocess.run([sys.executable, str(TOOL), str(cache), str(seed)], check=True, capture_output=True) + subprocess.run(["cmake", "-C", str(seed), "-DREQUIRE_SEED=ON", "-S", str(source), "-B", str(second), "-G", "Ninja"], + check=True, capture_output=True) + self.assertEqual(configure_seed.checks(second / "CMakeCache.txt"), entries) + for index, (value, doc) in enumerate(entries.values()): + for build in (first, second): + self.assertEqual((build / f"{index}.value").read_text(encoding="utf-8"), value) + self.assertEqual((build / f"{index}.help").read_text(encoding="utf-8"), doc) + def test_architecture_dependent_sizes_leave_compile_guards_to_cmake(self): entries = { "ARCH_SIZE": ("0", "CHECK_TYPE_SIZE: sizeof(architecture_type)"),