Python · 9174 bytes Raw Blame History
1 """Merge resolver — parent directive + discovered `.dlm/` → effective verdict."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 from dlm.directives.discovery import discover_configs
8 from dlm.directives.merge import effective_config_for
9 from dlm.doc.schema import SourceDirective
10
11
12 def _write(path: Path, content: str) -> None:
13 path.parent.mkdir(parents=True, exist_ok=True)
14 path.write_text(content)
15
16
17 def _directive(
18 root: Path,
19 *,
20 include: tuple[str, ...] = ("**/*",),
21 exclude: tuple[str, ...] = (),
22 ) -> SourceDirective:
23 return SourceDirective(path=str(root), include=include, exclude=exclude)
24
25
26 # ---- Core include/exclude resolution ---------------------------------------
27
28
29 def test_directive_include_wins_without_training_yaml(tmp_path: Path) -> None:
30 _write(tmp_path / "src" / "main.py", "x")
31 _write(tmp_path / "src" / "readme.md", "x")
32 directive = _directive(tmp_path, include=("**/*.py",))
33 configs = discover_configs(tmp_path)
34 eff_py = effective_config_for(
35 tmp_path / "src" / "main.py",
36 source_root=tmp_path,
37 discovered=configs,
38 parent_directive=directive,
39 )
40 eff_md = effective_config_for(
41 tmp_path / "src" / "readme.md",
42 source_root=tmp_path,
43 discovered=configs,
44 parent_directive=directive,
45 )
46 assert eff_py.included is True
47 assert eff_md.included is False
48
49
50 def test_training_yaml_include_overrides_directive(tmp_path: Path) -> None:
51 _write(tmp_path / "src" / "main.py", "x")
52 _write(tmp_path / "src" / "doc.md", "x")
53 _write(
54 tmp_path / ".dlm" / "training.yaml",
55 "dlm_training_version: 1\ninclude: ['**/*.md']\n",
56 )
57 directive = _directive(tmp_path, include=("**/*.py",))
58 configs = discover_configs(tmp_path)
59 eff_py = effective_config_for(
60 tmp_path / "src" / "main.py",
61 source_root=tmp_path,
62 discovered=configs,
63 parent_directive=directive,
64 )
65 eff_md = effective_config_for(
66 tmp_path / "src" / "doc.md",
67 source_root=tmp_path,
68 discovered=configs,
69 parent_directive=directive,
70 )
71 # training.yaml include: **/*.md → py excluded, md included
72 assert eff_py.included is False
73 assert eff_md.included is True
74
75
76 def test_training_yaml_empty_include_inherits_parent(tmp_path: Path) -> None:
77 _write(tmp_path / "src" / "main.py", "x")
78 _write(
79 tmp_path / ".dlm" / "training.yaml",
80 "dlm_training_version: 1\nexclude: ['**/*.min.*']\n",
81 )
82 directive = _directive(tmp_path, include=("**/*.py",))
83 configs = discover_configs(tmp_path)
84 eff = effective_config_for(
85 tmp_path / "src" / "main.py",
86 source_root=tmp_path,
87 discovered=configs,
88 parent_directive=directive,
89 )
90 assert eff.included is True
91
92
93 def test_training_yaml_exclude_blocks_file(tmp_path: Path) -> None:
94 _write(tmp_path / "src" / "main.py", "x")
95 _write(tmp_path / "src" / "test_main.py", "x")
96 _write(
97 tmp_path / ".dlm" / "training.yaml",
98 "dlm_training_version: 1\nexclude: ['**/test_*.py']\n",
99 )
100 directive = _directive(tmp_path, include=("**/*.py",))
101 configs = discover_configs(tmp_path)
102 assert (
103 effective_config_for(
104 tmp_path / "src" / "main.py",
105 source_root=tmp_path,
106 discovered=configs,
107 parent_directive=directive,
108 ).included
109 is True
110 )
111 assert (
112 effective_config_for(
113 tmp_path / "src" / "test_main.py",
114 source_root=tmp_path,
115 discovered=configs,
116 parent_directive=directive,
117 ).included
118 is False
119 )
120
121
122 def test_parent_directive_exclude_blocks_file(tmp_path: Path) -> None:
123 _write(tmp_path / "src" / "main.py", "x")
124 directive = _directive(tmp_path, exclude=("**/*.py",))
125 configs = discover_configs(tmp_path)
126 eff = effective_config_for(
127 tmp_path / "src" / "main.py",
128 source_root=tmp_path,
129 discovered=configs,
130 parent_directive=directive,
131 )
132 assert eff.included is False
133
134
135 # ---- .dlm/ignore negation --------------------------------------------------
136
137
138 def test_ignore_rule_excludes_file(tmp_path: Path) -> None:
139 _write(tmp_path / "debug.log", "x")
140 _write(tmp_path / ".dlm" / "ignore", "*.log\n")
141 directive = _directive(tmp_path)
142 configs = discover_configs(tmp_path)
143 eff = effective_config_for(
144 tmp_path / "debug.log",
145 source_root=tmp_path,
146 discovered=configs,
147 parent_directive=directive,
148 )
149 assert eff.included is False
150
151
152 def test_ignore_negation_re_includes_file(tmp_path: Path) -> None:
153 _write(tmp_path / "debug.log", "x")
154 _write(tmp_path / "special.log", "x")
155 _write(tmp_path / ".dlm" / "ignore", "*.log\n!special.log\n")
156 directive = _directive(tmp_path)
157 configs = discover_configs(tmp_path)
158 assert (
159 effective_config_for(
160 tmp_path / "debug.log",
161 source_root=tmp_path,
162 discovered=configs,
163 parent_directive=directive,
164 ).included
165 is False
166 )
167 assert (
168 effective_config_for(
169 tmp_path / "special.log",
170 source_root=tmp_path,
171 discovered=configs,
172 parent_directive=directive,
173 ).included
174 is True
175 )
176
177
178 def test_deeper_ignore_negation_unblocks_parent_exclude(tmp_path: Path) -> None:
179 """Nearest-ancestor last-match-wins: a deeper .dlm/ignore with
180 `!path/specific.md` re-includes a file that a shallower rule
181 excluded."""
182 _write(tmp_path / "docs" / "guide.md", "x")
183 _write(tmp_path / ".dlm" / "ignore", "*.md\n")
184 _write(tmp_path / "docs" / ".dlm" / "ignore", "!guide.md\n")
185 directive = _directive(tmp_path)
186 configs = discover_configs(tmp_path)
187 eff = effective_config_for(
188 tmp_path / "docs" / "guide.md",
189 source_root=tmp_path,
190 discovered=configs,
191 parent_directive=directive,
192 )
193 assert eff.included is True
194
195
196 # ---- Default excludes ------------------------------------------------------
197
198
199 def test_default_excludes_apply_by_default(tmp_path: Path) -> None:
200 _write(tmp_path / ".git" / "HEAD", "x")
201 _write(tmp_path / "src" / "main.py", "x")
202 directive = _directive(tmp_path)
203 configs = discover_configs(tmp_path)
204 assert (
205 effective_config_for(
206 tmp_path / ".git" / "HEAD",
207 source_root=tmp_path,
208 discovered=configs,
209 parent_directive=directive,
210 ).included
211 is False
212 )
213 assert (
214 effective_config_for(
215 tmp_path / "src" / "main.py",
216 source_root=tmp_path,
217 discovered=configs,
218 parent_directive=directive,
219 ).included
220 is True
221 )
222
223
224 def test_exclude_defaults_false_disables_default_set(tmp_path: Path) -> None:
225 _write(tmp_path / ".git" / "HEAD", "x")
226 _write(
227 tmp_path / ".dlm" / "training.yaml",
228 "dlm_training_version: 1\nexclude_defaults: false\n",
229 )
230 directive = _directive(tmp_path)
231 configs = discover_configs(tmp_path)
232 eff = effective_config_for(
233 tmp_path / ".git" / "HEAD",
234 source_root=tmp_path,
235 discovered=configs,
236 parent_directive=directive,
237 )
238 # With defaults off, .git/ files are included by `**/*`.
239 assert eff.included is True
240
241
242 # ---- Metadata merging ------------------------------------------------------
243
244
245 def test_metadata_shallow_to_deep_merge(tmp_path: Path) -> None:
246 _write(tmp_path / "vendor" / "dep.py", "x")
247 _write(
248 tmp_path / ".dlm" / "training.yaml",
249 "dlm_training_version: 1\nmetadata:\n language: python\n domain: main\n",
250 )
251 _write(
252 tmp_path / "vendor" / ".dlm" / "training.yaml",
253 "dlm_training_version: 1\nmetadata:\n domain: vendor_override\n source: third_party\n",
254 )
255 directive = _directive(tmp_path)
256 configs = discover_configs(tmp_path)
257 eff = effective_config_for(
258 tmp_path / "vendor" / "dep.py",
259 source_root=tmp_path,
260 discovered=configs,
261 parent_directive=directive,
262 )
263 assert eff.tags == {
264 "language": "python", # from shallower
265 "domain": "vendor_override", # deeper overrides shallower
266 "source": "third_party", # from deeper only
267 }
268
269
270 def test_metadata_empty_when_no_training_yaml(tmp_path: Path) -> None:
271 _write(tmp_path / "main.py", "x")
272 _write(tmp_path / ".dlm" / "ignore", "*.log\n")
273 directive = _directive(tmp_path)
274 configs = discover_configs(tmp_path)
275 eff = effective_config_for(
276 tmp_path / "main.py",
277 source_root=tmp_path,
278 discovered=configs,
279 parent_directive=directive,
280 )
281 assert dict(eff.tags) == {}
282
283
284 def test_relpath_falls_back_to_filename_for_non_ancestor_anchor(tmp_path: Path) -> None:
285 from dlm.directives.merge import _relpath
286
287 file_path = tmp_path / "src" / "main.py"
288 _write(file_path, "x")
289 other_anchor = tmp_path / "elsewhere"
290 other_anchor.mkdir()
291 assert _relpath(file_path, other_anchor) == "main.py"