gabriel / muse public
test_stress_store_provenance.py python
444 lines 15.7 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
1 """Stress tests for CommitRecord, SnapshotRecord, TagRecord, and provenance fields.
2
3 Covers:
4 - CommitRecord round-trip through to_dict/from_dict for all format versions.
5 - format_version evolution: missing fields default correctly when reading old records.
6 - reviewed_by (ORSet semantics): list preserved, sorted, deduplicated via overwrite_commit.
7 - test_runs (GCounter semantics): monotonically increases via overwrite_commit.
8 - agent_id / model_id / toolchain_id / prompt_hash / signature fields.
9 - SnapshotRecord round-trip with large manifests.
10 - TagRecord round-trip.
11 - get_head_commit_id on empty branch returns None.
12 - write_commit is idempotent (won't overwrite).
13 - overwrite_commit updates the persisted record correctly.
14 - read_commit for absent commit returns None.
15 - list_commits and list_branches.
16 - list_tags returns all tags.
17 """
18
19 import datetime
20 import pathlib
21
22 import pytest
23
24 from muse.core.crdts.or_set import ORSet
25 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
26 from muse.domain import SemVerBump
27 from muse.core.store import (
28 CommitDict,
29 CommitRecord,
30 SnapshotRecord,
31 TagRecord,
32 get_all_commits,
33 get_all_tags,
34 get_head_commit_id,
35 overwrite_commit,
36 read_commit,
37 read_snapshot,
38 write_commit,
39 write_snapshot,
40 write_tag,
41 )
42
43
44 # ---------------------------------------------------------------------------
45 # Fixtures
46 # ---------------------------------------------------------------------------
47
48
49 @pytest.fixture
50 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
51 muse = tmp_path / ".muse"
52 (muse / "commits").mkdir(parents=True)
53 (muse / "snapshots").mkdir(parents=True)
54 (muse / "tags").mkdir(parents=True)
55 (muse / "refs" / "heads").mkdir(parents=True)
56 return tmp_path
57
58
59 def _now() -> datetime.datetime:
60 return datetime.datetime.now(datetime.timezone.utc)
61
62
63 _SNAP_ID: str = compute_snapshot_id({})
64
65
66 def _commit(
67 label: str = "default",
68 branch: str = "main",
69 parent: str | None = None,
70 ) -> CommitRecord:
71 """Create a CommitRecord with a real content-addressed commit_id."""
72 committed_at = _now()
73 cid = compute_commit_id(
74 parent_ids=[parent] if parent else [],
75 snapshot_id=_SNAP_ID,
76 message=f"commit {label}",
77 committed_at_iso=committed_at.isoformat(),
78 )
79 return CommitRecord(
80 commit_id=cid,
81 repo_id="test-repo",
82 branch=branch,
83 snapshot_id=_SNAP_ID,
84 message=f"commit {label}",
85 committed_at=committed_at,
86 parent_commit_id=parent,
87 )
88
89
90 # ===========================================================================
91 # CommitRecord round-trip
92 # ===========================================================================
93
94
95 class TestCommitRecordRoundTrip:
96 def test_minimal_round_trip(self) -> None:
97 c = _commit()
98 restored = CommitRecord.from_dict(c.to_dict())
99 assert restored.commit_id == c.commit_id
100 assert restored.branch == c.branch
101 assert restored.message == c.message
102
103 def test_all_provenance_fields_preserved(self) -> None:
104 c = CommitRecord(
105 commit_id="prov123",
106 repo_id="repo",
107 branch="main",
108 snapshot_id="snap",
109 message="provenance commit",
110 committed_at=_now(),
111 agent_id="claude-v4",
112 model_id="claude-3-5-sonnet",
113 toolchain_id="muse-cli-1.0",
114 prompt_hash="abc" * 10 + "ab",
115 signature="sig-" + "x" * 60,
116 signer_key_id="key-001",
117 )
118 d = c.to_dict()
119 restored = CommitRecord.from_dict(d)
120 assert restored.agent_id == "claude-v4"
121 assert restored.model_id == "claude-3-5-sonnet"
122 assert restored.toolchain_id == "muse-cli-1.0"
123 assert restored.signature == c.signature
124 assert restored.signer_key_id == "key-001"
125
126 def test_crdt_fields_preserved(self) -> None:
127 c = CommitRecord(
128 commit_id="crdt123",
129 repo_id="repo",
130 branch="main",
131 snapshot_id="snap",
132 message="crdt",
133 committed_at=_now(),
134 reviewed_by=["alice", "bob", "charlie"],
135 test_runs=42,
136 )
137 d = c.to_dict()
138 restored = CommitRecord.from_dict(d)
139 assert sorted(restored.reviewed_by) == ["alice", "bob", "charlie"]
140 assert restored.test_runs == 42
141
142 def test_format_version_7_is_default(self) -> None:
143 c = _commit()
144 assert c.format_version == 7
145
146 def test_format_version_persisted(self) -> None:
147 c = _commit()
148 assert CommitRecord.from_dict(c.to_dict()).format_version == 7
149
150 def test_sem_ver_bump_preserved(self) -> None:
151 bumps: tuple[SemVerBump, ...] = ("none", "patch", "minor", "major")
152 for bump in bumps:
153 c = CommitRecord(
154 commit_id="sv",
155 repo_id="r",
156 branch="main",
157 snapshot_id="s",
158 message="m",
159 committed_at=_now(),
160 sem_ver_bump=bump,
161 )
162 assert CommitRecord.from_dict(c.to_dict()).sem_ver_bump == bump
163
164 def test_breaking_changes_preserved(self) -> None:
165 c = CommitRecord(
166 commit_id="bc",
167 repo_id="r",
168 branch="main",
169 snapshot_id="s",
170 message="m",
171 committed_at=_now(),
172 breaking_changes=["removed `old_api`", "renamed `foo` → `bar`"],
173 )
174 restored = CommitRecord.from_dict(c.to_dict())
175 assert restored.breaking_changes == ["removed `old_api`", "renamed `foo` → `bar`"]
176
177 def test_parent_ids_preserved(self) -> None:
178 c = CommitRecord(
179 commit_id="merge",
180 repo_id="r",
181 branch="main",
182 snapshot_id="s",
183 message="m",
184 committed_at=_now(),
185 parent_commit_id="parent-1",
186 parent2_commit_id="parent-2",
187 )
188 restored = CommitRecord.from_dict(c.to_dict())
189 assert restored.parent_commit_id == "parent-1"
190 assert restored.parent2_commit_id == "parent-2"
191
192 def test_missing_crdt_fields_default_correctly(self) -> None:
193 """Simulates reading an older commit that lacks reviewed_by / test_runs."""
194 minimal: CommitDict = {
195 "commit_id": "old",
196 "repo_id": "r",
197 "branch": "main",
198 "snapshot_id": "snap",
199 "message": "old commit",
200 "committed_at": _now().isoformat(),
201 }
202 restored = CommitRecord.from_dict(minimal)
203 assert restored.reviewed_by == []
204 assert restored.test_runs == 0
205 assert restored.format_version == 1
206
207 def test_committed_at_timezone_aware(self) -> None:
208 c = _commit()
209 restored = CommitRecord.from_dict(c.to_dict())
210 assert restored.committed_at.tzinfo is not None
211
212
213 # ===========================================================================
214 # CommitRecord persistence
215 # ===========================================================================
216
217
218 class TestCommitPersistence:
219 def test_write_and_read_back(self, repo: pathlib.Path) -> None:
220 c = _commit("id001")
221 write_commit(repo, c)
222 restored = read_commit(repo, c.commit_id)
223 assert restored is not None
224 assert restored.commit_id == c.commit_id
225
226 def test_write_is_idempotent(self, repo: pathlib.Path) -> None:
227 c = _commit("id002")
228 write_commit(repo, c)
229 # Write the same commit again — the store must not corrupt it.
230 write_commit(repo, c)
231 restored = read_commit(repo, c.commit_id)
232 assert restored is not None
233 assert restored.message == c.message
234
235 def test_read_absent_commit_returns_none(self, repo: pathlib.Path) -> None:
236 assert read_commit(repo, "does-not-exist") is None
237
238 def test_overwrite_commit_updates_reviewed_by(self, repo: pathlib.Path) -> None:
239 c = _commit("id003")
240 write_commit(repo, c)
241 # Simulate ORSet merge: add reviewer.
242 updated = read_commit(repo, c.commit_id)
243 assert updated is not None
244 updated.reviewed_by = ["agent-x", "human-bob"]
245 overwrite_commit(repo, updated)
246 restored = read_commit(repo, c.commit_id)
247 assert restored is not None
248 assert "agent-x" in restored.reviewed_by
249 assert "human-bob" in restored.reviewed_by
250
251 def test_overwrite_commit_updates_test_runs(self, repo: pathlib.Path) -> None:
252 c = _commit("id004")
253 write_commit(repo, c)
254 for expected in range(1, 6):
255 rec = read_commit(repo, c.commit_id)
256 assert rec is not None
257 rec.test_runs += 1
258 overwrite_commit(repo, rec)
259 after = read_commit(repo, c.commit_id)
260 assert after is not None
261 assert after.test_runs == expected
262
263 def test_list_commits_returns_all_written(self, repo: pathlib.Path) -> None:
264 commits = [_commit(f"c{i:04d}") for i in range(20)]
265 for c in commits:
266 write_commit(repo, c)
267 found = {c.commit_id for c in get_all_commits(repo)}
268 for c in commits:
269 assert c.commit_id in found
270
271 def test_many_commits_all_retrievable(self, repo: pathlib.Path) -> None:
272 real_ids: list[str] = []
273 prev: str | None = None
274 base_ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
275 for i in range(100):
276 committed_at = base_ts + datetime.timedelta(seconds=i)
277 cid = compute_commit_id(
278 parent_ids=[prev] if prev else [],
279 snapshot_id=_SNAP_ID,
280 message=f"stress-{i:04d}",
281 committed_at_iso=committed_at.isoformat(),
282 )
283 write_commit(repo, CommitRecord(
284 commit_id=cid,
285 repo_id="test-repo",
286 branch="main",
287 snapshot_id=_SNAP_ID,
288 message=f"stress-{i:04d}",
289 committed_at=committed_at,
290 parent_commit_id=prev,
291 ))
292 real_ids.append(cid)
293 prev = cid
294 for cid in real_ids:
295 assert read_commit(repo, cid) is not None
296
297
298 # ===========================================================================
299 # SnapshotRecord
300 # ===========================================================================
301
302
303 class TestSnapshotRecordRoundTrip:
304 def test_minimal_round_trip(self) -> None:
305 s = SnapshotRecord(snapshot_id="snap-1", manifest={"f.mid": "hash1"})
306 restored = SnapshotRecord.from_dict(s.to_dict())
307 assert restored.snapshot_id == "snap-1"
308 assert restored.manifest == {"f.mid": "hash1"}
309
310 def test_large_manifest(self) -> None:
311 manifest = {f"track_{i:04d}.mid": f"hash-{i:064d}" for i in range(500)}
312 s = SnapshotRecord(snapshot_id="big-snap", manifest=manifest)
313 restored = SnapshotRecord.from_dict(s.to_dict())
314 assert len(restored.manifest) == 500
315 assert restored.manifest["track_0000.mid"] == f"hash-{0:064d}"
316
317 def test_write_and_read_back(self, repo: pathlib.Path) -> None:
318 manifest = {"a.mid": "h1", "b.mid": "h2"}
319 snap_id = compute_snapshot_id(manifest)
320 s = SnapshotRecord(snapshot_id=snap_id, manifest=manifest)
321 write_snapshot(repo, s)
322 restored = read_snapshot(repo, snap_id)
323 assert restored is not None
324 assert restored.manifest == manifest
325
326 def test_empty_manifest_round_trip(self) -> None:
327 s = SnapshotRecord(snapshot_id="empty-snap", manifest={})
328 restored = SnapshotRecord.from_dict(s.to_dict())
329 assert restored.manifest == {}
330
331
332 # ===========================================================================
333 # TagRecord
334 # ===========================================================================
335
336
337 class TestTagRecord:
338 def test_round_trip(self) -> None:
339 t = TagRecord(
340 tag_id="tag-001",
341 repo_id="repo",
342 commit_id="abc123",
343 tag="v1.0.0",
344 )
345 restored = TagRecord.from_dict(t.to_dict())
346 assert restored.tag_id == "tag-001"
347 assert restored.tag == "v1.0.0"
348 assert restored.commit_id == "abc123"
349
350 def test_write_and_list(self, repo: pathlib.Path) -> None:
351 for i in range(10):
352 write_tag(repo, TagRecord(
353 tag_id=f"tag-{i:04d}",
354 repo_id="repo",
355 commit_id=f"commit-{i:04d}",
356 tag=f"v{i}.0.0",
357 ))
358 tags = get_all_tags(repo, "repo")
359 assert len(tags) == 10
360
361 def test_created_at_preserved(self) -> None:
362 ts = datetime.datetime(2025, 6, 15, 12, 0, 0, tzinfo=datetime.timezone.utc)
363 t = TagRecord(tag_id="t", repo_id="r", commit_id="c", tag="v1", created_at=ts)
364 restored = TagRecord.from_dict(t.to_dict())
365 assert abs((restored.created_at - ts).total_seconds()) < 1.0
366
367
368 # ===========================================================================
369 # get_head_commit_id
370 # ===========================================================================
371
372
373 class TestGetHeadCommitId:
374 def test_empty_branch_returns_none(self, repo: pathlib.Path) -> None:
375 assert get_head_commit_id(repo, "nonexistent-branch") is None
376
377 def test_returns_id_after_writing_head_ref(self, repo: pathlib.Path) -> None:
378 ref_path = repo / ".muse" / "refs" / "heads" / "main"
379 ref_path.write_text("abc1234\n")
380 assert get_head_commit_id(repo, "main") == "abc1234"
381
382 def test_strips_whitespace(self, repo: pathlib.Path) -> None:
383 ref_path = repo / ".muse" / "refs" / "heads" / "feature"
384 ref_path.write_text(" deadbeef \n")
385 assert get_head_commit_id(repo, "feature") == "deadbeef"
386
387
388 # ===========================================================================
389 # CRDT semantics on CommitRecord fields
390 # ===========================================================================
391
392
393 class TestCRDTAnnotationSemantics:
394 def test_reviewed_by_orset_union_semantics(self, repo: pathlib.Path) -> None:
395 """ORSet union: multiple overwrite_commit calls accumulate reviewers."""
396 c = _commit("crdt-or-001")
397 write_commit(repo, c)
398
399 # Agent 1 adds their name.
400 rec = read_commit(repo, c.commit_id)
401 assert rec is not None
402 s, tok1 = ORSet().add("agent-alpha")
403 rec.reviewed_by = list(s.elements())
404 overwrite_commit(repo, rec)
405
406 # Agent 2 independently adds their name.
407 rec2 = read_commit(repo, c.commit_id)
408 assert rec2 is not None
409 s2 = ORSet()
410 for name in rec2.reviewed_by:
411 s2, _ = s2.add(name)
412 s2, tok2 = s2.add("agent-beta")
413 rec2.reviewed_by = sorted(s2.elements())
414 overwrite_commit(repo, rec2)
415
416 final = read_commit(repo, c.commit_id)
417 assert final is not None
418 assert "agent-alpha" in final.reviewed_by
419 assert "agent-beta" in final.reviewed_by
420
421 def test_test_runs_gcounter_monotone(self, repo: pathlib.Path) -> None:
422 """GCounter: test_runs must never decrease."""
423 c = _commit("crdt-gc-001")
424 write_commit(repo, c)
425 prev = 0
426 for _ in range(50):
427 rec = read_commit(repo, c.commit_id)
428 assert rec is not None
429 rec.test_runs += 1
430 overwrite_commit(repo, rec)
431 current = read_commit(repo, c.commit_id)
432 assert current is not None
433 assert current.test_runs >= prev
434 prev = current.test_runs
435 assert prev == 50
436
437 def test_all_provenance_fields_default_to_empty_string(self) -> None:
438 c = _commit()
439 assert c.agent_id == ""
440 assert c.model_id == ""
441 assert c.toolchain_id == ""
442 assert c.prompt_hash == ""
443 assert c.signature == ""
444 assert c.signer_key_id == ""
File History 5 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 26 days ago
sha256:50d413a635f57761c3fce1f70251b957b6423821525bf330747ef4007339222e Merge branch 'dev' into main Human 29 days ago
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce Merge branch 'dev' into main Human 48 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 100 days ago