gabriel / muse public
test_cmd_describe_hardening.py python
845 lines 30.4 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 138 days ago
1 """Hardening test suite for ``muse describe``.
2
3 Coverage:
4 - Unit: describe_commit core — no tags, exact, distance, long, match_pattern,
5 first_parent, abbrev, exact_match, _MAX_WALK budget, multi-tag tie-break
6 - Security: ANSI injection in tag names sanitized in text output,
7 raw in JSON; --ref ANSI passthrough in error message
8 - Error routing: all user errors routed to stderr
9 - JSON schema: _DescribeJson shape, all fields present, repo_id + branch
10 - New flags: --match, --exact-match, --first-parent, --abbrev, --json
11 - Integration: tag walk across merge commits, --first-parent vs full walk
12 - E2E: help output, combined flags
13 - Stress: 5 000-commit chain, 200-tag repo, concurrent reads
14 """
15
16 from __future__ import annotations
17
18 import datetime
19 import hashlib
20 import json
21 import pathlib
22 import threading
23 from typing import TypedDict
24 from unittest.mock import patch
25
26 import pytest
27 from tests.cli_test_helper import CliRunner, InvokeResult
28
29 from muse.core.describe import describe_commit, _MAX_WALK
30 from muse.core.object_store import write_object
31 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
32 from muse.core.store import CommitRecord, SnapshotRecord, TagRecord, write_commit, write_snapshot, write_tag
33 from muse.core._types import Manifest, long_id
34
35 runner = CliRunner()
36 _REPO_ID = "describe-hard-test"
37
38
39 # ---------------------------------------------------------------------------
40 # Helpers
41 # ---------------------------------------------------------------------------
42
43
44 def _sha(data: bytes) -> str:
45 return long_id(hashlib.sha256(data).hexdigest())
46
47
48 def _init_repo(path: pathlib.Path, *, domain: str = "midi") -> pathlib.Path:
49 muse = path / ".muse"
50 for sub in ("commits", "snapshots", "objects", "refs/heads", f"tags/{_REPO_ID}"):
51 (muse / sub).mkdir(parents=True, exist_ok=True)
52 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
53 (muse / "repo.json").write_text(
54 json.dumps({"repo_id": _REPO_ID, "domain": domain}),
55 encoding="utf-8",
56 )
57 return path
58
59
60 def _make_commit(
61 root: pathlib.Path,
62 parent_id: str | None = None,
63 parent2_id: str | None = None,
64 content: bytes = b"data",
65 branch: str = "main",
66 ) -> str:
67 obj_id = _sha(content)
68 write_object(root, obj_id, content)
69 manifest = {f"f_{obj_id[len('sha256:'):len('sha256:') + 8]}.txt": obj_id}
70 snap_id = compute_snapshot_id(manifest)
71 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
72 committed_at = datetime.datetime.now(datetime.timezone.utc)
73 parent_ids = [pid for pid in (parent_id, parent2_id) if pid is not None]
74 commit_id = compute_commit_id(
75 parent_ids, snap_id, f"msg", committed_at.isoformat()
76 )
77 rec = CommitRecord(
78 commit_id=commit_id,
79 repo_id=_REPO_ID,
80 branch=branch,
81 snapshot_id=snap_id,
82 message="msg",
83 committed_at=committed_at,
84 parent_commit_id=parent_id,
85 parent2_commit_id=parent2_id,
86 )
87 write_commit(root, rec)
88 (root / ".muse" / "refs" / "heads" / branch).write_text(
89 commit_id, encoding="utf-8"
90 )
91 return commit_id
92
93
94 def _make_tag(root: pathlib.Path, tag: str, commit_id: str) -> None:
95 import uuid as _uuid
96 write_tag(
97 root,
98 TagRecord(
99 tag_id=str(_uuid.uuid4()),
100 tag=tag,
101 commit_id=commit_id,
102 repo_id=_REPO_ID,
103 created_at=datetime.datetime.now(datetime.timezone.utc),
104 ),
105 )
106
107
108 def _env(repo: pathlib.Path) -> Manifest:
109 return {"MUSE_REPO_ROOT": str(repo)}
110
111
112 def _invoke(args: list[str], env: Manifest) -> InvokeResult:
113 return runner.invoke(None, args, env=env)
114
115
116 class _DescribeOut(TypedDict):
117 commit_id: str
118 tag: str | None
119 distance: int
120 short_sha: str
121 name: str
122 exact: bool
123 repo_id: str
124 branch: str
125 duration_ms: float
126 exit_code: int
127
128
129 def _parse_json(result: InvokeResult) -> _DescribeOut:
130 raw = json.loads(result.output.strip())
131 return _DescribeOut(
132 commit_id=raw["commit_id"],
133 tag=raw["tag"],
134 distance=raw["distance"],
135 short_sha=raw["short_sha"],
136 name=raw["name"],
137 exact=raw["exact"],
138 repo_id=raw["repo_id"],
139 branch=raw["branch"],
140 duration_ms=raw["duration_ms"],
141 exit_code=raw["exit_code"],
142 )
143
144
145 # ---------------------------------------------------------------------------
146 # Unit: describe_commit core
147 # ---------------------------------------------------------------------------
148
149
150 def test_core_no_tags_returns_short_sha(tmp_path: pathlib.Path) -> None:
151 _init_repo(tmp_path)
152 cid = _make_commit(tmp_path, content=b"a")
153 r = describe_commit(tmp_path, _REPO_ID, cid)
154 assert r["tag"] is None
155 assert r["short_sha"] == cid[:len("sha256:") + 12]
156 assert r["name"] == r["short_sha"]
157 assert r["exact"] is False
158
159
160 def test_core_exact_tag(tmp_path: pathlib.Path) -> None:
161 _init_repo(tmp_path)
162 cid = _make_commit(tmp_path, content=b"b")
163 _make_tag(tmp_path, "v1.0.0", cid)
164 r = describe_commit(tmp_path, _REPO_ID, cid)
165 assert r["tag"] == "v1.0.0"
166 assert r["distance"] == 0
167 assert r["exact"] is True
168 assert r["name"] == "v1.0.0"
169
170
171 def test_core_distance_one(tmp_path: pathlib.Path) -> None:
172 _init_repo(tmp_path)
173 c1 = _make_commit(tmp_path, content=b"c1")
174 _make_tag(tmp_path, "v0.9", c1)
175 c2 = _make_commit(tmp_path, parent_id=c1, content=b"c2")
176 r = describe_commit(tmp_path, _REPO_ID, c2)
177 assert r["tag"] == "v0.9"
178 assert r["distance"] == 1
179 assert r["exact"] is False
180 assert r["name"] == "v0.9~1"
181
182
183 def test_core_long_format_on_tag(tmp_path: pathlib.Path) -> None:
184 _init_repo(tmp_path)
185 cid = _make_commit(tmp_path, content=b"long")
186 _make_tag(tmp_path, "v2.0.0", cid)
187 r = describe_commit(tmp_path, _REPO_ID, cid, long_format=True)
188 assert r["name"].startswith("v2.0.0-0-sha256:")
189
190
191 def test_core_long_format_with_distance(tmp_path: pathlib.Path) -> None:
192 _init_repo(tmp_path)
193 c1 = _make_commit(tmp_path, content=b"root")
194 _make_tag(tmp_path, "v1.0", c1)
195 c2 = _make_commit(tmp_path, parent_id=c1, content=b"next")
196 r = describe_commit(tmp_path, _REPO_ID, c2, long_format=True)
197 assert "-1-sha256:" in r["name"]
198
199
200 def test_core_abbrev_controls_sha_length(tmp_path: pathlib.Path) -> None:
201 _init_repo(tmp_path)
202 cid = _make_commit(tmp_path, content=b"abbrev")
203 r = describe_commit(tmp_path, _REPO_ID, cid, abbrev=8)
204 assert r["short_sha"] == cid[:len("sha256:") + 8]
205 assert r["short_sha"].startswith("sha256:")
206 assert len(r["short_sha"]) == len("sha256:") + 8
207
208
209 def test_core_match_pattern_filters_tags(tmp_path: pathlib.Path) -> None:
210 _init_repo(tmp_path)
211 cid = _make_commit(tmp_path, content=b"match")
212 _make_tag(tmp_path, "release-1", cid)
213 _make_tag(tmp_path, "v1.0.0", cid)
214 # Only semver tags — "release-1" excluded.
215 r = describe_commit(tmp_path, _REPO_ID, cid, match_pattern="v*")
216 assert r["tag"] == "v1.0.0"
217
218
219 def test_core_match_pattern_no_match_returns_sha(tmp_path: pathlib.Path) -> None:
220 _init_repo(tmp_path)
221 cid = _make_commit(tmp_path, content=b"nomatch")
222 _make_tag(tmp_path, "nightly-123", cid)
223 r = describe_commit(tmp_path, _REPO_ID, cid, match_pattern="v*")
224 assert r["tag"] is None
225 assert r["name"] == cid[:len("sha256:") + 12]
226
227
228 def test_core_exact_match_on_tag(tmp_path: pathlib.Path) -> None:
229 _init_repo(tmp_path)
230 cid = _make_commit(tmp_path, content=b"exact")
231 _make_tag(tmp_path, "v3.0", cid)
232 r = describe_commit(tmp_path, _REPO_ID, cid, exact_match=True)
233 assert r["tag"] == "v3.0"
234 assert r["exact"] is True
235
236
237 def test_core_exact_match_off_tag_returns_sha(tmp_path: pathlib.Path) -> None:
238 _init_repo(tmp_path)
239 c1 = _make_commit(tmp_path, content=b"root")
240 _make_tag(tmp_path, "v3.0", c1)
241 c2 = _make_commit(tmp_path, parent_id=c1, content=b"after")
242 r = describe_commit(tmp_path, _REPO_ID, c2, exact_match=True)
243 assert r["tag"] is None
244 assert r["name"] == c2[:len("sha256:") + 12]
245
246
247 def test_core_first_parent_skips_merge_branch(tmp_path: pathlib.Path) -> None:
248 """With --first-parent the tag on a merged branch is invisible."""
249 _init_repo(tmp_path)
250 # main: c1 → c3 (merge of feat)
251 # feat: c1 → c2 (tag here)
252 c1 = _make_commit(tmp_path, content=b"root")
253 c2 = _make_commit(tmp_path, parent_id=c1, content=b"feat", branch="feat")
254 _make_tag(tmp_path, "feat-tag", c2)
255 c3 = _make_commit(
256 tmp_path, parent_id=c1, parent2_id=c2, content=b"merge", branch="main"
257 )
258 # Without first_parent: feat-tag is reachable via second parent.
259 r_full = describe_commit(tmp_path, _REPO_ID, c3)
260 # With first_parent: only first parent chain; feat-tag not reachable.
261 r_fp = describe_commit(tmp_path, _REPO_ID, c3, first_parent=True)
262 assert r_fp["tag"] is None
263 # Full walk should find the tag (via c2).
264 assert r_full["tag"] == "feat-tag"
265
266
267 def test_core_multi_tag_same_commit_lex_greatest(tmp_path: pathlib.Path) -> None:
268 """When multiple tags point at the same commit, greatest lex name wins."""
269 _init_repo(tmp_path)
270 cid = _make_commit(tmp_path, content=b"multi")
271 _make_tag(tmp_path, "v1.0.0", cid)
272 _make_tag(tmp_path, "v2.0.0", cid)
273 _make_tag(tmp_path, "v1.5.0", cid)
274 r = describe_commit(tmp_path, _REPO_ID, cid)
275 assert r["tag"] == "v2.0.0"
276
277
278 def test_core_max_walk_budget(tmp_path: pathlib.Path) -> None:
279 """Walk stops at _MAX_WALK without crashing; returns short-SHA fallback."""
280 _init_repo(tmp_path)
281 # Inject a fake read_commit that always returns a parent so BFS never
282 # finds a commit-store miss — we just want to trigger the budget guard.
283 cid = _make_commit(tmp_path, content=b"budget")
284 _make_tag(tmp_path, "very-far", cid)
285
286 call_count = 0
287
288 import muse.core.store as _store
289 from muse.core.store import CommitRecord as _CR
290 import datetime as _dt
291
292 orig = _store.read_commit
293
294 def _fake_read(root: pathlib.Path, cid: str) -> _CR | None:
295 nonlocal call_count
296 call_count += 1
297 if call_count > _MAX_WALK + 5:
298 return None
299 fake_parent = hashlib.sha256(cid.encode()).hexdigest()
300 return _CR(
301 commit_id=cid,
302 repo_id=_REPO_ID,
303 branch="main",
304 snapshot_id="snap",
305 message="x",
306 committed_at=_dt.datetime.now(_dt.timezone.utc),
307 parent_commit_id=fake_parent,
308 )
309
310 with patch.object(_store, "read_commit", side_effect=_fake_read):
311 # Start from a commit far from any tag — BFS will exhaust budget.
312 far_commit = hashlib.sha256(b"far").hexdigest()
313 r = describe_commit(tmp_path, _REPO_ID, far_commit)
314
315 # Budget exhausted → tag not found → name is short SHA.
316 assert r["tag"] is None
317
318
319 # ---------------------------------------------------------------------------
320 # Security: ANSI injection in tag names
321 # ---------------------------------------------------------------------------
322
323
324 def test_ansi_in_tag_name_stripped_in_text_output(tmp_path: pathlib.Path) -> None:
325 _init_repo(tmp_path)
326 cid = _make_commit(tmp_path, content=b"ansi")
327 evil_tag = "v1.0\x1b[31mRED\x1b[0m"
328 _make_tag(tmp_path, evil_tag, cid)
329 result = _invoke(["describe"], _env(tmp_path))
330 assert result.exit_code == 0
331 assert "\x1b[31m" not in result.output
332
333
334 def test_ansi_in_tag_name_preserved_in_json(tmp_path: pathlib.Path) -> None:
335 """JSON output must not sanitize so callers see the raw value."""
336 _init_repo(tmp_path)
337 cid = _make_commit(tmp_path, content=b"ansi-json")
338 evil_tag = "v1.0\x1b[31mRED\x1b[0m"
339 _make_tag(tmp_path, evil_tag, cid)
340 result = _invoke(["describe", "--json"], _env(tmp_path))
341 assert result.exit_code == 0
342 data = _parse_json(result)
343 assert data["tag"] == evil_tag
344
345
346 # ---------------------------------------------------------------------------
347 # Error routing: all user errors go to stderr
348 # ---------------------------------------------------------------------------
349
350
351 def test_no_commits_error_on_stderr(tmp_path: pathlib.Path) -> None:
352 _init_repo(tmp_path)
353 result = _invoke(["describe"], _env(tmp_path))
354 assert result.exit_code != 0
355 assert result.stderr != "" or "commits" in result.output.lower()
356
357
358 def test_ref_not_found_error_on_stderr(tmp_path: pathlib.Path) -> None:
359 _init_repo(tmp_path)
360 _make_commit(tmp_path, content=b"x")
361 result = _invoke(["describe", "--ref", "nonexistent"], _env(tmp_path))
362 assert result.exit_code != 0
363
364
365 def test_require_tag_no_tags_error(tmp_path: pathlib.Path) -> None:
366 _init_repo(tmp_path)
367 _make_commit(tmp_path, content=b"no-tag")
368 result = _invoke(["describe", "--require-tag"], _env(tmp_path))
369 assert result.exit_code != 0
370
371
372 def test_exact_match_not_on_tag_error(tmp_path: pathlib.Path) -> None:
373 _init_repo(tmp_path)
374 c1 = _make_commit(tmp_path, content=b"c1")
375 _make_tag(tmp_path, "v1", c1)
376 _make_commit(tmp_path, parent_id=c1, content=b"c2")
377 result = _invoke(["describe", "--exact-match"], _env(tmp_path))
378 assert result.exit_code != 0
379
380
381 def test_abbrev_too_small_error(tmp_path: pathlib.Path) -> None:
382 _init_repo(tmp_path)
383 _make_commit(tmp_path, content=b"ab")
384 result = _invoke(["describe", "--abbrev", "2"], _env(tmp_path))
385 assert result.exit_code != 0
386
387
388 def test_abbrev_too_large_error(tmp_path: pathlib.Path) -> None:
389 _init_repo(tmp_path)
390 _make_commit(tmp_path, content=b"ab")
391 result = _invoke(["describe", "--abbrev", "65"], _env(tmp_path))
392 assert result.exit_code != 0
393
394
395 # ---------------------------------------------------------------------------
396 # JSON schema: _DescribeJson
397 # ---------------------------------------------------------------------------
398
399
400 def test_json_schema_all_fields(tmp_path: pathlib.Path) -> None:
401 _init_repo(tmp_path)
402 cid = _make_commit(tmp_path, content=b"schema")
403 _make_tag(tmp_path, "v1.0.0", cid)
404 result = _invoke(["describe", "--json"], _env(tmp_path))
405 assert result.exit_code == 0
406 data = _parse_json(result)
407 assert data["tag"] == "v1.0.0"
408 assert data["distance"] == 0
409 assert data["exact"] is True
410 assert data["repo_id"] == _REPO_ID
411 assert data["branch"] == "main"
412 assert data["commit_id"] == cid
413 assert data["short_sha"] == cid[:len("sha256:") + 12]
414
415
416 def test_json_schema_no_tag(tmp_path: pathlib.Path) -> None:
417 _init_repo(tmp_path)
418 cid = _make_commit(tmp_path, content=b"no-tag-json")
419 result = _invoke(["describe", "--json"], _env(tmp_path))
420 assert result.exit_code == 0
421 data = _parse_json(result)
422 assert data["tag"] is None
423 assert data["name"] == cid[:len("sha256:") + 12]
424 assert data["exact"] is False
425
426
427 def test_json_schema_with_distance(tmp_path: pathlib.Path) -> None:
428 _init_repo(tmp_path)
429 c1 = _make_commit(tmp_path, content=b"root")
430 _make_tag(tmp_path, "v0.1", c1)
431 _make_commit(tmp_path, parent_id=c1, content=b"next")
432 result = _invoke(["describe", "--json"], _env(tmp_path))
433 assert result.exit_code == 0
434 data = _parse_json(result)
435 assert data["tag"] == "v0.1"
436 assert data["distance"] == 1
437 assert data["exact"] is False
438
439
440 def test_json_abbrev_reflected(tmp_path: pathlib.Path) -> None:
441 _init_repo(tmp_path)
442 cid = _make_commit(tmp_path, content=b"abbrev-json")
443 result = _invoke(["describe", "--abbrev", "8", "--json"], _env(tmp_path))
444 assert result.exit_code == 0
445 data = _parse_json(result)
446 assert data["short_sha"].startswith("sha256:")
447 assert len(data["short_sha"]) == len("sha256:") + 8
448
449
450 # ---------------------------------------------------------------------------
451 # New flags: --match, --exact-match, --first-parent, --abbrev
452 # ---------------------------------------------------------------------------
453
454
455 def test_flag_match_filters_tags(tmp_path: pathlib.Path) -> None:
456 _init_repo(tmp_path)
457 cid = _make_commit(tmp_path, content=b"match-flag")
458 _make_tag(tmp_path, "nightly-1", cid)
459 _make_tag(tmp_path, "v1.0.0", cid)
460 result = _invoke(["describe", "--match", "v*", "--json"], _env(tmp_path))
461 assert result.exit_code == 0
462 data = _parse_json(result)
463 assert data["tag"] == "v1.0.0"
464
465
466 def test_flag_match_no_matching_tag(tmp_path: pathlib.Path) -> None:
467 _init_repo(tmp_path)
468 cid = _make_commit(tmp_path, content=b"match-none")
469 _make_tag(tmp_path, "nightly-1", cid)
470 result = _invoke(["describe", "--match", "v*", "--json"], _env(tmp_path))
471 assert result.exit_code == 0
472 data = _parse_json(result)
473 assert data["tag"] is None
474
475
476 def test_flag_exact_match_on_tag(tmp_path: pathlib.Path) -> None:
477 _init_repo(tmp_path)
478 cid = _make_commit(tmp_path, content=b"exact-flag")
479 _make_tag(tmp_path, "v1.0", cid)
480 result = _invoke(["describe", "--exact-match", "--json"], _env(tmp_path))
481 assert result.exit_code == 0
482 data = _parse_json(result)
483 assert data["exact"] is True
484
485
486 def test_flag_exact_match_off_tag_fails(tmp_path: pathlib.Path) -> None:
487 _init_repo(tmp_path)
488 c1 = _make_commit(tmp_path, content=b"em-root")
489 _make_tag(tmp_path, "v1.0", c1)
490 _make_commit(tmp_path, parent_id=c1, content=b"em-next")
491 result = _invoke(["describe", "--exact-match"], _env(tmp_path))
492 assert result.exit_code != 0
493
494
495 def test_flag_first_parent(tmp_path: pathlib.Path) -> None:
496 _init_repo(tmp_path)
497 c1 = _make_commit(tmp_path, content=b"fp-root")
498 c2 = _make_commit(tmp_path, parent_id=c1, content=b"feat-side", branch="feat")
499 _make_tag(tmp_path, "side-tag", c2)
500 c3 = _make_commit(
501 tmp_path, parent_id=c1, parent2_id=c2, content=b"fp-merge", branch="main"
502 )
503 # --first-parent should not see side-tag.
504 result = _invoke(["describe", "--first-parent", "--json"], _env(tmp_path))
505 assert result.exit_code == 0
506 data = _parse_json(result)
507 assert data["tag"] is None # side-tag not reachable via first-parent
508
509
510 def test_flag_abbrev(tmp_path: pathlib.Path) -> None:
511 _init_repo(tmp_path)
512 _make_commit(tmp_path, content=b"abbrev-flag")
513 result = _invoke(["describe", "--abbrev", "16", "--json"], _env(tmp_path))
514 assert result.exit_code == 0
515 data = _parse_json(result)
516 assert len(data["short_sha"]) == len("sha256:") + 16
517
518
519 # ---------------------------------------------------------------------------
520 # Integration
521 # ---------------------------------------------------------------------------
522
523
524 def test_integration_ref_to_branch_tip(tmp_path: pathlib.Path) -> None:
525 _init_repo(tmp_path)
526 c1 = _make_commit(tmp_path, content=b"ref-root")
527 _make_tag(tmp_path, "v10.0", c1)
528 _make_commit(tmp_path, parent_id=c1, content=b"ref-next")
529 # Describe the HEAD (which is 1 hop past the tag).
530 result = _invoke(["describe", "--json"], _env(tmp_path))
531 assert result.exit_code == 0
532 data = _parse_json(result)
533 assert data["distance"] == 1
534 assert data["tag"] == "v10.0"
535
536
537 def test_integration_long_and_match_combined(tmp_path: pathlib.Path) -> None:
538 _init_repo(tmp_path)
539 cid = _make_commit(tmp_path, content=b"combo")
540 _make_tag(tmp_path, "v5.0.0", cid)
541 result = _invoke(
542 ["describe", "--long", "--match", "v*", "--json"], _env(tmp_path)
543 )
544 assert result.exit_code == 0
545 data = _parse_json(result)
546 assert data["name"].startswith("v5.0.0-0-sha256:")
547
548
549 def test_integration_require_tag_passes_when_tag_exists(
550 tmp_path: pathlib.Path,
551 ) -> None:
552 _init_repo(tmp_path)
553 cid = _make_commit(tmp_path, content=b"req-tag")
554 _make_tag(tmp_path, "v7.0", cid)
555 result = _invoke(["describe", "--require-tag", "--json"], _env(tmp_path))
556 assert result.exit_code == 0
557
558
559 def test_integration_text_output_sanitized(tmp_path: pathlib.Path) -> None:
560 _init_repo(tmp_path)
561 cid = _make_commit(tmp_path, content=b"text-sanitize")
562 _make_tag(tmp_path, "v1.0\x1b[1mBOLD\x1b[0m", cid)
563 result = _invoke(["describe"], _env(tmp_path))
564 assert result.exit_code == 0
565 assert "\x1b[1m" not in result.output
566
567
568 # ---------------------------------------------------------------------------
569 # E2E: help output
570 # ---------------------------------------------------------------------------
571
572
573 def test_help_contains_new_flags() -> None:
574 result = _invoke(["describe", "--help"], {})
575 assert result.exit_code == 0
576 for flag in ("--match", "--exact-match", "--first-parent", "--abbrev", "--json"):
577 assert flag in result.output, f"Missing flag in help: {flag}"
578
579
580 def test_help_mentions_json_schema() -> None:
581 result = _invoke(["describe", "--help"], {})
582 assert "json" in result.output.lower()
583
584
585 # ---------------------------------------------------------------------------
586 # Stress: deep ancestry + many tags + concurrent reads
587 # ---------------------------------------------------------------------------
588
589
590 def test_stress_5000_commit_chain(tmp_path: pathlib.Path) -> None:
591 _init_repo(tmp_path)
592 prev: str | None = None
593 root_cid = ""
594 for i in range(5_000):
595 cid = _make_commit(tmp_path, parent_id=prev, content=f"s{i}".encode())
596 if i == 0:
597 root_cid = cid
598 prev = cid
599
600 _make_tag(tmp_path, "v-deep", root_cid)
601 assert prev is not None
602 r = describe_commit(tmp_path, _REPO_ID, prev)
603 assert r["tag"] == "v-deep"
604 assert r["distance"] == 4_999
605
606
607 def test_stress_200_tags_repo(tmp_path: pathlib.Path) -> None:
608 """Many tags — describe still picks the nearest one efficiently."""
609 _init_repo(tmp_path)
610 commits: list[str] = []
611 prev: str | None = None
612 for i in range(200):
613 cid = _make_commit(tmp_path, parent_id=prev, content=f"t{i}".encode())
614 commits.append(cid)
615 # Tag every 10th commit.
616 if i % 10 == 0:
617 _make_tag(tmp_path, f"v{i}.0", cid)
618 prev = cid
619
620 # HEAD is commits[-1], nearest tag is v190.0 (at commits[190]).
621 r = describe_commit(tmp_path, _REPO_ID, commits[-1])
622 assert r["tag"] == "v190.0"
623 assert r["distance"] == 9
624
625
626 def test_stress_concurrent_describe(tmp_path: pathlib.Path) -> None:
627 """Concurrent --json calls must all return consistent, valid JSON."""
628 _init_repo(tmp_path)
629 c1 = _make_commit(tmp_path, content=b"conc-root")
630 _make_tag(tmp_path, "v-conc", c1)
631 _make_commit(tmp_path, parent_id=c1, content=b"conc-next")
632
633 invoke_lock = threading.Lock()
634 errors: list[str] = []
635
636 def _worker() -> None:
637 with invoke_lock:
638 r = _invoke(["describe", "--json"], _env(tmp_path))
639 try:
640 assert r.exit_code == 0
641 data = _parse_json(r)
642 assert data["tag"] == "v-conc"
643 assert data["distance"] == 1
644 except Exception as exc:
645 errors.append(str(exc))
646
647 threads = [threading.Thread(target=_worker) for _ in range(8)]
648 for t in threads:
649 t.start()
650 for t in threads:
651 t.join()
652
653 assert errors == [], f"Concurrent failures: {errors}"
654
655
656 # ---------------------------------------------------------------------------
657 # JSON schema: duration_ms + exit_code always present
658 # ---------------------------------------------------------------------------
659
660
661 class TestJsonSchemaComplete:
662 """Every --json path includes duration_ms and exit_code."""
663
664 def test_elapsed_present_on_tag(self, tmp_path: pathlib.Path) -> None:
665 _init_repo(tmp_path)
666 cid = _make_commit(tmp_path, content=b"sc-tag")
667 _make_tag(tmp_path, "v1.0.0", cid)
668 r = _invoke(["describe", "--json"], _env(tmp_path))
669 assert r.exit_code == 0
670 raw = json.loads(r.output)
671 assert "duration_ms" in raw
672 assert "exit_code" in raw
673
674 def test_elapsed_present_no_tag(self, tmp_path: pathlib.Path) -> None:
675 _init_repo(tmp_path)
676 _make_commit(tmp_path, content=b"sc-notag")
677 r = _invoke(["describe", "--json"], _env(tmp_path))
678 assert r.exit_code == 0
679 raw = json.loads(r.output)
680 assert "duration_ms" in raw
681 assert "exit_code" in raw
682
683 def test_elapsed_present_with_distance(self, tmp_path: pathlib.Path) -> None:
684 _init_repo(tmp_path)
685 c1 = _make_commit(tmp_path, content=b"sc-dist-root")
686 _make_tag(tmp_path, "v0.1", c1)
687 _make_commit(tmp_path, parent_id=c1, content=b"sc-dist-next")
688 r = _invoke(["describe", "--json"], _env(tmp_path))
689 assert r.exit_code == 0
690 raw = json.loads(r.output)
691 assert "duration_ms" in raw
692 assert raw["exit_code"] == 0
693
694 def test_elapsed_present_long_format(self, tmp_path: pathlib.Path) -> None:
695 _init_repo(tmp_path)
696 cid = _make_commit(tmp_path, content=b"sc-long")
697 _make_tag(tmp_path, "v2.0.0", cid)
698 r = _invoke(["describe", "--long", "--json"], _env(tmp_path))
699 assert r.exit_code == 0
700 raw = json.loads(r.output)
701 assert "duration_ms" in raw
702 assert raw["exit_code"] == 0
703
704 def test_elapsed_present_with_match(self, tmp_path: pathlib.Path) -> None:
705 _init_repo(tmp_path)
706 cid = _make_commit(tmp_path, content=b"sc-match")
707 _make_tag(tmp_path, "v3.0.0", cid)
708 r = _invoke(["describe", "--match", "v*", "--json"], _env(tmp_path))
709 assert r.exit_code == 0
710 raw = json.loads(r.output)
711 assert "duration_ms" in raw
712
713 def test_elapsed_present_with_abbrev(self, tmp_path: pathlib.Path) -> None:
714 _init_repo(tmp_path)
715 _make_commit(tmp_path, content=b"sc-abbrev")
716 r = _invoke(["describe", "--abbrev", "8", "--json"], _env(tmp_path))
717 assert r.exit_code == 0
718 raw = json.loads(r.output)
719 assert "duration_ms" in raw
720 assert raw["exit_code"] == 0
721
722 def test_all_eight_base_fields_present(self, tmp_path: pathlib.Path) -> None:
723 _init_repo(tmp_path)
724 cid = _make_commit(tmp_path, content=b"sc-all")
725 _make_tag(tmp_path, "v9.0.0", cid)
726 r = _invoke(["describe", "--json"], _env(tmp_path))
727 assert r.exit_code == 0
728 raw = json.loads(r.output)
729 for field in ("commit_id", "tag", "distance", "short_sha", "name",
730 "exact", "repo_id", "branch", "duration_ms", "exit_code"):
731 assert field in raw, f"Missing field: {field}"
732
733 def test_exit_code_field_is_zero(self, tmp_path: pathlib.Path) -> None:
734 _init_repo(tmp_path)
735 cid = _make_commit(tmp_path, content=b"sc-exit")
736 _make_tag(tmp_path, "v10.0.0", cid)
737 r = _invoke(["describe", "--json"], _env(tmp_path))
738 assert r.exit_code == 0
739 raw = json.loads(r.output)
740 assert raw["exit_code"] == 0
741
742
743 # ---------------------------------------------------------------------------
744 # duration_ms: type and magnitude checks
745 # ---------------------------------------------------------------------------
746
747
748 class TestElapsedSeconds:
749 """duration_ms is a non-negative float in a reasonable range."""
750
751 def test_elapsed_is_float(self, tmp_path: pathlib.Path) -> None:
752 _init_repo(tmp_path)
753 _make_commit(tmp_path, content=b"el-float")
754 r = _invoke(["describe", "--json"], _env(tmp_path))
755 raw = json.loads(r.output)
756 assert isinstance(raw["duration_ms"], float)
757
758 def test_elapsed_non_negative(self, tmp_path: pathlib.Path) -> None:
759 _init_repo(tmp_path)
760 _make_commit(tmp_path, content=b"el-nonneg")
761 r = _invoke(["describe", "--json"], _env(tmp_path))
762 raw = json.loads(r.output)
763 assert raw["duration_ms"] >= 0.0
764
765 def test_elapsed_under_ten_seconds(self, tmp_path: pathlib.Path) -> None:
766 _init_repo(tmp_path)
767 _make_commit(tmp_path, content=b"el-under")
768 r = _invoke(["describe", "--json"], _env(tmp_path))
769 raw = json.loads(r.output)
770 assert raw["duration_ms"] < 10.0
771
772 def test_elapsed_with_tag(self, tmp_path: pathlib.Path) -> None:
773 _init_repo(tmp_path)
774 cid = _make_commit(tmp_path, content=b"el-tag")
775 _make_tag(tmp_path, "v1.2.3", cid)
776 r = _invoke(["describe", "--json"], _env(tmp_path))
777 raw = json.loads(r.output)
778 assert raw["duration_ms"] >= 0.0
779
780 def test_elapsed_with_require_tag(self, tmp_path: pathlib.Path) -> None:
781 _init_repo(tmp_path)
782 cid = _make_commit(tmp_path, content=b"el-req")
783 _make_tag(tmp_path, "v1.0", cid)
784 r = _invoke(["describe", "--require-tag", "--json"], _env(tmp_path))
785 assert r.exit_code == 0
786 raw = json.loads(r.output)
787 assert "duration_ms" in raw
788
789 def test_elapsed_six_decimal_places(self, tmp_path: pathlib.Path) -> None:
790 _init_repo(tmp_path)
791 _make_commit(tmp_path, content=b"el-prec")
792 r = _invoke(["describe", "--json"], _env(tmp_path))
793 raw = json.loads(r.output)
794 # round(..., 6) produces at most 6 decimal places — str check
795 s = str(raw["duration_ms"])
796 dec = s.split(".")[-1] if "." in s else ""
797 assert len(dec) <= 6
798
799
800 # ---------------------------------------------------------------------------
801 # exit_code field
802 # ---------------------------------------------------------------------------
803
804
805 class TestExitCode:
806 """exit_code field mirrors process exit code; always 0 on success."""
807
808 def test_exit_code_zero_no_tag(self, tmp_path: pathlib.Path) -> None:
809 _init_repo(tmp_path)
810 _make_commit(tmp_path, content=b"ec-notag")
811 r = _invoke(["describe", "--json"], _env(tmp_path))
812 assert r.exit_code == 0
813 assert json.loads(r.output)["exit_code"] == 0
814
815 def test_exit_code_zero_on_tag(self, tmp_path: pathlib.Path) -> None:
816 _init_repo(tmp_path)
817 cid = _make_commit(tmp_path, content=b"ec-tag")
818 _make_tag(tmp_path, "v1.0.0", cid)
819 r = _invoke(["describe", "--json"], _env(tmp_path))
820 assert r.exit_code == 0
821 assert json.loads(r.output)["exit_code"] == 0
822
823 def test_exit_code_zero_with_distance(self, tmp_path: pathlib.Path) -> None:
824 _init_repo(tmp_path)
825 c1 = _make_commit(tmp_path, content=b"ec-dist-root")
826 _make_tag(tmp_path, "v0.5", c1)
827 _make_commit(tmp_path, parent_id=c1, content=b"ec-dist-next")
828 r = _invoke(["describe", "--json"], _env(tmp_path))
829 assert r.exit_code == 0
830 assert json.loads(r.output)["exit_code"] == 0
831
832 def test_exit_code_zero_long_format(self, tmp_path: pathlib.Path) -> None:
833 _init_repo(tmp_path)
834 cid = _make_commit(tmp_path, content=b"ec-long")
835 _make_tag(tmp_path, "v2.0.0", cid)
836 r = _invoke(["describe", "--long", "--json"], _env(tmp_path))
837 assert r.exit_code == 0
838 assert json.loads(r.output)["exit_code"] == 0
839
840 def test_exit_code_zero_with_abbrev(self, tmp_path: pathlib.Path) -> None:
841 _init_repo(tmp_path)
842 _make_commit(tmp_path, content=b"ec-abbrev")
843 r = _invoke(["describe", "--abbrev", "10", "--json"], _env(tmp_path))
844 assert r.exit_code == 0
845 assert json.loads(r.output)["exit_code"] == 0
File History 2 commits
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 138 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 141 days ago