gabriel / muse public
test_integrity_I5_commit_integrity.py python
728 lines 30.1 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 131 days ago
1 """Phase 1.5 — Commit record integrity on re-read.
2
3 Tests cover:
4 - write_commit idempotency: silent drop of duplicate ID
5 - write_commit collision detection: existing file is corrupt → CRITICAL + overwrite
6 - write_commit integrity violation: existing record has mismatched commit_id
7 - read_commit: WARNING→CRITICAL upgrade for corrupt files
8 - read_commit_result: discriminated union (ok / not_found / corrupt)
9 - read_snapshot / read_snapshot_result: same guarantees
10 - get_all_commits / get_all_tags: CRITICAL on corrupt (previously silent)
11 - list_releases: CRITICAL on corrupt (previously silent)
12 - verify-pack integration after write_commit
13 - Concurrent write with same ID: first writer always wins (idempotency at scale)
14 - Regression: corrupt file must log CRITICAL (level 50), never WARNING (level 30)
15 """
16
17 from __future__ import annotations
18
19 import datetime
20 import json
21 import logging
22 import pathlib
23 import threading
24 import uuid
25
26 import msgpack
27 import pytest
28
29 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
30
31 from muse.core._types import Manifest, fake_id, long_id
32
33 _REPO_ID = fake_id("repo")
34 from muse.core.store import (
35 CommitReadCorrupt,
36 CommitReadNotFound,
37 CommitReadOk,
38 CommitRecord,
39 ReleaseRecord,
40 SemVerTag,
41 SnapshotReadCorrupt,
42 SnapshotReadNotFound,
43 SnapshotReadOk,
44 SnapshotRecord,
45 TagRecord,
46 commit_read_is_corrupt,
47 commit_read_is_not_found,
48 commit_read_is_ok,
49 get_all_commits,
50 get_all_tags,
51 list_releases,
52 read_commit,
53 read_commit_result,
54 read_snapshot,
55 read_snapshot_result,
56 snapshot_read_is_corrupt,
57 snapshot_read_is_ok,
58 write_commit,
59 write_release,
60 write_snapshot,
61 write_tag,
62 commit_path,
63 snapshot_path,
64 tag_path,
65 release_path as _release_path,
66 )
67
68 # ---------------------------------------------------------------------------
69 # Helpers
70 # ---------------------------------------------------------------------------
71
72 def _make_commit(
73 root: pathlib.Path,
74 message: str = "msg",
75 branch: str = "main",
76 parent: str | None = None,
77 write: bool = True,
78 ) -> CommitRecord:
79 """Create a CommitRecord with a content-addressed commit_id.
80
81 Uses ``compute_commit_id`` so every record passes ``_verify_commit_id``
82 on read-back. ``write=False`` builds the record without persisting it —
83 useful for testing concurrent or idempotent write scenarios.
84 """
85 committed_at = datetime.datetime(2026, 3, 1, tzinfo=datetime.timezone.utc)
86 snap_id = compute_snapshot_id({})
87 parent_ids = [parent] if parent else []
88 cid = compute_commit_id(
89 repo_id=fake_id("repo"),
90 parent_ids=parent_ids,
91 snapshot_id=snap_id,
92 message=message,
93 committed_at_iso=committed_at.isoformat(),
94 author="tester",
95 )
96 c = CommitRecord(
97 commit_id=cid,
98 repo_id=fake_id("repo"),
99 created_on_branch=branch,
100 snapshot_id=snap_id,
101 message=message,
102 committed_at=committed_at,
103 author="tester",
104 parent_commit_id=parent,
105 parent2_commit_id=None,
106 )
107 if write:
108 write_commit(root, c)
109 return c
110
111
112 def _make_snapshot(
113 root: pathlib.Path, manifest: Manifest | None = None
114 ) -> SnapshotRecord:
115 """Create a SnapshotRecord with a content-addressed snapshot_id.
116
117 Pass distinct ``manifest`` dicts to get distinct snapshot_ids — e.g.
118 ``{"file-A.py": "a" * 64}`` vs ``{"file-B.py": "b" * 64}``.
119 """
120 m = manifest or {}
121 sid = compute_snapshot_id(m)
122 s = SnapshotRecord(
123 snapshot_id=sid,
124 manifest=m,
125 created_at=datetime.datetime(2026, 3, 1, tzinfo=datetime.timezone.utc),
126 )
127 write_snapshot(root, s)
128 return s
129
130
131 def _make_tag(root: pathlib.Path, tag_name: str) -> TagRecord:
132 t = TagRecord(
133 tag_id=fake_id(tag_name),
134 repo_id=fake_id("repo"),
135 commit_id=fake_id("tag-commit"),
136 tag=tag_name,
137 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
138 )
139 write_tag(root, t)
140 return t
141
142
143 def _make_release(root: pathlib.Path, tag: str, semver: SemVerTag) -> ReleaseRecord:
144 r = ReleaseRecord(
145 release_id=fake_id(tag + "-release"),
146 repo_id=fake_id("repo"),
147 tag=tag,
148 semver=semver,
149 channel="stable",
150 commit_id=fake_id("release-commit"),
151 snapshot_id=fake_id(tag),
152 title=tag,
153 body="",
154 changelog=[],
155 )
156 write_release(root, r)
157 return r
158
159
160
161 def _tag_path(root: pathlib.Path, tag_id: str) -> pathlib.Path:
162 return tag_path(root, _REPO_ID, tag_id)
163
164
165 # ---------------------------------------------------------------------------
166 # Fixtures
167 # ---------------------------------------------------------------------------
168
169 @pytest.fixture()
170 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
171 muse = tmp_path / ".muse"
172 (muse / "commits").mkdir(parents=True)
173 (muse / "snapshots").mkdir(parents=True)
174 (muse / "refs" / "heads").mkdir(parents=True)
175 (muse / "tags").mkdir(parents=True)
176 (muse / "releases").mkdir(parents=True)
177 (muse / "repo.json").write_text(json.dumps({"repo_id": _REPO_ID}))
178 (muse / "HEAD").write_text("ref: refs/heads/main\n")
179 (muse / "refs" / "heads" / "main").write_text("")
180 return tmp_path
181
182
183 # ===========================================================================
184 # 1. write_commit — idempotency
185 # ===========================================================================
186
187 class TestWriteCommitIdempotency:
188 def test_first_writer_wins(self, repo: pathlib.Path) -> None:
189 """A record with wrong incoming hash is rejected before it can overwrite anything.
190
191 The old "first writer wins via silent drop" path is superseded by incoming
192 hash verification: a record whose commit_id doesn't match its content hash
193 raises ValueError immediately — the good file on disk is never touched.
194 """
195 c1 = _make_commit(repo, message="first-wins")
196 # Construct a record with the same commit_id but different content —
197 # the hash won't match, so write_commit must raise before touching disk.
198 c2 = CommitRecord(
199 commit_id=c1.commit_id,
200 repo_id=fake_id("repo"),
201 created_on_branch="main",
202 snapshot_id=c1.snapshot_id,
203 message="second-attempt",
204 committed_at=c1.committed_at,
205 author="tester",
206 parent_commit_id=None,
207 parent2_commit_id=None,
208 )
209 with pytest.raises(ValueError):
210 write_commit(repo, c2)
211 loaded = read_commit(repo, c1.commit_id)
212 assert loaded is not None
213 assert loaded.message == "first-wins", "bad incoming record must not overwrite good file"
214
215 def test_exact_duplicate_emits_no_critical(
216 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
217 ) -> None:
218 """Writing the exact same record twice must not log CRITICAL."""
219 c = _make_commit(repo, message="exact-dup-no-critical")
220 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
221 write_commit(repo, c)
222 assert not any(r.levelno >= logging.CRITICAL for r in caplog.records)
223
224 def test_idempotent_round_trip_preserves_all_fields(self, repo: pathlib.Path) -> None:
225 c = _make_commit(repo, message="preserve-me", branch="feat/x")
226 write_commit(repo, c) # second write — must be completely harmless
227 loaded = read_commit(repo, c.commit_id)
228 assert loaded is not None
229 assert loaded.message == "preserve-me"
230 assert loaded.created_on_branch == "feat/x"
231
232
233 # ===========================================================================
234 # 2. write_commit — corrupt existing file → CRITICAL + overwrite
235 # ===========================================================================
236
237 class TestWriteCommitCorruptExistingFile:
238 def test_corrupt_existing_is_overwritten(
239 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
240 ) -> None:
241 """Corrupt existing commit file is replaced with the incoming good record."""
242 c = _make_commit(repo, message="original-overwrite")
243 commit_path(repo, c.commit_id).write_bytes(b"\xff\xfe\x00bad-data\x99")
244 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
245 write_commit(repo, c)
246 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
247 assert crits, "Must log CRITICAL when overwriting corrupt file"
248 loaded = read_commit(repo, c.commit_id)
249 assert loaded is not None
250 assert loaded.message == "original-overwrite"
251
252 def test_empty_existing_is_overwritten(
253 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
254 ) -> None:
255 """Zero-byte commit file (crash during write) is replaced with good record."""
256 c = _make_commit(repo, message="after-crash-overwrite")
257 commit_path(repo, c.commit_id).write_bytes(b"")
258 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
259 write_commit(repo, c)
260 loaded = read_commit(repo, c.commit_id)
261 assert loaded is not None
262 assert loaded.message == "after-crash-overwrite"
263
264 def test_truncated_msgpack_is_overwritten(
265 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
266 ) -> None:
267 """Partially-written (truncated) msgpack file is replaced."""
268 c = _make_commit(repo, message="after-truncation-overwrite")
269 path = commit_path(repo, c.commit_id)
270 good_bytes = path.read_bytes()
271 path.write_bytes(good_bytes[: len(good_bytes) // 2])
272 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
273 write_commit(repo, c)
274 loaded = read_commit(repo, c.commit_id)
275 assert loaded is not None
276 assert loaded.message == "after-truncation-overwrite"
277
278
279 # ===========================================================================
280 # 3. write_commit — store integrity violation
281 # ===========================================================================
282
283 class TestWriteCommitIntegrityViolation:
284 def test_commit_id_mismatch_raises_os_error(self, repo: pathlib.Path) -> None:
285 """Stored record's commit_id does not match filename → OSError."""
286 # Write a legitimate commit so the file exists on disk.
287 c_legit = _make_commit(repo, message="legitimate-mismatch")
288 # Build a different commit (different content → different commit_id).
289 c_impostor = _make_commit(repo, message="impostor-mismatch")
290 # Overwrite the legitimate file with the impostor's msgpack bytes —
291 # the file path says c_legit.commit_id but the bytes claim c_impostor.commit_id.
292 impostor_bytes = msgpack.packb(c_impostor.to_dict(), use_bin_type=True)
293 commit_path(repo, c_legit.commit_id).write_bytes(impostor_bytes)
294 # Now re-write the original c_legit — it passes incoming hash verification
295 # (its commit_id matches its content), but the file on disk now contains
296 # the impostor's bytes. write_commit must detect the mismatch and raise OSError.
297 with pytest.raises(OSError, match="Store integrity violation"):
298 write_commit(repo, c_legit)
299
300
301 # ===========================================================================
302 # 4. read_commit — CRITICAL log for corrupt
303 # ===========================================================================
304
305 class TestReadCommitCriticalLogging:
306 def test_corrupt_file_logs_critical(
307 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
308 ) -> None:
309 c = _make_commit(repo, message="garbage-payload")
310 commit_path(repo, c.commit_id).write_bytes(b"\x00\x01garbage\xff")
311 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
312 result = read_commit(repo, c.commit_id)
313 assert result is None
314 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
315 assert crits, "Must log CRITICAL for corrupt commit file"
316 assert any("Corrupt" in r.message for r in crits)
317
318 def test_missing_file_returns_none_no_log(
319 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
320 ) -> None:
321 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
322 result = read_commit(repo, fake_id("missing-commit"))
323 assert result is None
324 assert not any(r.levelno >= logging.WARNING for r in caplog.records)
325
326 def test_valid_file_returns_record_no_critical(
327 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
328 ) -> None:
329 c = _make_commit(repo, message="clean-read")
330 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
331 result = read_commit(repo, c.commit_id)
332 assert result is not None
333 assert result.message == "clean-read"
334 assert not any(r.levelno >= logging.CRITICAL for r in caplog.records)
335
336 def test_corrupt_log_references_filename(
337 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
338 ) -> None:
339 c = _make_commit(repo, message="not-msgpack-content")
340 commit_path(repo, c.commit_id).write_bytes(b"not-msgpack")
341 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
342 read_commit(repo, c.commit_id)
343 messages = " ".join(r.message + str(r.args) for r in caplog.records)
344 bare = long_id(c.commit_id, strip=True)
345 assert bare[:8] in messages or bare in messages
346
347
348 # ===========================================================================
349 # 5. read_commit_result — discriminated union
350 # ===========================================================================
351
352 class TestReadCommitResult:
353 def test_ok_status_on_valid_record(self, repo: pathlib.Path) -> None:
354 c = _make_commit(repo, message="typed-ok")
355 r = read_commit_result(repo, c.commit_id)
356 assert commit_read_is_ok(r)
357 assert isinstance(r["commit"], CommitRecord)
358 assert r["commit"].message == "typed-ok"
359
360 def test_not_found_status_when_missing(self, repo: pathlib.Path) -> None:
361 r = read_commit_result(repo, "ff" * 32)
362 assert commit_read_is_not_found(r)
363
364 def test_corrupt_status_on_bad_bytes(
365 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
366 ) -> None:
367 c = _make_commit(repo, message="corrupt-bytes")
368 commit_path(repo, c.commit_id).write_bytes(b"\xff\x00garbage")
369 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
370 r = read_commit_result(repo, c.commit_id)
371 assert commit_read_is_corrupt(r)
372 assert r["path"] != ""
373 assert r["error"] != ""
374 crits = [rec for rec in caplog.records if rec.levelno >= logging.CRITICAL]
375 assert crits
376
377 def test_corrupt_result_path_contains_commit_id(self, repo: pathlib.Path) -> None:
378 c = _make_commit(repo, message="path-in-corrupt")
379 commit_path(repo, c.commit_id).write_bytes(b"")
380 r = read_commit_result(repo, c.commit_id)
381 assert commit_read_is_corrupt(r)
382 assert long_id(c.commit_id, strip=True) in r["path"]
383
384 def test_ok_result_roundtrips_all_metadata(self, repo: pathlib.Path) -> None:
385 # Build with a real content-addressed ID so _verify_commit_id passes.
386 snap_id = fake_id("snap-meta-roundtrip")
387 committed_at = datetime.datetime(2026, 3, 15, tzinfo=datetime.timezone.utc)
388 cid = compute_commit_id(
389 repo_id=fake_id("repo"),
390 parent_ids=[],
391 snapshot_id=snap_id,
392 message="full metadata",
393 committed_at_iso=committed_at.isoformat(),
394 author="alice",
395 )
396 c = CommitRecord(
397 commit_id=cid,
398 repo_id=fake_id("repo"),
399 created_on_branch="dev",
400 snapshot_id=snap_id,
401 message="full metadata",
402 committed_at=committed_at,
403 author="alice",
404 parent_commit_id=None,
405 parent2_commit_id=None,
406 metadata={"key": "val"},
407 )
408 write_commit(repo, c)
409 r = read_commit_result(repo, cid)
410 assert commit_read_is_ok(r)
411 assert r["commit"].created_on_branch == "dev"
412 assert r["commit"].author == "alice"
413 assert r["commit"].metadata == {"key": "val"}
414
415 def test_status_field_is_string(self, repo: pathlib.Path) -> None:
416 """Status values are plain strings — easy for agents to pattern-match."""
417 c = _make_commit(repo, message="status-str-check")
418 r = read_commit_result(repo, c.commit_id)
419 assert isinstance(r["status"], str)
420
421 def test_not_found_has_only_status_key(self, repo: pathlib.Path) -> None:
422 r = read_commit_result(repo, "90" * 32)
423 assert set(r.keys()) == {"status"}
424
425 def test_three_outcomes_are_mutually_exclusive(self, repo: pathlib.Path) -> None:
426 """Confirm all three outcome strings are distinct and unambiguous."""
427 c_ok = _make_commit(repo, message="outcome-ok")
428 c_corrupt = _make_commit(repo, message="outcome-corrupt")
429 commit_path(repo, c_corrupt.commit_id).write_bytes(b"bad")
430 statuses = {
431 read_commit_result(repo, c_ok.commit_id)["status"],
432 read_commit_result(repo, "cc" * 32)["status"],
433 read_commit_result(repo, c_corrupt.commit_id)["status"],
434 }
435 assert statuses == {"ok", "not_found", "corrupt"}
436
437
438 # ===========================================================================
439 # 6. read_snapshot / read_snapshot_result
440 # ===========================================================================
441
442 class TestReadSnapshotIntegrity:
443 def test_corrupt_snapshot_logs_critical(
444 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
445 ) -> None:
446 s = _make_snapshot(repo, manifest={"snap-critical.py": fake_id("oid-a")})
447 snapshot_path(repo, s.snapshot_id).write_bytes(b"\xde\xad\xbe\xef")
448 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
449 result = read_snapshot(repo, s.snapshot_id)
450 assert result is None
451 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
452
453 def test_snapshot_result_ok(self, repo: pathlib.Path) -> None:
454 s = _make_snapshot(repo, manifest={"snap-ok.py": fake_id("oid-b")})
455 r = read_snapshot_result(repo, s.snapshot_id)
456 assert snapshot_read_is_ok(r)
457 assert isinstance(r["snapshot"], SnapshotRecord)
458
459 def test_snapshot_result_not_found(self, repo: pathlib.Path) -> None:
460 r = read_snapshot_result(repo, fake_id("no-snap"))
461 assert r["status"] == "not_found"
462 assert set(r.keys()) == {"status"}
463
464 def test_snapshot_result_corrupt(
465 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
466 ) -> None:
467 s = _make_snapshot(repo, manifest={"snap-corrupt.py": fake_id("oid-c")})
468 snapshot_path(repo, s.snapshot_id).write_bytes(b"garbage-bytes\x00")
469 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
470 r = read_snapshot_result(repo, s.snapshot_id)
471 assert snapshot_read_is_corrupt(r)
472 assert r["path"] != ""
473 assert r["error"] != ""
474
475 def test_missing_snapshot_no_log(
476 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
477 ) -> None:
478 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
479 result = read_snapshot(repo, fake_id("missing"))
480 assert result is None
481 assert not any(r.levelno >= logging.WARNING for r in caplog.records)
482
483
484 # ===========================================================================
485 # 7. get_all_commits — CRITICAL on corrupt (previously silent)
486 # ===========================================================================
487
488 class TestGetAllCommitsCorruptLogging:
489 def test_one_corrupt_skipped_with_critical(
490 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
491 ) -> None:
492 """Corrupt commit is skipped; good commits returned; CRITICAL emitted."""
493 c_good = _make_commit(repo, message="good-survives")
494 c_bad = _make_commit(repo, message="will-corrupt")
495 commit_path(repo, c_bad.commit_id).write_bytes(b"\xff\x00")
496 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
497 commits = get_all_commits(repo)
498 ids = {c.commit_id for c in commits}
499 assert c_good.commit_id in ids, "good commit must still appear"
500 assert c_bad.commit_id not in ids, "corrupt commit must be excluded"
501 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
502
503 def test_all_corrupt_returns_empty_with_critical(
504 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
505 ) -> None:
506 written = [_make_commit(repo, message=f"c{i}") for i in range(3)]
507 for c in written:
508 commit_path(repo, c.commit_id).write_bytes(b"bad")
509 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
510 commits = get_all_commits(repo)
511 assert commits == []
512 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
513 assert len(crits) == 3
514
515 def test_empty_store_returns_empty_no_log(
516 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
517 ) -> None:
518 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
519 commits = get_all_commits(repo)
520 assert commits == []
521 assert not any(r.levelno >= logging.WARNING for r in caplog.records)
522
523 def test_mixed_good_and_corrupt_correct_count(self, repo: pathlib.Path) -> None:
524 good = [_make_commit(repo, message=f"g{i}") for i in range(5)]
525 bad = [_make_commit(repo, message=f"b{i}") for i in range(3)]
526 for c in bad:
527 commit_path(repo, c.commit_id).write_bytes(b"corrupt")
528 commits = get_all_commits(repo)
529 assert len(commits) == len(good)
530
531
532 # ===========================================================================
533 # 8. get_all_tags — CRITICAL on corrupt (previously silent)
534 # ===========================================================================
535
536 class TestGetAllTagsCorruptLogging:
537 def test_corrupt_tag_skipped_with_critical(
538 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
539 ) -> None:
540 t1 = _make_tag(repo, "v1.0.0")
541 t2 = _make_tag(repo, "v2.0.0")
542 _tag_path(repo, t2.tag_id).write_bytes(b"\x00bad")
543 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
544 tags = get_all_tags(repo, _REPO_ID)
545 tag_values = {t.tag for t in tags}
546 assert "v1.0.0" in tag_values
547 assert "v2.0.0" not in tag_values
548 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
549
550 def test_good_tags_all_returned(self, repo: pathlib.Path) -> None:
551 _make_tag(repo, "v0.1")
552 _make_tag(repo, "v0.2")
553 tags = get_all_tags(repo, _REPO_ID)
554 assert len(tags) == 2
555
556 def test_all_corrupt_tags_returns_empty_with_critical(
557 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
558 ) -> None:
559 for name in ("v1", "v2", "v3"):
560 t = _make_tag(repo, name)
561 _tag_path(repo, t.tag_id).write_bytes(b"bad")
562 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
563 tags = get_all_tags(repo, _REPO_ID)
564 assert tags == []
565 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
566 assert len(crits) == 3
567
568
569 # ===========================================================================
570 # 9. list_releases — CRITICAL on corrupt (previously silent)
571 # ===========================================================================
572
573 class TestListReleasesCorruptLogging:
574 def test_corrupt_release_skipped_with_critical(
575 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
576 ) -> None:
577 good = _make_release(
578 repo, "v1.0.0", SemVerTag(major=1, minor=0, patch=0, pre="", build="")
579 )
580 bad = _make_release(
581 repo, "v2.0.0", SemVerTag(major=2, minor=0, patch=0, pre="", build="")
582 )
583 _release_path(repo, _REPO_ID, bad.release_id).write_bytes(b"\xff\x00garbage")
584 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
585 releases = list_releases(repo, _REPO_ID)
586 ids = {r.release_id for r in releases}
587 assert good.release_id in ids
588 assert bad.release_id not in ids
589 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
590
591 def test_all_releases_good_returns_all(self, repo: pathlib.Path) -> None:
592 _make_release(repo, "v1.0.0", SemVerTag(major=1, minor=0, patch=0, pre="", build=""))
593 _make_release(repo, "v1.1.0", SemVerTag(major=1, minor=1, patch=0, pre="", build=""))
594 releases = list_releases(repo, _REPO_ID)
595 assert len(releases) == 2
596
597
598 # ===========================================================================
599 # 10. verify-pack integration after write_commit
600 # ===========================================================================
601
602 class TestVerifyPackAfterWriteCommit:
603 def test_cmd_read_commit_roundtrip(self, repo: pathlib.Path) -> None:
604 """``muse read-commit`` must succeed for every written commit."""
605 from tests.cli_test_helper import CliRunner
606
607 c = _make_commit(repo, message="plumbing-check")
608
609 runner = CliRunner()
610 result = runner.invoke(
611 None,
612 ["read-commit", c.commit_id, "--json"],
613 env={"MUSE_REPO_ROOT": str(repo)},
614 )
615 assert result.exit_code == 0
616 import json as _json
617 data = _json.loads(result.output)
618 assert data["commit_id"] == c.commit_id
619 assert data["message"] == "plumbing-check"
620
621
622 # ===========================================================================
623 # 11. Concurrent idempotency — 50 threads race to write the same commit
624 # ===========================================================================
625
626 class TestConcurrentIdempotentWrite:
627 def test_50_threads_same_commit_id_first_wins(self, repo: pathlib.Path) -> None:
628 """50 threads writing the EXACT same commit — idempotent, exactly one file written."""
629 # In a content-addressed system, identical content → identical commit_id.
630 c = _make_commit(repo, message="concurrent-idempotent", write=False)
631 errors: list[Exception] = []
632
633 def write_one() -> None:
634 try:
635 write_commit(repo, c)
636 except Exception as exc:
637 errors.append(exc)
638
639 threads = [threading.Thread(target=write_one) for _ in range(50)]
640 for t in threads:
641 t.start()
642 for t in threads:
643 t.join()
644
645 assert not errors, f"Unexpected errors in same-ID concurrent writes: {errors[:3]}"
646
647 loaded = read_commit(repo, c.commit_id)
648 assert loaded is not None
649 assert loaded.commit_id == c.commit_id
650 assert loaded.message == "concurrent-idempotent"
651
652 def test_50_threads_distinct_ids_all_survive(self, repo: pathlib.Path) -> None:
653 """50 threads writing distinct commit IDs must all persist without errors."""
654 errors: list[Exception] = []
655
656 def write_unique(i: int) -> None:
657 # _make_commit uses compute_commit_id so the hash always matches content.
658 c = _make_commit(repo, message=f"unique {i}", write=False)
659 try:
660 write_commit(repo, c)
661 except Exception as exc:
662 errors.append(exc)
663
664 threads = [threading.Thread(target=write_unique, args=(i,)) for i in range(50)]
665 for t in threads:
666 t.start()
667 for t in threads:
668 t.join()
669
670 assert not errors, f"Unexpected errors in distinct-ID concurrent writes: {errors[:3]}"
671 commits = get_all_commits(repo)
672 assert len(commits) == 50
673
674
675 # ===========================================================================
676 # 12. Regression: WARNING→CRITICAL upgrade is permanent
677 # ===========================================================================
678
679 class TestRegressionCorruptLevelUpgrade:
680 """Confirm the upgrade from WARNING to CRITICAL is permanent and precise."""
681
682 def test_corrupt_commit_logs_at_critical_not_warning(
683 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
684 ) -> None:
685 c = _make_commit(repo, message="level-upgrade-commit")
686 commit_path(repo, c.commit_id).write_bytes(b"trash")
687 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
688 read_commit(repo, c.commit_id)
689 levels = [r.levelno for r in caplog.records]
690 assert any(lvl == logging.CRITICAL for lvl in levels), (
691 f"Expected CRITICAL (50) but got levels: {levels}"
692 )
693 assert not any(lvl == logging.WARNING for lvl in levels), (
694 "Must not downgrade corruption to WARNING — only CRITICAL is acceptable"
695 )
696
697 def test_corrupt_snapshot_logs_at_critical_not_warning(
698 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
699 ) -> None:
700 s = _make_snapshot(repo, manifest={"snap-level.py": fake_id("oid-d")})
701 snapshot_path(repo, s.snapshot_id).write_bytes(b"bad")
702 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
703 read_snapshot(repo, s.snapshot_id)
704 levels = [r.levelno for r in caplog.records]
705 assert any(lvl == logging.CRITICAL for lvl in levels)
706 assert not any(lvl == logging.WARNING for lvl in levels)
707
708 def test_get_all_commits_logs_corrupt_at_critical(
709 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
710 ) -> None:
711 c = _make_commit(repo, message="level-upgrade-get-all")
712 commit_path(repo, c.commit_id).write_bytes(b"trash")
713 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
714 get_all_commits(repo)
715 levels = [r.levelno for r in caplog.records]
716 assert any(lvl == logging.CRITICAL for lvl in levels)
717 assert not any(lvl == logging.WARNING for lvl in levels)
718
719 def test_get_all_tags_logs_corrupt_at_critical(
720 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
721 ) -> None:
722 t = _make_tag(repo, "v-crit")
723 _tag_path(repo, t.tag_id).write_bytes(b"trash")
724 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
725 get_all_tags(repo, _REPO_ID)
726 levels = [r.levelno for r in caplog.records]
727 assert any(lvl == logging.CRITICAL for lvl in levels)
728 assert not any(lvl == logging.WARNING for lvl in levels)
File History 3 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 131 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 137 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 140 days ago