gabriel / muse public
test_cmd_type.py python
648 lines 23.3 KB
Raw
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 29 days ago
1 """End-to-end CLI tests for ``muse code type``.
2
3 Coverage:
4 - Default health report: text and JSON on a minimal repo.
5 - --any-blast-radius: finds callers up to depth 2.
6 - --drift: across 5 commits shows coverage trend.
7 - --migration-targets: top-5 ranked correctly.
8 - --diff HEAD~1: detects widened signature.
9 - --file: filter restricts output.
10 - --json: output is valid JSON with all required keys.
11 - Missing repo exits non-zero.
12 - Depth cap respected (no infinite BFS).
13 - Stress: --drift over 100 commits completes in < 10 s.
14 """
15
16 from __future__ import annotations
17
18 import datetime
19 import json
20 import pathlib
21 import time
22
23 import pytest
24 from muse.core.types import blob_id
25 from muse.core.object_store import write_object as _write_object_store
26
27 from tests.cli_test_helper import CliRunner
28 from muse.core.paths import muse_dir
29
30 runner = CliRunner()
31 cli = None
32
33
34 # ---------------------------------------------------------------------------
35 # Helpers
36 # ---------------------------------------------------------------------------
37
38
39 def _env(root: pathlib.Path) -> Manifest:
40 return {"MUSE_REPO_ROOT": str(root)}
41
42
43 def _write_object(root: pathlib.Path, content: bytes) -> str:
44 oid = blob_id(content)
45 _write_object_store(root, oid, content)
46 return oid
47
48
49 def _make_repo(
50 tmp: pathlib.Path,
51 *,
52 src: bytes | None = None,
53 branch: str = "main",
54 repo_id: str = "test-type-repo",
55 ) -> pathlib.Path:
56 """Minimal one-commit repo with a single Python source file."""
57 from muse.core.ids import hash_commit, hash_snapshot
58 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
59
60 if src is None:
61 src = (
62 b"def add(x: int, y: int) -> int:\n"
63 b" return x + y\n"
64 b"\n"
65 b"def untyped(a, b):\n"
66 b" return a + b\n"
67 )
68
69 dot_muse = muse_dir(tmp)
70 dot_muse.mkdir(exist_ok=True)
71 (dot_muse / "repo.json").write_text(f'{{"repo_id": "{repo_id}", "name": "test"}}')
72
73 oid = _write_object(tmp, src)
74 (tmp / "sample.py").write_bytes(src)
75
76 manifest: Manifest = {"sample.py": oid}
77 snap_id = hash_snapshot(manifest)
78 write_snapshot(tmp, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
79
80 committed_at = datetime.datetime(2026, 3, 26, tzinfo=datetime.timezone.utc)
81 commit_id = hash_commit(
82 parent_ids=[],
83 snapshot_id=snap_id,
84 message="initial",
85 committed_at_iso=committed_at.isoformat(),
86 author="test",
87 )
88 commit = CommitRecord(
89 commit_id=commit_id,
90 branch=branch,
91 snapshot_id=snap_id,
92 message="initial",
93 committed_at=committed_at,
94 author="test",
95 )
96 write_commit(tmp, commit)
97
98 refs = dot_muse / "refs" / "heads"
99 refs.mkdir(parents=True, exist_ok=True)
100 (refs / branch).write_text(commit_id)
101 (dot_muse / "HEAD").write_text(f"ref: refs/heads/{branch}\n")
102
103 return tmp
104
105
106 def _make_multi_commit_repo(
107 tmp: pathlib.Path,
108 srcs: list[bytes],
109 branch: str = "main",
110 repo_id: str = "drift-repo",
111 ) -> pathlib.Path:
112 """Repo with multiple sequential commits, each replacing sample.py."""
113 from muse.core.ids import hash_commit, hash_snapshot
114 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
115
116 dot_muse = muse_dir(tmp)
117 dot_muse.mkdir(exist_ok=True)
118 (dot_muse / "repo.json").write_text(f'{{"repo_id": "{repo_id}", "name": "test"}}')
119 refs = dot_muse / "refs" / "heads"
120 refs.mkdir(parents=True, exist_ok=True)
121
122 parent_ids: list[str] = []
123 base_time = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
124
125 for i, src in enumerate(srcs):
126 oid = _write_object(tmp, src)
127 manifest: Manifest = {"sample.py": oid}
128 snap_id = hash_snapshot(manifest)
129 write_snapshot(tmp, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
130
131 committed_at = base_time + datetime.timedelta(days=i)
132 msg = f"commit {i}"
133 commit_id = hash_commit(
134 parent_ids=parent_ids,
135 snapshot_id=snap_id,
136 message=msg,
137 committed_at_iso=committed_at.isoformat(),
138 author="test",
139 )
140 commit = CommitRecord(
141 commit_id=commit_id,
142 branch=branch,
143 snapshot_id=snap_id,
144 message=msg,
145 committed_at=committed_at,
146 author="test",
147 parent_commit_id=parent_ids[0] if parent_ids else None,
148 )
149 write_commit(tmp, commit)
150 parent_ids = [commit_id]
151
152 (refs / branch).write_text(parent_ids[-1])
153 (dot_muse / "HEAD").write_text(f"ref: refs/heads/{branch}\n")
154 (tmp / "sample.py").write_bytes(srcs[-1])
155 return tmp
156
157
158 # ---------------------------------------------------------------------------
159 # Fixtures
160 # ---------------------------------------------------------------------------
161
162
163 @pytest.fixture()
164 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
165 return _make_repo(tmp_path)
166
167
168 @pytest.fixture()
169 def empty_repo(tmp_path: pathlib.Path) -> pathlib.Path:
170 dot_muse = muse_dir(tmp_path)
171 dot_muse.mkdir()
172 (dot_muse / "repo.json").write_text('{"repo_id": "empty", "name": "empty"}')
173 refs = dot_muse / "refs" / "heads"
174 refs.mkdir(parents=True)
175 (dot_muse / "HEAD").write_text("ref: refs/heads/main\n")
176 return tmp_path
177
178
179 @pytest.fixture()
180 def no_repo(tmp_path: pathlib.Path) -> pathlib.Path:
181 return tmp_path
182
183
184 # ---------------------------------------------------------------------------
185 # Tests: default health report
186 # ---------------------------------------------------------------------------
187
188
189 class TestHealthReport:
190 def test_exits_zero(self, repo: pathlib.Path) -> None:
191 result = runner.invoke(cli, ["code", "type"], env=_env(repo))
192 assert result.exit_code == 0, result.output
193
194 def test_output_contains_health_header(self, repo: pathlib.Path) -> None:
195 result = runner.invoke(cli, ["code", "type"], env=_env(repo))
196 assert "Type Health" in result.output
197
198 def test_output_shows_coverage(self, repo: pathlib.Path) -> None:
199 result = runner.invoke(cli, ["code", "type"], env=_env(repo))
200 assert "%" in result.output
201
202 def test_json_output_valid(self, repo: pathlib.Path) -> None:
203 result = runner.invoke(cli, ["code", "type", "--json"], env=_env(repo))
204 assert result.exit_code == 0, result.output
205 data = json.loads(result.output)
206 required_keys = {
207 "total_symbols",
208 "fully_typed",
209 "partially_typed",
210 "untyped",
211 "any_count",
212 "coverage_fraction",
213 "symbols",
214 }
215 assert required_keys.issubset(data.keys())
216
217 def test_json_symbols_list(self, repo: pathlib.Path) -> None:
218 result = runner.invoke(cli, ["code", "type", "--json"], env=_env(repo))
219 data = json.loads(result.output)
220 assert isinstance(data["symbols"], list)
221 # Our fixture has 2 functions: add (typed) and untyped (not typed)
222 assert data["total_symbols"] == 2
223
224 def test_json_coverage_between_0_and_1(self, repo: pathlib.Path) -> None:
225 result = runner.invoke(cli, ["code", "type", "--json"], env=_env(repo))
226 data = json.loads(result.output)
227 assert 0.0 <= data["coverage_fraction"] <= 1.0
228
229 def test_file_filter_restricts_output(self, tmp_path: pathlib.Path) -> None:
230 src = b"def fn(x: int) -> int:\n return x\n"
231 _make_repo(tmp_path, src=src)
232 # Filter to "other/" which doesn't match "sample.py"
233 result = runner.invoke(
234 cli, ["code", "type", "--file", "other/", "--json"], env=_env(tmp_path)
235 )
236 assert result.exit_code == 0
237 data = json.loads(result.output)
238 assert data["total_symbols"] == 0
239
240 def test_no_repo_exits_nonzero(self, no_repo: pathlib.Path) -> None:
241 result = runner.invoke(cli, ["code", "type"], env=_env(no_repo))
242 assert result.exit_code != 0
243
244 def test_empty_repo_no_head_snapshot(self, empty_repo: pathlib.Path) -> None:
245 result = runner.invoke(cli, ["code", "type"], env=_env(empty_repo))
246 # Should exit non-zero with an informative message
247 assert result.exit_code != 0
248 assert "No snapshot" in result.stderr or "snapshot" in result.stderr.lower()
249
250
251 # ---------------------------------------------------------------------------
252 # Tests: --any-blast-radius
253 # ---------------------------------------------------------------------------
254
255
256 class TestAnyBlastRadius:
257 def test_any_return_exits_nonzero_when_callers_found(
258 self, tmp_path: pathlib.Path
259 ) -> None:
260 src = b"""\
261 import typing
262 from muse.core.paths import muse_dir
263 def load() -> typing.Any:
264 return {}
265
266 def process():
267 return load()
268 """
269 _make_repo(tmp_path, src=src)
270 result = runner.invoke(
271 cli,
272 ["code", "type", "--any-blast-radius", "sample.py::load"],
273 env=_env(tmp_path),
274 )
275 # load() has Any return AND has a caller (process), so exit non-zero
276 assert result.exit_code != 0
277
278 def test_no_any_exits_zero(self, repo: pathlib.Path) -> None:
279 # "add" is fully typed with no Any — blast radius is empty → exit 0
280 result = runner.invoke(
281 cli,
282 ["code", "type", "--any-blast-radius", "sample.py::add"],
283 env=_env(repo),
284 )
285 assert result.exit_code == 0
286
287 def test_json_output_has_nodes_key(self, tmp_path: pathlib.Path) -> None:
288 src = b"import typing\ndef f() -> typing.Any:\n return {}\n"
289 _make_repo(tmp_path, src=src)
290 result = runner.invoke(
291 cli,
292 ["code", "type", "--any-blast-radius", "sample.py::f", "--json"],
293 env=_env(tmp_path),
294 )
295 data = json.loads(result.output)
296 assert "nodes" in data
297 assert "address" in data
298
299 def test_depth_flag_accepted(self, repo: pathlib.Path) -> None:
300 result = runner.invoke(
301 cli,
302 ["code", "type", "--any-blast-radius", "sample.py::add", "--depth", "3"],
303 env=_env(repo),
304 )
305 # Should not raise — depth=3 is valid
306 assert result.exit_code == 0
307
308 def test_depth_over_max_capped(self, tmp_path: pathlib.Path) -> None:
309 src = b"from typing import Any\ndef f(x: Any) -> None:\n pass\n"
310 _make_repo(tmp_path, src=src)
311 result = runner.invoke(
312 cli,
313 ["code", "type", "--any-blast-radius", "sample.py::f", "--depth", "999"],
314 env=_env(tmp_path),
315 )
316 # Should not hang or error due to infinite BFS
317 assert result.exit_code in (0, 1) # 0 if no callers, 1 if callers found
318
319 def test_missing_address_exits_zero(self, repo: pathlib.Path) -> None:
320 result = runner.invoke(
321 cli,
322 ["code", "type", "--any-blast-radius", "sample.py::nonexistent"],
323 env=_env(repo),
324 )
325 assert result.exit_code == 0
326
327 def test_text_output_shows_address(self, tmp_path: pathlib.Path) -> None:
328 src = b"from typing import Any\ndef f(x: Any) -> None:\n pass\n"
329 _make_repo(tmp_path, src=src)
330 result = runner.invoke(
331 cli,
332 ["code", "type", "--any-blast-radius", "sample.py::f"],
333 env=_env(tmp_path),
334 )
335 assert "sample.py::f" in result.output
336
337
338 # ---------------------------------------------------------------------------
339 # Tests: --drift
340 # ---------------------------------------------------------------------------
341
342
343 class TestDrift:
344 def test_drift_on_single_commit(self, repo: pathlib.Path) -> None:
345 result = runner.invoke(cli, ["code", "type", "--drift"], env=_env(repo))
346 assert result.exit_code == 0
347
348 def test_drift_json_has_branch_and_drift(self, repo: pathlib.Path) -> None:
349 result = runner.invoke(
350 cli, ["code", "type", "--drift", "--json"], env=_env(repo)
351 )
352 assert result.exit_code == 0
353 data = json.loads(result.output)
354 assert "branch" in data
355 assert "drift" in data
356 assert isinstance(data["drift"], list)
357
358 def test_drift_json_point_keys(self, repo: pathlib.Path) -> None:
359 result = runner.invoke(
360 cli, ["code", "type", "--drift", "--json"], env=_env(repo)
361 )
362 data = json.loads(result.output)
363 assert len(data["drift"]) >= 1
364 point = data["drift"][0]
365 required = {
366 "commit_id",
367 "committed_at",
368 "message",
369 "coverage_fraction",
370 "any_count",
371 "delta_coverage",
372 }
373 assert required.issubset(point.keys())
374
375 def test_drift_five_commits_coverage_trend(self, tmp_path: pathlib.Path) -> None:
376 """Coverage improves across 5 commits (none typed → fully typed)."""
377 srcs: list[bytes] = [
378 b"def a(x, y): return x\n",
379 b"def a(x: int, y): return x\n",
380 b"def a(x: int, y: int): return x\n",
381 b"def a(x: int, y: int) -> int: return x\n",
382 b"def a(x: int, y: int) -> int:\n return x\n",
383 ]
384 _make_multi_commit_repo(tmp_path, srcs)
385 result = runner.invoke(
386 cli, ["code", "type", "--drift", "--json"], env=_env(tmp_path)
387 )
388 assert result.exit_code == 0
389 data = json.loads(result.output)
390 coverages = [p["coverage_fraction"] for p in data["drift"]]
391 assert len(coverages) == 5
392 # Coverage should be non-decreasing (or at worst plateau)
393 for i in range(1, len(coverages)):
394 assert coverages[i] >= coverages[0] - 0.01 # allow tiny float noise
395
396 def test_drift_since_flag_accepted(self, repo: pathlib.Path) -> None:
397 result = runner.invoke(
398 cli,
399 ["code", "type", "--drift", "--since", "2020-01-01"],
400 env=_env(repo),
401 )
402 assert result.exit_code == 0
403
404 def test_drift_since_invalid_date_exits_nonzero(self, repo: pathlib.Path) -> None:
405 result = runner.invoke(
406 cli,
407 ["code", "type", "--drift", "--since", "not-a-date"],
408 env=_env(repo),
409 )
410 assert result.exit_code != 0
411
412 def test_drift_max_commits_flag(self, repo: pathlib.Path) -> None:
413 result = runner.invoke(
414 cli,
415 ["code", "type", "--drift", "--max-commits", "10"],
416 env=_env(repo),
417 )
418 assert result.exit_code == 0
419
420 def test_drift_text_shows_trend(self, repo: pathlib.Path) -> None:
421 result = runner.invoke(cli, ["code", "type", "--drift"], env=_env(repo))
422 assert "Type Coverage Drift" in result.output or "Coverage" in result.output
423
424
425 # ---------------------------------------------------------------------------
426 # Tests: --migration-targets
427 # ---------------------------------------------------------------------------
428
429
430 class TestMigrationTargets:
431 def test_exits_zero(self, repo: pathlib.Path) -> None:
432 result = runner.invoke(
433 cli, ["code", "type", "--migration-targets"], env=_env(repo)
434 )
435 assert result.exit_code == 0
436
437 def test_json_has_targets_key(self, repo: pathlib.Path) -> None:
438 result = runner.invoke(
439 cli, ["code", "type", "--migration-targets", "--json"], env=_env(repo)
440 )
441 assert result.exit_code == 0
442 data = json.loads(result.output)
443 assert "targets" in data
444 assert isinstance(data["targets"], list)
445
446 def test_untyped_fn_appears_in_targets(self, repo: pathlib.Path) -> None:
447 result = runner.invoke(
448 cli, ["code", "type", "--migration-targets", "--json"], env=_env(repo)
449 )
450 data = json.loads(result.output)
451 addresses = [t["address"] for t in data["targets"]]
452 # "sample.py::untyped" should appear since it has no annotations
453 assert any("untyped" in addr for addr in addresses)
454
455 def test_fully_typed_repo_shows_no_targets(self, tmp_path: pathlib.Path) -> None:
456 src = b"def fn(x: int, y: str) -> float:\n return float(x)\n"
457 _make_repo(tmp_path, src=src)
458 result = runner.invoke(
459 cli, ["code", "type", "--migration-targets", "--json"], env=_env(tmp_path)
460 )
461 data = json.loads(result.output)
462 assert data["targets"] == []
463
464 def test_top_n_limits_output(self, tmp_path: pathlib.Path) -> None:
465 fns = b"\n".join([f"def fn{i}(x, y): pass".encode() for i in range(20)])
466 _make_repo(tmp_path, src=fns)
467 result = runner.invoke(
468 cli,
469 ["code", "type", "--migration-targets", "--top", "5", "--json"],
470 env=_env(tmp_path),
471 )
472 data = json.loads(result.output)
473 assert len(data["targets"]) <= 5
474
475 def test_targets_sorted_by_priority(self, repo: pathlib.Path) -> None:
476 result = runner.invoke(
477 cli, ["code", "type", "--migration-targets", "--json"], env=_env(repo)
478 )
479 data = json.loads(result.output)
480 scores = [t["priority_score"] for t in data["targets"]]
481 assert scores == sorted(scores, reverse=True)
482
483 def test_target_has_required_keys(self, repo: pathlib.Path) -> None:
484 result = runner.invoke(
485 cli, ["code", "type", "--migration-targets", "--json"], env=_env(repo)
486 )
487 data = json.loads(result.output)
488 if data["targets"]:
489 t = data["targets"][0]
490 required = {"address", "caller_count", "type_score", "priority_score"}
491 assert required.issubset(t.keys())
492
493
494 # ---------------------------------------------------------------------------
495 # Tests: --diff REF
496 # ---------------------------------------------------------------------------
497
498
499 class TestDiff:
500 def _make_two_commit_repo(
501 self,
502 tmp: pathlib.Path,
503 src_a: bytes,
504 src_b: bytes,
505 ) -> pathlib.Path:
506 return _make_multi_commit_repo(tmp, [src_a, src_b])
507
508 def test_identical_snapshots_exit_zero(self, tmp_path: pathlib.Path) -> None:
509 src = b"def fn(x: int) -> int:\n return x\n"
510 _make_multi_commit_repo(tmp_path, [src, src])
511 result = runner.invoke(
512 cli, ["code", "type", "--diff", "HEAD~1"], env=_env(tmp_path)
513 )
514 assert result.exit_code == 0
515
516 def test_widened_signature_exits_nonzero(self, tmp_path: pathlib.Path) -> None:
517 src_a = b"def fn(x: str) -> str:\n return x\n"
518 src_b = b"from typing import Any\ndef fn(x: Any) -> str:\n return str(x)\n"
519 self._make_two_commit_repo(tmp_path, src_a, src_b)
520 result = runner.invoke(
521 cli, ["code", "type", "--diff", "HEAD~1"], env=_env(tmp_path)
522 )
523 # Widened → exit non-zero
524 assert result.exit_code != 0
525
526 def test_narrowed_signature_exits_zero(self, tmp_path: pathlib.Path) -> None:
527 src_a = b"from typing import Any\ndef fn(x: Any) -> str:\n return str(x)\n"
528 src_b = b"def fn(x: str) -> str:\n return x\n"
529 self._make_two_commit_repo(tmp_path, src_a, src_b)
530 result = runner.invoke(
531 cli, ["code", "type", "--diff", "HEAD~1"], env=_env(tmp_path)
532 )
533 # Narrowed only → exit zero
534 assert result.exit_code == 0
535
536 def test_json_has_conflicts_key(self, tmp_path: pathlib.Path) -> None:
537 src = b"def fn(x: int) -> int:\n return x\n"
538 _make_multi_commit_repo(tmp_path, [src, src])
539 result = runner.invoke(
540 cli, ["code", "type", "--diff", "HEAD~1", "--json"], env=_env(tmp_path)
541 )
542 assert result.exit_code == 0
543 data = json.loads(result.output)
544 assert "conflicts" in data
545 assert "diff_ref" in data
546
547 def test_conflict_has_required_keys(self, tmp_path: pathlib.Path) -> None:
548 src_a = b"def fn(x: str) -> str:\n return x\n"
549 src_b = b"from typing import Any\ndef fn(x: Any) -> str:\n return str(x)\n"
550 self._make_two_commit_repo(tmp_path, src_a, src_b)
551 result = runner.invoke(
552 cli, ["code", "type", "--diff", "HEAD~1", "--json"], env=_env(tmp_path)
553 )
554 data = json.loads(result.output)
555 if data["conflicts"]:
556 c = data["conflicts"][0]
557 required = {"address", "signature_a", "signature_b", "change_kind"}
558 assert required.issubset(c.keys())
559
560 def test_invalid_ref_exits_nonzero(self, repo: pathlib.Path) -> None:
561 result = runner.invoke(
562 cli, ["code", "type", "--diff", "HEAD~999"], env=_env(repo)
563 )
564 assert result.exit_code != 0
565
566 def test_text_output_shows_type_diff_header(self, tmp_path: pathlib.Path) -> None:
567 src = b"def fn(x: int) -> int:\n return x\n"
568 _make_multi_commit_repo(tmp_path, [src, src])
569 result = runner.invoke(
570 cli, ["code", "type", "--diff", "HEAD~1"], env=_env(tmp_path)
571 )
572 assert "Type Diff" in result.output or "HEAD" in result.output
573
574
575 # ---------------------------------------------------------------------------
576 # Stress test
577 # ---------------------------------------------------------------------------
578
579
580 class TestStress:
581 def test_drift_100_commits_under_10_seconds(self, tmp_path: pathlib.Path) -> None:
582 """--drift over 100 commits must complete in under 10 seconds."""
583 from muse.core.ids import hash_commit, hash_snapshot
584 from muse.core.store import (
585 CommitRecord,
586 SnapshotRecord,
587 write_commit,
588 write_snapshot,
589 )
590
591 dot_muse = muse_dir(tmp_path)
592 dot_muse.mkdir()
593 repo_id = "stress-drift"
594 (dot_muse / "repo.json").write_text(
595 f'{{"repo_id": "{repo_id}", "name": "stress"}}'
596 )
597 refs = dot_muse / "refs" / "heads"
598 refs.mkdir(parents=True)
599
600 parent_ids: list[str] = []
601 base_time = datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc)
602
603 # Write a single typed Python file once — reuse its object ID across commits.
604 src = b"def fn(x: int, y: int) -> int:\n return x + y\n"
605 oid = _write_object(tmp_path, src)
606
607 for i in range(100):
608 manifest: Manifest = {"sample.py": oid}
609 snap_id = hash_snapshot(manifest)
610 write_snapshot(tmp_path, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
611
612 committed_at = base_time + datetime.timedelta(days=i)
613 msg = f"commit {i}"
614 commit_id = hash_commit(
615 parent_ids=parent_ids,
616 snapshot_id=snap_id,
617 message=msg,
618 committed_at_iso=committed_at.isoformat(),
619 author="test",
620 )
621 commit = CommitRecord(
622 commit_id=commit_id,
623 branch="main",
624 snapshot_id=snap_id,
625 message=msg,
626 committed_at=committed_at,
627 author="test",
628 parent_commit_id=parent_ids[0] if parent_ids else None,
629 )
630 write_commit(tmp_path, commit)
631 parent_ids = [commit_id]
632
633 (refs / "main").write_text(parent_ids[-1])
634 (dot_muse / "HEAD").write_text("ref: refs/heads/main\n")
635 (tmp_path / "sample.py").write_bytes(src)
636
637 start = time.monotonic()
638 result = runner.invoke(
639 cli,
640 ["code", "type", "--drift", "--json"],
641 env=_env(tmp_path),
642 )
643 elapsed = time.monotonic() - start
644
645 assert result.exit_code == 0, result.output
646 data = json.loads(result.output)
647 assert len(data["drift"]) == 100
648 assert elapsed < 10.0, f"--drift 100 commits took {elapsed:.2f}s — too slow"
File History 2 commits
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 29 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago