返回 CodeWhale
test_check_source_structure_budget.py
根目录 / scripts / test_check_source_structure_budget.py
1 #!/usr/bin/env python3
2 """Hermetic tests for check-source-structure-budget.py."""
3
4 from __future__ import annotations
5
6 import importlib.util
7 import json
8 import os
9 import stat
10 import subprocess
11 import sys
12 import tempfile
13 import unittest
14 from pathlib import Path
15 from unittest import mock
16
17 ROOT = Path(__file__).resolve().parents[1]
18 SCRIPT = ROOT / "scripts" / "check-source-structure-budget.py"
19 SPEC = importlib.util.spec_from_file_location("check_source_structure_budget", SCRIPT)
20 assert SPEC and SPEC.loader
21 mod = importlib.util.module_from_spec(SPEC)
22 sys.modules[SPEC.name] = mod
23 SPEC.loader.exec_module(mod)
24
25
26 def snapshot() -> mod.StructureSnapshot:
27 return mod.StructureSnapshot(
28 ("codewhale-a", "codewhale-b"),
29 ("codewhale-a:codew", "codewhale-a:codewhale"),
30 {
31 "crates/a/src/lib.rs": 1_200,
32 "crates/b/src/main.rs": 1_050,
33 "crates/b/src/small.rs": 200,
34 },
35 )
36
37
38 class StructureBudgetTests(unittest.TestCase):
39 def test_equal_snapshot_passes(self) -> None:
40 current = snapshot()
41 budget = mod.validate_budget(mod.budget_document(current))
42 self.assertEqual(mod.compare(current, budget), ([], []))
43
44 def test_deletion_and_decomposition_pass_freely(self) -> None:
45 budget = mod.validate_budget(mod.budget_document(snapshot()))
46 current = mod.StructureSnapshot(
47 ("codewhale-a",),
48 ("codewhale-a:codew",),
49 {
50 "crates/a/src/lib.rs": 900,
51 "crates/b/src/small.rs": 150,
52 },
53 )
54 failures, improvements = mod.compare(current, budget)
55 self.assertEqual(failures, [])
56 self.assertGreaterEqual(len(improvements), 4)
57
58 def test_added_package_and_binary_fail(self) -> None:
59 baseline = snapshot()
60 budget = mod.validate_budget(mod.budget_document(baseline))
61 current = mod.StructureSnapshot(
62 (*baseline.workspace_packages, "codewhale-c"),
63 (*baseline.binary_targets, "codewhale-c:whale-helper"),
64 dict(baseline.module_lines),
65 )
66 failures, _ = mod.compare(current, budget)
67 self.assertTrue(any("packages added" in failure for failure in failures))
68 self.assertTrue(any("binary targets added" in failure for failure in failures))
69
70 def test_new_large_file_and_aggregate_growth_fail(self) -> None:
71 baseline = snapshot()
72 budget = mod.validate_budget(mod.budget_document(baseline))
73 modules = dict(baseline.module_lines)
74 modules["crates/c/src/lib.rs"] = mod.LARGE_MODULE_THRESHOLD
75 failures, _ = mod.compare(
76 mod.StructureSnapshot(
77 baseline.workspace_packages,
78 baseline.binary_targets,
79 modules,
80 ),
81 budget,
82 )
83 self.assertTrue(any("new thousand-line" in failure for failure in failures))
84 self.assertTrue(any("aggregate owned" in failure for failure in failures))
85
86 def test_line_neutral_ownership_move_passes_but_larger_maximum_fails(self) -> None:
87 baseline = snapshot()
88 budget = mod.validate_budget(mod.budget_document(baseline))
89 moved = dict(baseline.module_lines)
90 moved["crates/a/src/lib.rs"] -= 50
91 moved["crates/b/src/main.rs"] += 50
92 self.assertEqual(
93 mod.compare(
94 mod.StructureSnapshot(
95 baseline.workspace_packages, baseline.binary_targets, moved
96 ),
97 budget,
98 )[0],
99 [],
100 )
101
102 moved["crates/a/src/lib.rs"] += 51
103 failures, _ = mod.compare(
104 mod.StructureSnapshot(
105 baseline.workspace_packages, baseline.binary_targets, moved
106 ),
107 budget,
108 )
109 self.assertTrue(any("largest module grew" in failure for failure in failures))
110
111 def test_budget_document_round_trips_with_exact_types(self) -> None:
112 document = mod.budget_document(snapshot())
113 budget = mod.validate_budget(document)
114 self.assertEqual(budget.max_module_lines, 1_200)
115 self.assertEqual(budget.max_total_owned_rust_lines, 2_450)
116 document["max_module_lines"] = True
117 with self.assertRaisesRegex(mod.StructureBudgetError, "non-negative integer"):
118 mod.validate_budget(document)
119
120 def test_source_discovery_excludes_owned_test_modules(self) -> None:
121 with tempfile.TemporaryDirectory() as tmp:
122 root = Path(tmp)
123 source = root / "crates" / "a" / "src"
124 (source / "tests").mkdir(parents=True)
125 (source / "lib.rs").write_text("one\ntwo\n", encoding="utf-8")
126 (source / "tests.rs").write_text("test\n", encoding="utf-8")
127 (source / "tests" / "fixture.rs").write_text("fixture\n", encoding="utf-8")
128 discovered = [path.relative_to(root).as_posix() for path in mod.production_rust_files(root)]
129 self.assertEqual(discovered, ["crates/a/src/lib.rs"])
130
131 def test_metadata_command_is_locked_no_deps_and_offline(self) -> None:
132 completed = subprocess.CompletedProcess(
133 args=[], returncode=0, stdout=json.dumps({"packages": []}), stderr=""
134 )
135 with mock.patch.object(mod.subprocess, "run", return_value=completed) as run:
136 self.assertEqual(mod.cargo_metadata(ROOT), {"packages": []})
137 command = run.call_args.args[0]
138 environment = run.call_args.kwargs["env"]
139 self.assertEqual(command[0:2], ["cargo", "metadata"])
140 self.assertIn("--offline", command)
141 self.assertIn("--locked", command)
142 self.assertIn("--no-deps", command)
143 self.assertEqual(environment["CARGO_NET_OFFLINE"], "true")
144
145 def test_atomic_update_preserves_permissions_and_cleans_failure(self) -> None:
146 with tempfile.TemporaryDirectory() as tmp:
147 path = Path(tmp) / "budget.json"
148 path.write_text("{}", encoding="utf-8")
149 os.chmod(path, 0o640)
150 mod.write_json_atomic(path, mod.budget_document(snapshot()))
151 self.assertEqual(stat.S_IMODE(path.stat().st_mode), 0o640)
152 original = path.read_text(encoding="utf-8")
153 with (
154 mock.patch.object(mod.os, "replace", side_effect=OSError("stop")),
155 self.assertRaisesRegex(OSError, "stop"),
156 ):
157 mod.write_json_atomic(path, {"replacement": True})
158 self.assertEqual(path.read_text(encoding="utf-8"), original)
159 self.assertEqual(list(Path(tmp).glob(".budget.json.*.tmp")), [])
160
161
162 if __name__ == "__main__":
163 raise SystemExit(unittest.main())
164
164 lines PYTHON