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