gabriel / muse public
test_code_manifest.py python
202 lines 8.2 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 123 days ago
1 """Tests for the hierarchical code manifest in muse/plugins/code/manifest.py."""
2
3 import pathlib
4 import tempfile
5
6 import pytest
7
8 from muse.core.types import blob_id
9 from muse.core.object_store import object_path
10 from muse.core.paths import muse_dir
11 from muse.plugins.code.manifest import (
12 CodeManifest,
13 ManifestFileDiff,
14 build_code_manifest,
15 diff_manifests,
16 read_code_manifest,
17 write_code_manifest,
18 )
19
20
21 # ---------------------------------------------------------------------------
22 # Helpers
23 # ---------------------------------------------------------------------------
24
25
26 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
27 dot_muse = muse_dir(tmp_path)
28 dot_muse.mkdir()
29 (dot_muse / "objects").mkdir()
30 return tmp_path
31
32
33 def _write_object(root: pathlib.Path, content: bytes) -> str:
34 oid = blob_id(content)
35 obj_file = object_path(root, oid)
36 obj_file.parent.mkdir(parents=True, exist_ok=True)
37 obj_file.write_bytes(content)
38 return oid
39
40
41 # ---------------------------------------------------------------------------
42 # build_code_manifest
43 # ---------------------------------------------------------------------------
44
45
46 class TestBuildCodeManifest:
47 def test_empty_snapshot(self) -> None:
48 with tempfile.TemporaryDirectory() as tmp:
49 root = _make_repo(pathlib.Path(tmp))
50 manifest = build_code_manifest("s" * 64, {}, root)
51 assert manifest["snapshot_id"] == "s" * 64
52 assert manifest["total_files"] == 0
53 assert manifest["packages"] == []
54 assert manifest["total_symbols"] == 0
55
56 def test_single_python_file(self) -> None:
57 with tempfile.TemporaryDirectory() as tmp:
58 root = _make_repo(pathlib.Path(tmp))
59 src = b"def foo():\n return 1\n"
60 h = _write_object(root, src)
61 manifest = build_code_manifest("s" * 64, {"src/utils.py": h}, root)
62 assert manifest["total_files"] == 1
63 assert manifest["semantic_files"] >= 1
64 assert len(manifest["packages"]) == 1
65 pkg = manifest["packages"][0]
66 assert pkg["package"] == "src"
67 assert len(pkg["modules"]) == 1
68 mod = pkg["modules"][0]
69 assert mod["module_path"] == "src/utils.py"
70 assert mod["language"] == "Python"
71
72 def test_groups_by_package(self) -> None:
73 with tempfile.TemporaryDirectory() as tmp:
74 root = _make_repo(pathlib.Path(tmp))
75 h1 = _write_object(root, b"x = 1\n")
76 h2 = _write_object(root, b"y = 2\n")
77 h3 = _write_object(root, b"z = 3\n")
78 flat = {
79 "src/a.py": h1,
80 "src/b.py": h2,
81 "tests/c.py": h3,
82 }
83 manifest = build_code_manifest("s" * 64, flat, root)
84 assert manifest["total_files"] == 3
85 packages = {pkg["package"] for pkg in manifest["packages"]}
86 assert "src" in packages
87 assert "tests" in packages
88
89 def test_manifest_hash_stable(self) -> None:
90 with tempfile.TemporaryDirectory() as tmp:
91 root = _make_repo(pathlib.Path(tmp))
92 src = b"x = 1\n"
93 h = _write_object(root, src)
94 m1 = build_code_manifest("s" * 64, {"a.py": h}, root)
95 m2 = build_code_manifest("s" * 64, {"a.py": h}, root)
96 assert m1["manifest_hash"] == m2["manifest_hash"]
97
98 def test_non_semantic_file_has_empty_ast_hash(self) -> None:
99 with tempfile.TemporaryDirectory() as tmp:
100 root = _make_repo(pathlib.Path(tmp))
101 h = _write_object(root, b"some binary or text content")
102 manifest = build_code_manifest("s" * 64, {"README.md": h}, root)
103 mod = manifest["packages"][0]["modules"][0]
104 assert mod["ast_hash"] == ""
105 assert mod["symbol_count"] == 0
106
107
108 # ---------------------------------------------------------------------------
109 # diff_manifests
110 # ---------------------------------------------------------------------------
111
112
113 class TestDiffManifests:
114 def _build_simple(self, root: pathlib.Path, files: _FileStore) -> CodeManifest:
115 flat: Manifest = {}
116 for path, content in files.items():
117 flat[path] = _write_object(root, content)
118 return build_code_manifest("snap", flat, root)
119
120 def test_identical_manifests_no_diff(self) -> None:
121 with tempfile.TemporaryDirectory() as tmp:
122 root = _make_repo(pathlib.Path(tmp))
123 base = self._build_simple(root, {"a.py": b"x = 1\n"})
124 diffs = diff_manifests(base, base)
125 assert diffs == []
126
127 def test_added_file_detected(self) -> None:
128 with tempfile.TemporaryDirectory() as tmp:
129 root = _make_repo(pathlib.Path(tmp))
130 base = self._build_simple(root, {"a.py": b"x = 1\n"})
131 target = self._build_simple(root, {"a.py": b"x = 1\n", "b.py": b"y = 2\n"})
132 diffs = diff_manifests(base, target)
133 added = [d for d in diffs if d["change"] == "added"]
134 assert any(d["path"] == "b.py" for d in added)
135
136 def test_removed_file_detected(self) -> None:
137 with tempfile.TemporaryDirectory() as tmp:
138 root = _make_repo(pathlib.Path(tmp))
139 base = self._build_simple(root, {"a.py": b"x = 1\n", "b.py": b"y = 2\n"})
140 target = self._build_simple(root, {"a.py": b"x = 1\n"})
141 diffs = diff_manifests(base, target)
142 removed = [d for d in diffs if d["change"] == "removed"]
143 assert any(d["path"] == "b.py" for d in removed)
144
145 def test_semantic_change_detected(self) -> None:
146 with tempfile.TemporaryDirectory() as tmp:
147 root = _make_repo(pathlib.Path(tmp))
148 base = self._build_simple(root, {"a.py": b"def foo():\n return 1\n"})
149 target = self._build_simple(root, {"a.py": b"def foo():\n return 2\n"})
150 diffs = diff_manifests(base, target)
151 assert len(diffs) == 1
152 assert diffs[0]["semantic_change"] is True
153
154 def test_whitespace_only_change_non_semantic(self) -> None:
155 # Whitespace-only changes: content_hash differs but ast_hash should be the same.
156 with tempfile.TemporaryDirectory() as tmp:
157 root = _make_repo(pathlib.Path(tmp))
158 base = self._build_simple(root, {"a.py": b"def foo():\n return 1\n"})
159 target = self._build_simple(root, {"a.py": b"def foo():\n return 1\n\n\n"})
160 diffs = diff_manifests(base, target)
161 # Whitespace diff may or may not change AST hash depending on parser.
162 # Just assert we get a diff record with a path.
163 if diffs:
164 assert diffs[0]["path"] == "a.py"
165
166
167 # ---------------------------------------------------------------------------
168 # Persistence
169 # ---------------------------------------------------------------------------
170
171
172 class TestManifestPersistence:
173 def test_write_and_read_roundtrip(self) -> None:
174 with tempfile.TemporaryDirectory() as tmp:
175 root = _make_repo(pathlib.Path(tmp))
176 src = b"def my_fn():\n pass\n"
177 h = _write_object(root, src)
178 original = build_code_manifest("s" * 64, {"src/a.py": h}, root)
179
180 write_code_manifest(root, original)
181 loaded = read_code_manifest(root, original["manifest_hash"])
182
183 assert loaded is not None
184 assert loaded["snapshot_id"] == "s" * 64
185 assert loaded["manifest_hash"] == original["manifest_hash"]
186 assert len(loaded["packages"]) == len(original["packages"])
187
188 def test_read_nonexistent_returns_none(self) -> None:
189 with tempfile.TemporaryDirectory() as tmp:
190 root = _make_repo(pathlib.Path(tmp))
191 result = read_code_manifest(root, "nonexistent_hash")
192 assert result is None
193
194 def test_write_idempotent(self) -> None:
195 with tempfile.TemporaryDirectory() as tmp:
196 root = _make_repo(pathlib.Path(tmp))
197 h = _write_object(root, b"x = 1\n")
198 manifest = build_code_manifest("s" * 64, {"a.py": h}, root)
199 write_code_manifest(root, manifest)
200 write_code_manifest(root, manifest) # second write should not error
201 loaded = read_code_manifest(root, manifest["manifest_hash"])
202 assert loaded is not None
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 123 days ago