gabriel / muse public

test_reorder_only_semver_classification.py file-level

at main · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:c feat(code-test): audit cache internals, add cache_hit/--cache-status, c… · gabriel · Oct 22, 2026
1 """muse#91 — semver classifier flags breaking_changes on a reorder-only diff
2 despite its own "no semantic change" summary.
3
4 Root cause: `build_diff_ops()` (muse/plugins/code/symbol_diff.py) falls back
5 to a whole-file ReplaceOp labeled "<path> (reformatted -- no semantic
6 change)" when every symbol's content_id in a modified file is unchanged --
7 e.g. a pure reorder of two top-level functions, or a comment-only edit
8 (muse#92). `_classify_replace()` (muse/core/semver_classifier.py) pattern-
9 matches `new_summary` for "renamed to"/"signature"/"implementation" but had
10 no case for this exact fallback text, so it fell through to the conservative
11 "unrecognised summary" catch-all and was classified breaking/minor --
12 directly contradicting the op's own "no semantic change" claim one field
13 over.
14
15 Fixed by adding a recognized branch for "no semantic change" in
16 `_classify_replace()`, classifying it `invisible` (no API impact, no bump).
17
18 Reproduces the ticket's exact repro steps through the real CLI: commit a
19 file with two functions, commit again with ONLY their order swapped, and
20 assert the resulting commit's structured_delta reports sem_ver_bump="none"
21 and breaking_changes=[].
22 """
23 from __future__ import annotations
24
25 import json
26 import os
27 import pathlib
28
29 from tests.cli_test_helper import CliRunner, InvokeResult
30
31 runner = CliRunner()
32
33
34 def _run(repo: pathlib.Path, *args: str) -> InvokeResult:
35 saved = os.getcwd()
36 try:
37 os.chdir(repo)
38 return runner.invoke(None, list(args))
39 finally:
40 os.chdir(saved)
41
42
43 def test_reorder_only_diff_is_zero_bump_and_not_breaking(tmp_path: pathlib.Path) -> None:
44 repo = tmp_path
45 r = _run(repo, "init")
46 assert r.exit_code == 0, r.stderr
47
48 (repo / "extra.py").write_text(
49 "def helper_a(x):\n return x\n\n\ndef helper_b(x):\n return x * 2\n"
50 )
51 r = _run(repo, "code", "add", "extra.py")
52 assert r.exit_code == 0, r.stderr
53 r = _run(repo, "commit", "-m", "base")
54 assert r.exit_code == 0, r.stderr
55
56 # Reorder only -- swap the two functions, no other edits.
57 (repo / "extra.py").write_text(
58 "def helper_b(x):\n return x * 2\n\n\ndef helper_a(x):\n return x\n"
59 )
60 r = _run(repo, "code", "add", "extra.py")
61 assert r.exit_code == 0, r.stderr
62 r = _run(repo, "commit", "-m", "reorder only")
63 assert r.exit_code == 0, r.stderr
64
65 r = _run(repo, "read-commit", "HEAD", "--json")
66 assert r.exit_code == 0, r.stderr
67 delta = json.loads(r.output)["structured_delta"]
68
69 assert delta["sem_ver_bump"] == "none", (
70 f"reorder-only diff must not trigger a version bump: {delta}"
71 )
72 assert delta["breaking_changes"] == [], (
73 f"reorder-only diff must not be flagged breaking: {delta}"
74 )