From 4571b9c6cb0876bce459f816698640072806f0e3 Mon Sep 17 00:00:00 2001 From: SunsetDrifter Date: Thu, 8 Oct 2026 10:57:50 +0200 Subject: [PATCH] fix: stop writing bytecode caches in installs and the test suite Any lint run on a normal Python created wikilint/__pycache__/ in the install. The template ships no .gitignore, so the agent's next `git add -A` would commit bytecode into the wiki's history. lint.py now sets sys.dont_write_bytecode before importing the engine; recompiling ~1,900 lines per run costs milliseconds. The suite also littered the template: test_variants' py_compile wrote wiki/__pycache__/lint.*.pyc, which then surfaced as an unknown top-level entry when linting the template. It now uses builtin compile(), and helpers.py disables bytecode writes for the whole run. --- tests/helpers.py | 3 +++ tests/test_no_bytecode.py | 44 +++++++++++++++++++++++++++++++++++++++ tests/test_variants.py | 7 +++---- wiki/lint.py | 3 +++ 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 tests/test_no_bytecode.py diff --git a/tests/helpers.py b/tests/helpers.py index 3d47fe6..93f6951 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -4,6 +4,9 @@ from datetime import date, timedelta from pathlib import Path +# Tests import the template's own files; never leave bytecode inside it. +sys.dont_write_bytecode = True + REPO = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO / "wiki")) diff --git a/tests/test_no_bytecode.py b/tests/test_no_bytecode.py new file mode 100644 index 0000000..c364a15 --- /dev/null +++ b/tests/test_no_bytecode.py @@ -0,0 +1,44 @@ +"""Lint runs must leave no bytecode behind. The template ships no +.gitignore, so a wikilint/__pycache__/ written by a lint run would be +swept into the wiki's history by the agent's next `git add -A`; and the +test suite must not litter the template it tests.""" + +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +from helpers import REPO + + +def pycache_dirs(root): + return sorted(p.relative_to(root).as_posix() for p in Path(root).rglob("__pycache__")) + + +class TestNoBytecode(unittest.TestCase): + def test_lint_run_writes_no_pycache_in_an_install(self): + with tempfile.TemporaryDirectory() as tmp: + install = Path(tmp) / "w" + shutil.copytree(REPO / "wiki", install, + ignore=shutil.ignore_patterns("__pycache__")) + (install / "raw" / "inbox").mkdir(parents=True) + result = subprocess.run( + [sys.executable, "lint.py", "check"], cwd=install, + capture_output=True, text=True, + env={"PATH": "/usr/bin:/bin"}, # no inherited PYTHONDONTWRITEBYTECODE + ) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(pycache_dirs(install), []) + + def test_suite_does_not_write_bytecode(self): + self.assertTrue(sys.dont_write_bytecode) + + def test_compile_check_writes_nothing(self): + source = (REPO / "tests" / "test_variants.py").read_text() + self.assertNotIn("py_compile", source) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_variants.py b/tests/test_variants.py index a6b7e31..4518d39 100644 --- a/tests/test_variants.py +++ b/tests/test_variants.py @@ -2,7 +2,6 @@ hook correctness properties, for the shipped template and the feature-named extension configs in extension_configs.py.""" -import py_compile import unittest from helpers import REPO, VARIANTS, load_variant_config @@ -13,10 +12,10 @@ class TestEngineIdentity(unittest.TestCase): def test_everything_compiles(self): + # Builtin compile() checks syntax without writing .pyc files. for variant in VARIANTS: - py_compile.compile(str(REPO / variant / "lint.py"), doraise=True) - for p in (REPO / variant / "wikilint").glob("*.py"): - py_compile.compile(str(p), doraise=True) + for p in [REPO / variant / "lint.py", *(REPO / variant / "wikilint").glob("*.py")]: + compile(p.read_text(encoding="utf-8"), str(p), "exec") class TestConfigSanity(unittest.TestCase): diff --git a/wiki/lint.py b/wiki/lint.py index 7b32128..4829514 100644 --- a/wiki/lint.py +++ b/wiki/lint.py @@ -102,5 +102,8 @@ def index_entry_extra(fields): if __name__ == "__main__": import sys + # No __pycache__: the wiki repo ships no .gitignore, so cached bytecode + # would land in history on the next `git add -A`. Recompiling is cheap. + sys.dont_write_bytecode = True from wikilint import main sys.exit(main(CONFIG, index_entry_extra))