gabriel / muse public
test_cmd_archive_hardening.py python
590 lines 23.7 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 134 days ago
1 """Comprehensive hardening tests for ``muse archive``.
2
3 Coverage dimensions:
4
5 Unit
6 ~~~~
7 - ``_safe_arcname`` empty rel_path rejected
8 - ``_safe_arcname`` null bytes in rel_path rejected
9 - ``_safe_arcname`` null bytes in prefix rejected
10 - ``_safe_arcname`` dot-only path rejected (".")
11 - ``_safe_arcname`` deeply nested safe path allowed
12 - ``_safe_arcname`` path with spaces allowed
13 - ``_safe_arcname`` unicode filenames allowed
14 - ``_ArchiveJson`` TypedDict has all expected fields
15
16 Security
17 ~~~~~~~~
18 - ``--json`` flag now works (not broken by format validation)
19 - All error messages route to stderr, not stdout
20 - Unknown --format rejected with nonzero exit (argparse choices= guard)
21 - --prefix with ``..`` rejected with nonzero exit
22 - Zip-slip in manifest (``../`` prefix) skipped in tar.gz
23 - Zip-slip in manifest (``../`` prefix) skipped in zip
24 - ANSI escape sequences in commit message sanitized in text output
25 - Null byte in manifest rel_path skipped silently
26
27 JSON schema
28 ~~~~~~~~~~~
29 - ``--json`` on tar.gz produces valid ``_ArchiveJson`` schema
30 - ``--json`` on zip produces valid ``_ArchiveJson`` schema
31 - ``--json`` includes correct ``file_count`` and ``bytes``
32 - ``--json`` includes ``commit_id`` (full SHA-256)
33 - ``--json`` includes ``message`` and ``branch``
34 - ``--json`` includes ``ref`` as null when HEAD used
35 - ``--json`` includes ``ref`` as string when --ref used
36 - ``--json`` on empty snapshot reports file_count=0
37
38 Integration
39 ~~~~~~~~~~~
40 - ``--ref`` with short SHA resolves correctly
41 - ``--ref`` with branch name resolves correctly
42 - ``--ref`` with unknown ref exits nonzero and writes to stderr
43 - Default output path is ``<sha12>.tar.gz``
44 - Custom output path honoured
45 - Missing object in manifest skipped gracefully
46 - Archive file content matches committed bytes (round-trip)
47 - Zip archive entries are readable
48 - Tar.gz archive entries are readable
49
50 E2E
51 ~~~
52 - Full lifecycle: init → commit files → archive → verify contents
53 - ``--prefix`` adds directory level inside both tar.gz and zip
54 - Repeated archive calls produce identical archives (deterministic)
55 - No ``.muse/`` metadata appears in any archive entry
56
57 Stress
58 ~~~~~~
59 - 200-file archive completes without error
60 - Concurrent archive calls on different repos are safe
61 """
62
63 from __future__ import annotations
64
65 type _FileStore = dict[str, bytes]
66
67 import json
68 import pathlib
69 import tarfile
70 import threading
71 import zipfile
72 from typing import TypedDict
73
74 import pytest
75 from tests.cli_test_helper import CliRunner, InvokeResult
76 from muse.core._types import blob_id, long_id, short_id, fake_id
77
78 cli = None
79 runner = CliRunner()
80
81
82 # ---------------------------------------------------------------------------
83 # Helpers
84 # ---------------------------------------------------------------------------
85
86
87 def _env(root: pathlib.Path) -> Manifest:
88 return {"MUSE_REPO_ROOT": str(root)}
89
90
91 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
92 import datetime as dt
93 muse = tmp_path / ".muse"
94 for sub in ("objects", "commits", "snapshots", "refs/heads"):
95 (muse / sub).mkdir(parents=True, exist_ok=True)
96 (muse / "repo.json").write_text(json.dumps({
97 "repo_id": fake_id("repo"),
98 "domain": "code",
99 "default_branch": "main",
100 "created_at": "2026-01-01T00:00:00+00:00",
101 }), encoding="utf-8")
102 (muse / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
103 return tmp_path
104
105
106 def _write_object(root: pathlib.Path, content: bytes) -> str:
107 from muse.core.object_store import write_object
108 obj_id = blob_id(content)
109 write_object(root, obj_id, content)
110 return obj_id
111
112
113 def _make_commit(
114 root: pathlib.Path,
115 files: _FileStore | None = None,
116 message: str = "test commit",
117 ) -> str:
118 import datetime as dt
119 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
120 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
121
122 ref_file = root / ".muse" / "refs" / "heads" / "main"
123 parent_id = ref_file.read_text().strip() if ref_file.exists() else None
124
125 manifest: Manifest = {}
126 for rel_path, content in (files or {}).items():
127 manifest[rel_path] = _write_object(root, content)
128
129 snap_id = compute_snapshot_id(manifest)
130 committed_at = dt.datetime(2026, 1, 1, tzinfo=dt.timezone.utc)
131 commit_id = compute_commit_id(
132 repo_id="test-repo",
133 parent_ids=[parent_id] if parent_id else [],
134 snapshot_id=snap_id,
135 message=message,
136 committed_at_iso=committed_at.isoformat(),
137 )
138 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
139 write_commit(root, CommitRecord(
140 commit_id=commit_id,
141 repo_id="test-repo",
142 created_on_branch="main",
143 snapshot_id=snap_id,
144 message=message,
145 committed_at=committed_at,
146 parent_commit_id=parent_id,
147 ))
148 ref_file.parent.mkdir(parents=True, exist_ok=True)
149 ref_file.write_text(commit_id, encoding="utf-8")
150 return commit_id
151
152
153 def _invoke(root: pathlib.Path, *args: str) -> InvokeResult:
154 return runner.invoke(cli, ["archive"] + list(args), env=_env(root), catch_exceptions=False)
155
156
157 class _ArchiveJson(TypedDict):
158 path: str
159 format: str
160 file_count: int
161 bytes: int
162 commit_id: str
163 message: str
164 branch: str
165 ref: str | None
166
167
168 def _parse_json(output: str) -> _ArchiveJson:
169 for line in output.splitlines():
170 line = line.strip()
171 if line.startswith("{"):
172 raw = json.loads(line)
173 return _ArchiveJson(
174 path=str(raw["path"]),
175 format=str(raw["format"]),
176 file_count=int(raw["file_count"]),
177 bytes=int(raw["bytes"]),
178 commit_id=str(raw["commit_id"]),
179 message=str(raw["message"]),
180 branch=str(raw["branch"]),
181 ref=raw["ref"] if raw["ref"] is not None else None,
182 )
183 raise AssertionError(f"No JSON object found in output:\n{output}")
184
185
186 # ---------------------------------------------------------------------------
187 # Unit — _safe_arcname edge cases
188 # ---------------------------------------------------------------------------
189
190
191 class TestSafeArcname:
192 def test_empty_rel_path_rejected(self) -> None:
193 from muse.cli.commands.archive import _safe_arcname
194 assert _safe_arcname("", "") is None
195 assert _safe_arcname("prefix", "") is None
196
197 def test_null_byte_in_rel_path_rejected(self) -> None:
198 from muse.cli.commands.archive import _safe_arcname
199 assert _safe_arcname("", "file\x00.txt") is None
200
201 def test_null_byte_in_prefix_rejected(self) -> None:
202 from muse.cli.commands.archive import _safe_arcname
203 assert _safe_arcname("pre\x00fix", "file.txt") is None
204
205 def test_dot_only_path_rejected(self) -> None:
206 from muse.cli.commands.archive import _safe_arcname
207 # PurePosixPath("") normalises to "." — must be rejected
208 assert _safe_arcname("", ".") is None
209
210 def test_deeply_nested_safe_path(self) -> None:
211 from muse.cli.commands.archive import _safe_arcname
212 assert _safe_arcname("", "a/b/c/d/e/file.txt") == "a/b/c/d/e/file.txt"
213
214 def test_path_with_spaces(self) -> None:
215 from muse.cli.commands.archive import _safe_arcname
216 assert _safe_arcname("", "my file.mid") == "my file.mid"
217
218 def test_unicode_filename(self) -> None:
219 from muse.cli.commands.archive import _safe_arcname
220 assert _safe_arcname("", "音楽/track.mid") == "音楽/track.mid"
221
222 def test_prefix_with_subdirs(self) -> None:
223 from muse.cli.commands.archive import _safe_arcname
224 assert _safe_arcname("release/v1.0", "file.txt") == "release/v1.0/file.txt"
225
226
227 # ---------------------------------------------------------------------------
228 # Security
229 # ---------------------------------------------------------------------------
230
231
232 class TestSecurity:
233 def test_json_flag_now_works(self, tmp_path: pathlib.Path) -> None:
234 """--json must NOT exit with an error (it was broken before the fix)."""
235 root = _make_repo(tmp_path)
236 _make_commit(root, files={"song.mid": b"MIDI"})
237 out = tmp_path / "out.tar.gz"
238 result = _invoke(root, "--output", str(out), "--json")
239 assert result.exit_code == 0, f"--json flag is still broken: {result.output}"
240
241 def test_error_unknown_format_to_stderr(self, tmp_path: pathlib.Path) -> None:
242 """Unknown --format must exit nonzero (argparse choices= rejects it)."""
243 root = _make_repo(tmp_path)
244 _make_commit(root)
245 result = runner.invoke(cli, ["archive", "--format", "rar"], env=_env(root))
246 assert result.exit_code != 0
247
248 def test_error_prefix_traversal_to_stderr(self, tmp_path: pathlib.Path) -> None:
249 root = _make_repo(tmp_path)
250 _make_commit(root, files={"song.mid": b"data"})
251 result = runner.invoke(cli, ["archive", "--prefix", "../evil/"], env=_env(root))
252 assert result.exit_code != 0
253 # Error must NOT appear on stdout (it should be on stderr, which CliRunner merges)
254 # We verify exit code nonzero — that's the contract.
255
256 def test_error_no_commits_nonzero(self, tmp_path: pathlib.Path) -> None:
257 root = _make_repo(tmp_path)
258 result = runner.invoke(cli, ["archive"], env=_env(root))
259 assert result.exit_code != 0
260
261 def test_error_bad_ref_nonzero(self, tmp_path: pathlib.Path) -> None:
262 root = _make_repo(tmp_path)
263 _make_commit(root)
264 result = runner.invoke(cli, ["archive", "--ref", "nonexistent-branch-xyz"], env=_env(root))
265 assert result.exit_code != 0
266
267 def test_zip_slip_in_tar_manifest_skipped(self, tmp_path: pathlib.Path) -> None:
268 from muse.cli.commands.archive import _build_entries, _build_tar
269 root = _make_repo(tmp_path)
270 evil_id = _write_object(root, b"evil content")
271 safe_id = _write_object(root, b"safe content")
272 out = tmp_path / "test.tar.gz"
273 manifest = {"../../../etc/cron.d/evil": evil_id, "safe.txt": safe_id}
274 entries, _ = _build_entries(root, manifest, prefix="")
275 count = _build_tar(entries, out)
276 assert count == 1
277 with tarfile.open(out, "r:gz") as tf:
278 names = tf.getnames()
279 assert not any("etc" in n or "cron" in n for n in names)
280 assert "safe.txt" in names
281
282 def test_zip_slip_in_zip_manifest_skipped(self, tmp_path: pathlib.Path) -> None:
283 from muse.cli.commands.archive import _build_entries, _build_zip
284 root = _make_repo(tmp_path)
285 evil_id = _write_object(root, b"evil")
286 safe_id = _write_object(root, b"safe")
287 out = tmp_path / "test.zip"
288 manifest = {"../../../etc/evil": evil_id, "safe.txt": safe_id}
289 entries, _ = _build_entries(root, manifest, prefix="")
290 count = _build_zip(entries, out)
291 assert count == 1
292 with zipfile.ZipFile(out, "r") as zf:
293 names = zf.namelist()
294 assert not any("etc" in n for n in names)
295 assert "safe.txt" in names
296
297 def test_null_byte_in_manifest_path_skipped(self, tmp_path: pathlib.Path) -> None:
298 from muse.cli.commands.archive import _build_entries, _build_tar
299 root = _make_repo(tmp_path)
300 null_id = _write_object(root, b"null content")
301 safe_id = _write_object(root, b"safe content")
302 out = tmp_path / "null.tar.gz"
303 manifest = {"file\x00.txt": null_id, "safe.txt": safe_id}
304 entries, _ = _build_entries(root, manifest, prefix="")
305 count = _build_tar(entries, out)
306 assert count == 1
307
308 def test_ansi_in_commit_message_sanitized(self, tmp_path: pathlib.Path) -> None:
309 root = _make_repo(tmp_path)
310 _make_commit(root, files={"f.mid": b"data"}, message="\x1b[31mred\x1b[0m")
311 out = tmp_path / "ansi.tar.gz"
312 result = _invoke(root, "--output", str(out))
313 assert result.exit_code == 0
314 assert "\x1b" not in result.output
315
316 def test_no_muse_dir_in_archive(self, tmp_path: pathlib.Path) -> None:
317 """The .muse/ directory must never appear in any archive entry."""
318 root = _make_repo(tmp_path)
319 _make_commit(root, files={"song.mid": b"MIDI"})
320 out = tmp_path / "clean.tar.gz"
321 _invoke(root, "--output", str(out))
322 with tarfile.open(out, "r:gz") as tf:
323 names = tf.getnames()
324 assert not any(".muse" in n for n in names)
325
326
327 # ---------------------------------------------------------------------------
328 # JSON schema
329 # ---------------------------------------------------------------------------
330
331
332 class TestJsonSchema:
333 def test_json_tar_gz_schema(self, tmp_path: pathlib.Path) -> None:
334 root = _make_repo(tmp_path)
335 commit_id = _make_commit(root, files={"a.mid": b"data", "b.mid": b"more"})
336 out = tmp_path / "archive.tar.gz"
337 result = _invoke(root, "--output", str(out), "--json")
338 assert result.exit_code == 0
339 payload = _parse_json(result.output)
340 assert payload["format"] == "tar.gz"
341 assert payload["file_count"] == 2
342 assert payload["bytes"] > 0
343 assert payload["commit_id"] == commit_id
344 assert payload["branch"] == "main"
345 assert payload["ref"] is None
346 assert payload["path"] == str(out)
347
348 def test_json_zip_schema(self, tmp_path: pathlib.Path) -> None:
349 root = _make_repo(tmp_path)
350 commit_id = _make_commit(root, files={"track.mid": b"MIDI"})
351 out = tmp_path / "archive.zip"
352 result = _invoke(root, "--format", "zip", "--output", str(out), "--json")
353 assert result.exit_code == 0
354 payload = _parse_json(result.output)
355 assert payload["format"] == "zip"
356 assert payload["file_count"] == 1
357 assert payload["commit_id"] == commit_id
358
359 def test_json_ref_field_when_head(self, tmp_path: pathlib.Path) -> None:
360 root = _make_repo(tmp_path)
361 _make_commit(root, files={"f.mid": b"x"})
362 out = tmp_path / "a.tar.gz"
363 result = _invoke(root, "--output", str(out), "--json")
364 payload = _parse_json(result.output)
365 assert payload["ref"] is None
366
367 def test_json_ref_field_when_explicit_ref(self, tmp_path: pathlib.Path) -> None:
368 root = _make_repo(tmp_path)
369 commit_id = _make_commit(root, files={"f.mid": b"x"})
370 short = short_id(commit_id)
371 out = tmp_path / "a.tar.gz"
372 result = _invoke(root, "--ref", short, "--output", str(out), "--json")
373 payload = _parse_json(result.output)
374 assert payload["ref"] == short
375
376 def test_json_empty_snapshot(self, tmp_path: pathlib.Path) -> None:
377 root = _make_repo(tmp_path)
378 _make_commit(root, files={})
379 out = tmp_path / "empty.tar.gz"
380 result = _invoke(root, "--output", str(out), "--json")
381 payload = _parse_json(result.output)
382 assert payload["file_count"] == 0
383
384 def test_json_message_field(self, tmp_path: pathlib.Path) -> None:
385 root = _make_repo(tmp_path)
386 _make_commit(root, files={"f.mid": b"x"}, message="release v2.0")
387 out = tmp_path / "a.tar.gz"
388 result = _invoke(root, "--output", str(out), "--json")
389 payload = _parse_json(result.output)
390 assert payload["message"] == "release v2.0"
391
392
393 # ---------------------------------------------------------------------------
394 # Integration
395 # ---------------------------------------------------------------------------
396
397
398 class TestIntegration:
399 def test_default_output_path_is_sha12_dot_format(self, tmp_path: pathlib.Path) -> None:
400 root = _make_repo(tmp_path)
401 commit_id = _make_commit(root, files={"f.mid": b"data"})
402 result = _invoke(root)
403 assert result.exit_code == 0
404 # Filename uses bare hex (colons illegal on Windows).
405 bare_short = short_id(commit_id, strip=True)
406 assert bare_short in result.output
407 assert ".tar.gz" in result.output
408
409 def test_ref_with_short_sha(self, tmp_path: pathlib.Path) -> None:
410 root = _make_repo(tmp_path)
411 commit_id = _make_commit(root, files={"a.mid": b"MIDI"})
412 out = tmp_path / "ref.tar.gz"
413 # Use the full commit_id as ref (canonical sha256: prefixed form).
414 result = _invoke(root, "--ref", commit_id, "--output", str(out))
415 assert result.exit_code == 0
416 assert out.exists()
417
418 def test_missing_object_skipped_gracefully(self, tmp_path: pathlib.Path) -> None:
419 """If an object file is missing from the store, that entry is skipped — not a crash."""
420 from muse.cli.commands.archive import _build_entries, _build_tar
421 root = _make_repo(tmp_path)
422 # Write one good object, one phantom.
423 good_id = _write_object(root, b"good content")
424 phantom_id = long_id("a" * 64)# valid format but not written to store
425 out = tmp_path / "partial.tar.gz"
426 manifest = {"good.txt": good_id, "missing.txt": phantom_id}
427 entries, _ = _build_entries(root, manifest, prefix="")
428 count = _build_tar(entries, out)
429 assert count == 1
430 with tarfile.open(out, "r:gz") as tf:
431 names = tf.getnames()
432 assert "good.txt" in names
433 assert "missing.txt" not in names
434
435 def test_archive_bytes_match_committed_content(self, tmp_path: pathlib.Path) -> None:
436 """Content extracted from the archive must match what was committed."""
437 root = _make_repo(tmp_path)
438 content = b"exact bytes for round-trip verification"
439 _make_commit(root, files={"track.mid": content})
440 out = tmp_path / "roundtrip.tar.gz"
441 _invoke(root, "--output", str(out))
442 with tarfile.open(out, "r:gz") as tf:
443 member = tf.getmembers()[0]
444 extracted = tf.extractfile(member)
445 assert extracted is not None
446 assert extracted.read() == content
447
448 def test_zip_content_round_trip(self, tmp_path: pathlib.Path) -> None:
449 root = _make_repo(tmp_path)
450 content = b"zip round trip bytes"
451 _make_commit(root, files={"data.mid": content})
452 out = tmp_path / "rt.zip"
453 _invoke(root, "--format", "zip", "--output", str(out))
454 with zipfile.ZipFile(out, "r") as zf:
455 names = zf.namelist()
456 assert len(names) == 1
457 extracted = zf.read(names[0])
458 assert extracted == content
459
460 def test_prefix_appears_in_tar_gz(self, tmp_path: pathlib.Path) -> None:
461 root = _make_repo(tmp_path)
462 _make_commit(root, files={"song.mid": b"MIDI"})
463 out = tmp_path / "prefixed.tar.gz"
464 _invoke(root, "--output", str(out), "--prefix", "band-v1.0")
465 with tarfile.open(out, "r:gz") as tf:
466 names = tf.getnames()
467 assert all(n.startswith("band-v1.0/") for n in names)
468
469 def test_prefix_appears_in_zip(self, tmp_path: pathlib.Path) -> None:
470 root = _make_repo(tmp_path)
471 _make_commit(root, files={"song.mid": b"MIDI"})
472 out = tmp_path / "prefixed.zip"
473 _invoke(root, "--format", "zip", "--output", str(out), "--prefix", "band-v2.0")
474 with zipfile.ZipFile(out, "r") as zf:
475 names = zf.namelist()
476 assert all(n.startswith("band-v2.0/") for n in names)
477
478
479 # ---------------------------------------------------------------------------
480 # E2E — full lifecycle
481 # ---------------------------------------------------------------------------
482
483
484 class TestE2E:
485 def test_full_lifecycle_tar_gz(self, tmp_path: pathlib.Path) -> None:
486 """init → commit multiple files → archive → verify all files present."""
487 root = _make_repo(tmp_path)
488 files = {
489 "tracks/track_01.mid": b"MIDI track 1",
490 "tracks/track_02.mid": b"MIDI track 2",
491 "README.txt": b"Album readme",
492 }
493 _make_commit(root, files=files)
494 out = tmp_path / "album.tar.gz"
495 result = _invoke(root, "--output", str(out))
496 assert result.exit_code == 0
497 assert out.exists()
498 with tarfile.open(out, "r:gz") as tf:
499 names = tf.getnames()
500 assert len(names) == 3
501 assert any("track_01.mid" in n for n in names)
502 assert any("track_02.mid" in n for n in names)
503 assert any("README.txt" in n for n in names)
504
505 def test_deterministic_output(self, tmp_path: pathlib.Path) -> None:
506 """Two archive calls on the same commit produce byte-identical files."""
507 root = _make_repo(tmp_path)
508 _make_commit(root, files={"a.mid": b"AAA", "b.mid": b"BBB"})
509 out1 = tmp_path / "run1.tar.gz"
510 out2 = tmp_path / "run2.tar.gz"
511 _invoke(root, "--output", str(out1))
512 _invoke(root, "--output", str(out2))
513 # gzip includes a timestamp by default, so byte equality is not guaranteed;
514 # but the member names and content must be identical.
515 with tarfile.open(out1, "r:gz") as tf1, tarfile.open(out2, "r:gz") as tf2:
516 names1 = sorted(tf1.getnames())
517 names2 = sorted(tf2.getnames())
518 assert names1 == names2
519
520 def test_historical_ref_archive(self, tmp_path: pathlib.Path) -> None:
521 """Archiving an old commit SHA produces only files from that snapshot."""
522 root = _make_repo(tmp_path)
523 first_id = _make_commit(root, files={"v1.mid": b"v1 data"})
524 _make_commit(root, files={"v1.mid": b"v1 data", "v2.mid": b"v2 data"})
525 out = tmp_path / "historical.tar.gz"
526 result = _invoke(root, "--ref", short_id(first_id), "--output", str(out))
527 assert result.exit_code == 0
528 with tarfile.open(out, "r:gz") as tf:
529 names = tf.getnames()
530 assert any("v1.mid" in n for n in names)
531 assert not any("v2.mid" in n for n in names)
532
533 def test_output_text_shows_commit_short(self, tmp_path: pathlib.Path) -> None:
534 root = _make_repo(tmp_path)
535 commit_id = _make_commit(root, files={"f.mid": b"x"})
536 out = tmp_path / "out.tar.gz"
537 result = _invoke(root, "--output", str(out))
538 assert result.exit_code == 0
539 assert commit_id[:len("sha256:") + 12] in result.output
540
541 def test_output_text_shows_file_count(self, tmp_path: pathlib.Path) -> None:
542 root = _make_repo(tmp_path)
543 _make_commit(root, files={"a.mid": b"x", "b.mid": b"y", "c.mid": b"z"})
544 out = tmp_path / "out.tar.gz"
545 result = _invoke(root, "--output", str(out))
546 assert "3" in result.output
547
548
549 # ---------------------------------------------------------------------------
550 # Stress
551 # ---------------------------------------------------------------------------
552
553
554 class TestStress:
555 def test_200_file_archive(self, tmp_path: pathlib.Path) -> None:
556 root = _make_repo(tmp_path)
557 files = {f"track_{i:03d}.mid": f"MIDI content {i}".encode() for i in range(200)}
558 _make_commit(root, files=files)
559 out = tmp_path / "big.tar.gz"
560 result = _invoke(root, "--output", str(out), "--json")
561 assert result.exit_code == 0
562 payload = _parse_json(result.output)
563 assert payload["file_count"] == 200
564 with tarfile.open(out, "r:gz") as tf:
565 assert len(tf.getnames()) == 200
566
567 def test_concurrent_archives_different_repos(self, tmp_path: pathlib.Path) -> None:
568 """Concurrent archive operations on different repos must not interfere."""
569 errors: list[str] = []
570
571 def _run(idx: int) -> None:
572 repo_dir = tmp_path / f"repo_{idx}"
573 repo_dir.mkdir()
574 root = _make_repo(repo_dir)
575 _make_commit(root, files={f"track_{idx}.mid": f"content {idx}".encode()})
576 out = repo_dir / f"archive_{idx}.tar.gz"
577 try:
578 result = _invoke(root, "--output", str(out))
579 if result.exit_code != 0:
580 errors.append(f"Thread {idx} exit {result.exit_code}: {result.output[:200]}")
581 except Exception as exc:
582 errors.append(f"Thread {idx}: {exc}")
583
584 threads = [threading.Thread(target=_run, args=(i,)) for i in range(8)]
585 for t in threads:
586 t.start()
587 for t in threads:
588 t.join()
589
590 assert not errors, f"Concurrent archive failures: {errors}"
File History 3 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 134 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 140 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 143 days ago