gabriel / muse public
test_wire_step2a_pack.py python
167 lines 5.4 KB
Raw
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 24 days ago
1 """Wire protocol step 2A — Pack performance gate (client-side).
2
3 Ticket #45, Step 2A: BFS walk + mpack serialization must complete in under 5s.
4
5 This test measures the two operations that constitute the client-side pack phase:
6
7 build_mpack(root, [head], have=[]) — BFS walk + collect all objects
8 msgpack.packb(mpack, use_bin_type) — serialize mpack to wire bytes
9
10 Total < 5s
11
12 Repo size: 100 commits, 600 unique objects, 4 KiB blobs (~2.4 MB raw).
13 600 objects sits just above _PRESIGN_OBJECT_THRESHOLD (500) — the size class
14 that triggers the new mpack upload path instead of N individual PUTs.
15
16 If the assertion fails, step 2A is not done. Do not move to step 2B.
17 """
18 from __future__ import annotations
19
20 import datetime
21 import pathlib
22 import time
23
24 import msgpack
25 import pytest
26
27 from muse.core.object_store import write_object
28 from muse.core.mpack import build_mpack
29 from muse.core.paths import muse_dir
30 from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id
31 from muse.core.refs import write_branch_ref
32 from muse.core.commits import (
33 CommitRecord,
34 write_commit,
35 )
36 from muse.core.snapshots import (
37 SnapshotRecord,
38 write_snapshot,
39 )
40 from muse.core.types import blob_id
41
42
43 # ---------------------------------------------------------------------------
44 # Gate constants
45 # ---------------------------------------------------------------------------
46
47 _N_COMMITS = 100
48 _N_OBJECTS = 600 # just above _PRESIGN_OBJECT_THRESHOLD = 500
49 _BLOB_SIZE = 4096 # 4 KiB per object → ~2.4 MB raw total
50 _TOTAL_GATE_S = 5.0
51
52
53 # ---------------------------------------------------------------------------
54 # Repo fixture
55 # ---------------------------------------------------------------------------
56
57 def _make_repo(tmp: pathlib.Path) -> pathlib.Path:
58 tmp.mkdir(parents=True, exist_ok=True)
59 dot = muse_dir(tmp)
60 dot.mkdir()
61 (dot / "repo.json").write_text('{"repo_id":"step2a","owner":"gabriel"}')
62 for d in ("commits", "snapshots", "objects"):
63 (dot / d).mkdir()
64 (dot / "refs" / "heads").mkdir(parents=True)
65 (dot / "HEAD").write_text("ref: refs/heads/main\n")
66 (dot / "config.toml").write_text("")
67 return tmp
68
69
70 def _populate(repo: pathlib.Path) -> str:
71 """Write _N_OBJECTS blobs + _N_COMMITS chain; return tip commit ID."""
72 blobs: dict[str, str] = {}
73 for i in range(_N_OBJECTS):
74 data = f"step2a-{i:08d}-".encode() + b"x" * _BLOB_SIZE
75 oid = blob_id(data)
76 write_object(repo, oid, data)
77 blobs[f"file_{i:04d}.py"] = oid
78
79 sid = compute_snapshot_id(blobs)
80 write_snapshot(repo, SnapshotRecord(snapshot_id=sid, manifest=blobs))
81
82 parent: str | None = None
83 tip = ""
84 for i in range(_N_COMMITS):
85 ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
86 msg = f"commit-{i:05d}"
87 cid = compute_commit_id(
88 parent_ids=[parent] if parent else [],
89 snapshot_id=sid,
90 message=msg,
91 committed_at_iso=ts.isoformat(),
92 author="gabriel",
93 )
94 rec = CommitRecord(
95 commit_id=cid,
96 branch="main",
97 snapshot_id=sid,
98 message=msg,
99 committed_at=ts,
100 parent_commit_id=parent,
101 parent2_commit_id=None,
102 author="gabriel",
103 metadata={},
104 structured_delta=None,
105 sem_ver_bump="none",
106 breaking_changes=[],
107 agent_id="",
108 model_id="",
109 toolchain_id="",
110 prompt_hash="",
111 signature="",
112 signer_key_id="",
113 )
114 write_commit(repo, rec)
115 parent = cid
116 tip = cid
117
118 write_branch_ref(repo, "main", tip)
119 return tip
120
121
122 # ---------------------------------------------------------------------------
123 # THE test
124 # ---------------------------------------------------------------------------
125
126 def test_pack_step2a_performance_gate(tmp_path: pathlib.Path) -> None:
127 """Step 2A gate: build_mpack + msgpack.packb must complete in under 5s.
128
129 This is a pass/fail performance gate, not a benchmark. If the assertion
130 fails, step 2A is not done and step 2B must not be attempted.
131 """
132 repo = _make_repo(tmp_path / "repo")
133 head = _populate(repo)
134
135 t0 = time.perf_counter()
136
137 mpack = build_mpack(repo, [head], have=[])
138 wire_bytes = msgpack.packb(mpack, use_bin_type=True)
139
140 total_s = time.perf_counter() - t0
141
142 assert isinstance(wire_bytes, bytes), "msgpack.packb returned non-bytes"
143 assert len(wire_bytes) > 0, "mpack serialized to empty bytes"
144
145 n_commits = len(mpack.get("commits", []))
146 n_objects = len(mpack.get("blobs", []))
147 wire_kb = len(wire_bytes) / 1024
148
149 assert n_commits == _N_COMMITS, (
150 f"expected {_N_COMMITS} commits in mpack, got {n_commits}"
151 )
152 assert n_objects == _N_OBJECTS, (
153 f"expected {_N_OBJECTS} objects in mpack, got {n_objects}"
154 )
155
156 assert total_s < _TOTAL_GATE_S, (
157 f"Step 2A FAIL: build_mpack + packb took {total_s:.2f}s — gate is {_TOTAL_GATE_S}s "
158 f"({n_commits} commits, {n_objects} objects, {wire_kb:.1f} KiB wire)"
159 )
160
161 print(
162 f"\n Step 2A — Pack (client-side)\n"
163 f" Commits: {n_commits}\n"
164 f" Objects: {n_objects}\n"
165 f" Wire size: {wire_kb:.1f} KiB\n"
166 f" Total time: {total_s:.3f}s (gate {_TOTAL_GATE_S}s) ✅"
167 )
File History 1 commit
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 24 days ago