3"""Both-direction selftests for the Markdown reference validator."""
5from __future__
import annotations
10from collections.abc
import Callable
11from pathlib
import Path
13sys.path.insert(0, str(Path(__file__).resolve().parents[1] /
"dev"))
15import markdown_references
as core
16from git_environment
import isolated_git_environment
17from markdown_reference_policy
import (
19 LIBWEBP_ABSENCE_CLAUSE,
20 PARSER_RUNTIME_LIMIT_SECONDS,
23AnchorRef = core.AnchorRef
26REPO_ROOT = core.REPO_ROOT
27_bare_declaration_findings = core.bare_declaration_findings
28_context_sha256 = core.context_sha256
29_declared_bare_code_file = core.declared_bare_code_file
30_declared_work_fixture = core.declared_work_fixture
33_is_vendor = core.is_vendor
34_path_reason = core.path_reason
35_tracked_basename_index = core.tracked_basename_index
36check_tree = core.check_tree
37parse_document = core.parse_document
39_SCRIPT_ROOT =
"scripts"
40_CITES_MARKER =
"CITES" +
"-OK"
43def _init_fixture(root: Path) ->
None:
44 """Create a small git-owned Markdown fixture tree."""
45 if _git(root,
"init",
"-q").returncode != 0:
46 _fail(
"selftest git init failed")
47 (root /
"docs").mkdir()
48 (root /
"docs" /
"planned").mkdir()
49 (root /
"docs" /
"qualification" /
"release").mkdir(parents=
True)
50 (root /
"infra" /
"live").mkdir(parents=
True)
51 (root /
"scripts").mkdir()
52 (root /
"tests" /
"core" /
"src").mkdir(parents=
True)
53 (root /
"tools").mkdir()
54 (root /
"tools" /
"sample" /
"src").mkdir(parents=
True)
55 (root /
"libs" /
"third_party" /
"sample").mkdir(parents=
True)
56 (root /
"libs" /
"third_party" /
"sample" /
"src").mkdir()
57 (root /
"libs" /
"third_party" /
"sample" /
"src" /
"live.c").write_text(
58 "/* fixture */\n", encoding=
"ascii"
60 (root /
"libs" /
"board_alpha" /
"src" /
"boot").mkdir(parents=
True)
61 (root /
"libs" /
"board_alpha" /
"ld").mkdir()
62 (root /
"scripts" /
"live.sh").write_text(
"#!/bin/sh\n", encoding=
"ascii")
63 (root /
"other").mkdir()
64 (root /
"other" /
"live.sh").write_text(
"#!/bin/sh\n", encoding=
"ascii")
65 (root /
"tests" /
"core" /
"src" /
"test_live.c").write_text(
66 "/* fixture */\n", encoding=
"ascii"
68 (root /
"scripts" /
"README.md").write_text(
"# Script authority\n", encoding=
"ascii")
69 (root /
"tools" /
"README.md").write_text(
70 "# Tool authority\n\nA future\n"
71 "`tools/<tool>/third_party/<component>` subtree is reserved for a dependency "
72 "used exclusively by that tool.\nNo current dependency qualifies.\n",
75 (root /
"docs" /
"live.md").write_text(
"# Live heading\n", encoding=
"ascii")
76 (root /
"current.md").write_text(
"# Current sibling\n", encoding=
"ascii")
77 (root /
"docs" /
"UPPER.MD").write_text(
"# Uppercase extension\n", encoding=
"ascii")
78 (root /
"docs" /
"planned" /
"README.md").write_text(
79 "# Planned release layout\n", encoding=
"ascii"
81 (root /
"docs" /
"qualification" /
"release" /
"README.md").write_text(
82 "# Planned qualification releases\n", encoding=
"ascii"
84 (root /
"CMakeLists.txt").write_text(
"# component\n", encoding=
"ascii")
85 (root /
".gitignore").write_text(
86 "infra/live/private/\ninfra/removed/private/\n", encoding=
"ascii"
90def _run_fixture_cases(
91 root: Path, cases: tuple[tuple[str, int, str], ...], failures: list[str]
92) -> list[dict[str, object]]:
93 """Run end-to-end document cases and return the last inventory."""
94 inventory: list[dict[str, object]] = []
95 for body, expected, label
in cases:
96 (root /
"README.md").write_text(body, encoding=
"ascii")
97 (root /
"libs" /
"third_party" /
"sample" /
"README.md").write_text(
98 "[upstream dead](missing.md)\n", encoding=
"ascii"
100 if _git(root,
"add",
".").returncode != 0:
101 _fail(
"selftest git add failed")
102 findings, _, inventory = check_tree(root, enforce_census=
False, include_untracked=
True)
103 if len(findings) != expected:
104 rendered = [finding.render()
for finding
in findings]
105 failures.append(f
"{label}: expected {expected}, got {rendered}")
109def _check_vendor_scope_cases(root: Path, failures: list[str]) ->
None:
110 """Prove authored vendor indexes are checked while upstream prose is not."""
111 authored_index = root /
"libs" /
"third_party" /
"README.md"
112 upstream_readme = root /
"libs" /
"third_party" /
"sample" /
"README.md"
113 (root /
"docs" /
"live.md").write_text(
"# Live heading\n", encoding=
"ascii")
114 authored_index.write_text(
"See docs/ABSENT.md.\n", encoding=
"ascii")
115 upstream_readme.write_text(
"[upstream dead](missing.md)\n", encoding=
"ascii")
116 if _git(root,
"add",
".").returncode != 0:
117 _fail(
"selftest vendor-scope git add failed")
118 findings, _, _ = check_tree(root, enforce_census=
False, include_untracked=
True)
119 if {finding.path
for finding
in findings} != {
"libs/third_party/README.md"}:
120 failures.append(f
"vendor scope classification regressed: {findings}")
122 authored_index.write_text(f
"See {_SCRIPT_ROOT}/live.sh.\n", encoding=
"ascii")
123 findings, _, _ = check_tree(root, enforce_census=
False, include_untracked=
True)
125 failures.append(f
"nested upstream Markdown was unexpectedly parsed: {findings}")
128def _check_component_cases(root: Path, failures: list[str]) ->
None:
129 """Prove nearest-component and non-component relative paths fail closed."""
130 (root /
"README.md").write_text(
"# Root\n", encoding=
"ascii")
131 (root /
"component").mkdir()
132 (root /
"component" /
"CMakeLists.txt").write_text(
"# component\n", encoding=
"ascii")
133 (root /
"component" /
"README.md").write_text(
134 "See ../dead.md and src/dead.c.\n", encoding=
"ascii"
136 (root /
"component" /
"docs").mkdir()
137 (root /
"component" /
"docs" /
"README.md").write_text(
138 "Nested docs still bind src/dead.c to the component.\n", encoding=
"ascii"
140 if _git(root,
"add",
".").returncode != 0:
141 _fail(
"selftest component git add failed")
142 findings, _, _ = check_tree(root, enforce_census=
False, include_untracked=
True)
143 values = {finding.value
for finding
in findings}
144 if values != {
"../dead.md",
"src/dead.c"}:
145 failures.append(f
"missing component-relative paths escaped: {findings}")
147 (root /
"component" /
"README.md").write_text(
"# Component\n", encoding=
"ascii")
148 (root /
"component" /
"docs" /
"README.md").write_text(
"# Nested docs\n", encoding=
"ascii")
149 (root /
"docs" /
"live.md").write_text(
150 "A non-component src/ABSENT.c must not disappear.\n", encoding=
"ascii"
152 findings, _, _ = check_tree(root, enforce_census=
False, include_untracked=
True)
153 if not any(finding.value ==
"src/ABSENT.c" for finding
in findings):
154 failures.append(
"non-component relative source path escaped")
157def _check_tests_path_authority_cases(root: Path) ->
None:
158 """Build the root/component tests fixture the three case sets share."""
159 component = root /
"nested_component"
160 (component /
"docs").mkdir(parents=
True)
161 (component /
"tests" /
"local" /
"src").mkdir(parents=
True)
162 (component /
"CMakeLists.txt").write_text(
"# component\n", encoding=
"ascii")
163 (component /
"docs" /
"README.md").write_text(
"# Nested\n", encoding=
"ascii")
164 (component /
"tests" /
"local" /
"src" /
"test_local.c").write_text(
165 "/* local */\n", encoding=
"ascii"
167 (root /
"tests" /
"shared" /
"src").mkdir(parents=
True)
168 (root /
"tests" /
"shared" /
"src" /
"test_shared.c").write_text(
169 "/* root */\n", encoding=
"ascii"
171 (root /
"tests" /
"root-owner").mkdir()
174def _tests_path_reason(root: Path, failures: list[str]) -> Callable[[str], str |
None]:
175 """Return a bounded reason() closure over one component tests fixture."""
176 source =
"nested_component/docs/README.md"
178 def reason(token: str) -> str |
None:
179 parsed = parse_document(f
"`{token}`\n")
180 if len(parsed.paths) != 1:
181 failures.append(f
"tests path fixture did not parse exactly: {token}")
182 return "parse failure"
183 return _path_reason(root, source, parsed.paths[0])
188def _check_tests_path_live_cases(root: Path, failures: list[str]) ->
None:
189 """Prove every live root/component tests path resolves to one authority."""
190 reason = _tests_path_reason(root, failures)
192 "tests/core/src/test_live.c",
193 "tests/shared/src/test_*.c",
194 "tests/<category>/src/test_live.c",
195 "tests/root-owner/build-cov/summary.json",
196 "tests/local/src/test_local.c",
199 f
"live root/component tests path was rejected: {token}"
201 if reason(token)
is not None
204 if reason(
"tests/local/src/absent.c")
is None:
205 failures.append(
"absent component-local tests path escaped")
208def _check_tests_path_ambiguous_cases(root: Path, failures: list[str]) ->
None:
209 """Prove a path claimable by two authorities fails closed, not silently."""
210 reason = _tests_path_reason(root, failures)
211 component = root /
"nested_component"
212 (component /
"tests" /
"shared" /
"src").mkdir(parents=
True)
213 (component /
"tests" /
"shared" /
"src" /
"test_shared.c").write_text(
214 "/* local duplicate */\n", encoding=
"ascii"
216 (component /
"tests" /
"root-owner").mkdir()
218 "tests/shared/src/test_shared.c",
219 "tests/shared/src/test_*.c",
220 "tests/<category>/src/test_*.c",
221 "tests/root-owner/build-cov/summary.json",
223 for token
in ambiguous:
224 detail = reason(token)
225 if detail
is None or "ambiguous" not in detail:
226 failures.append(f
"ambiguous tests path did not fail closed: {token}: {detail}")
229def _check_tests_path_hostile_cases(root: Path, failures: list[str]) ->
None:
230 """Prove traversal and placeholder escapes out of a component are refused."""
231 reason = _tests_path_reason(root, failures)
232 (root /
"outside.md").write_text(
"# Outside component\n", encoding=
"ascii")
234 "tests/local/../../../outside.md",
235 "tests/${NAME}/../../outside.md",
236 "tests/${lower}/src/test.c",
239 f
"hostile tests path escaped: {token}" for token
in hostile
if reason(token)
is None
243def _check_parser_cases(failures: list[str]) ->
None:
244 """Prove anchor and reference parsers against structural edge cases."""
245 parsed = parse_document(
"# Same\n# Same\n[second](#same-1)\n")
247 "same-1" not in parsed.anchors
248 or parsed.anchor_collisions
249 or parsed.links != (LinkRef(3,
"#same-1"),)
251 failures.append(
"duplicate heading anchors or inline-link parsing regressed")
252 duplicate = parse_document(
'<a id="same"></a>\n# Same\n')
253 if duplicate.anchor_collisions != (AnchorRef(2,
"same"),):
254 failures.append(
"explicit/generated duplicate anchors were not rejected")
255 footnote = parse_document(
"[^note]: prose, not a link definition\n")
257 failures.append(
"a footnote was misclassified as a link definition")
258 image = parse_document(
"![diagram][missing-image]\n")
259 if image.missing_references != (LinkRef(1,
"missing-image"),):
260 failures.append(
"reference-style image without a definition escaped")
261 shortcut = parse_document(
"See [docs/missing.md] for the plan.\n")
262 if shortcut.missing_references != (LinkRef(1,
"docs/missing.md"),):
263 failures.append(
"path-looking shortcut reference without a definition escaped")
264 fixture = PathRef(1, 0,
"../escape",
"A key that looks like a path")
265 if not _declared_work_fixture(
"scripts/dev/work/tests/fixtures/bad_key.md", fixture):
266 failures.append(
"exact workflow fixture declaration was rejected")
267 wrong_fixture = PathRef(1, 0,
"../other",
"A key that looks like a path")
268 if _declared_work_fixture(
"scripts/dev/work/tests/fixtures/bad_key.md", wrong_fixture):
269 failures.append(
"a different workflow fixture escape was accepted")
270 declared_document = parse_document(
271 (REPO_ROOT /
"docs/HIL_SUITE.md").read_text(encoding=
"utf-8")
273 declared_name =
"dwf.h"
274 declared_bare = next(ref
for ref
in declared_document.paths
if ref.token == declared_name)
275 if _declared_bare_code_file(
"docs/HIL_SUITE.md", declared_bare)
is None:
276 failures.append(
"exact declared external bare filename was rejected")
277 if _declared_bare_code_file(
"docs/OTHER.md", declared_bare)
is not None:
278 failures.append(
"declared bare filename escaped from the wrong source")
279 wrong_bare = PathRef(1, 0,
"missing.h",
"The package installs `missing.h`.")
280 if _declared_bare_code_file(
"docs/HIL_SUITE.md", wrong_bare)
is not None:
281 failures.append(
"a different bare filename inherited an exact declaration")
282 altered_bare = PathRef(1, 0,
"dwf.h",
"Use `dwf.h` from this repository now.")
283 if _declared_bare_code_file(
"docs/HIL_SUITE.md", altered_bare)
is not None:
284 failures.append(
"declared bare filename escaped through unrelated current-use prose")
287def _check_bare_parser_cases(failures: list[str]) ->
None:
288 """Prove the bounded bare-file parser is complete and linear-time."""
289 bounded_names = tuple(f
"sample.{suffix}" for suffix
in BARE_FILE_SUFFIXES)
290 bounded = parse_document(
" ".join(f
"`{name}`" for name
in bounded_names) +
"\n")
291 if tuple(ref.token
for ref
in bounded.paths) != bounded_names:
292 failures.append(
"bounded bare-file suffix census was not parsed exactly")
293 if parse_document(
"`property.value`\n").paths:
294 failures.append(
"arbitrary dotted tokens escaped the bounded suffix census")
295 started = time.perf_counter()
296 long_document = parse_document(f
"`{'a' * 250_000}`\n")
297 elapsed = time.perf_counter() - started
298 if long_document.paths
or elapsed > PARSER_RUNTIME_LIMIT_SECONDS:
299 failures.append(f
"bare-filename parser is non-linear ({elapsed:.3f}s)")
302def _check_soup_cases(root: Path, failures: list[str]) ->
None:
303 """Prove upstream-relative paths bind to the declared local vendor root."""
304 (root /
"docs" /
"SOUP").mkdir()
305 source =
"docs/SOUP/libwebp.md"
306 (root / source).write_text(
307 "# Sample\n\n- **Local path**: `libs/third_party/sample/`\n",
310 live = PathRef(3, 0,
"src/live.c",
"`src/live.c`")
311 absent = PathRef(3, 0,
"src/ABSENT.c",
"`src/ABSENT.c`")
312 declared_absent = PathRef(3, 0,
"src/enc/*.c", LIBWEBP_ABSENCE_CLAUSE)
313 malicious_absent = PathRef(
317 "`src/ABSENT.c` is required; `src/enc/*.c` are **not** vendored",
319 if _path_reason(root, source, live)
is not None:
320 failures.append(
"declared SOUP local source was rejected")
321 if _path_reason(root, source, absent)
is None:
322 failures.append(
"absent SOUP local source escaped")
323 if _path_reason(root, source, declared_absent)
is not None:
324 failures.append(
"exact declared SOUP absence was rejected")
325 if _path_reason(root, source, malicious_absent)
is None:
326 failures.append(
"an unrelated SOUP path escaped through a negative claim")
327 malicious_whitelisted = PathRef(
331 "`src/enc/*.c` is required; documentation files are **not** vendored",
333 if _path_reason(root, source, malicious_whitelisted)
is None:
334 failures.append(
"a whitelisted SOUP token escaped through an unrelated negative clause")
337def _check_planned_path_cases(root: Path, failures: list[str]) ->
None:
338 """Prove only named future namespaces can rely on policy authority."""
342 "docs/qualification/release/<tag>/conformance.md",
343 "will be added under `docs/qualification/release/<tag>/conformance.md`",
345 if _path_reason(root,
"docs/qualification/SQAP.md", release)
is not None:
346 failures.append(
"authorized qualification release namespace was rejected")
347 if _path_reason(root,
"README.md", release)
is None:
348 failures.append(
"qualification release namespace escaped from an unauthorized source")
350 tool_private = PathRef(
353 "tools/<tool>/third_party/<component>",
354 "`tools/<tool>/third_party/<component>` subtree is reserved for a dependency "
355 "used exclusively by that tool.",
357 if _path_reason(root,
"tools/README.md", tool_private)
is not None:
358 failures.append(
"authorized future tool-private namespace was rejected")
359 malicious_tool_private = PathRef(
362 "tools/<tool>/third_party/<component>",
363 "Use `tools/<tool>/third_party/<component>` now.",
365 if _path_reason(root,
"tools/README.md", malicious_tool_private)
is None:
366 failures.append(
"tool-private namespace escaped without planned-language binding")
367 misleading_future = PathRef(
370 "tools/<tool>/third_party/<component>",
371 "Future work is separate; use `tools/<tool>/third_party/<component>` now.",
373 if _path_reason(root,
"tools/README.md", misleading_future)
is None:
374 failures.append(
"tool-private namespace escaped through unrelated future prose")
375 misleading_current = PathRef(
378 "tools/<tool>/third_party/<component>",
379 "The current dependency may use `tools/<tool>/third_party/<component>` now.",
381 if _path_reason(root,
"tools/README.md", misleading_current)
is None:
382 failures.append(
"tool-private namespace escaped through a current-dependency clause")
383 if _path_reason(root,
"README.md", tool_private)
is None:
384 failures.append(
"tool-private namespace escaped from a non-authority document")
387def _check_bare_declaration_cases(root: Path, failures: list[str]) ->
None:
388 """Prove exact absent-file declarations cannot linger or broaden."""
389 source =
"docs/HIL_SUITE.md"
390 (root / source).write_text(
"The package installs `dwf.h`.\n", encoding=
"ascii")
391 if _git(root,
"add", source).returncode != 0:
392 _fail(
"selftest bare-declaration git add failed")
393 parsed = {source: parse_document((root / source).read_text(encoding=
"ascii"))}
394 key = (source,
"dwf.h")
395 exact = {key:
"external package header"}
396 exact_contexts = {key: (_context_sha256(
"The package installs `dwf.h`."),)}
397 if _bare_declaration_findings(root, parsed, exact, exact_contexts):
398 failures.append(
"live exact bare-file declaration was rejected")
399 if not _bare_declaration_findings(
402 {(source,
"other.h"):
"wrong token"},
403 {(source,
"other.h"): exact_contexts[key]},
405 failures.append(
"wrong bare-file declaration token did not become stale")
406 if not _bare_declaration_findings(
409 {(
"docs/OTHER.md",
"dwf.h"):
"wrong"},
410 {(
"docs/OTHER.md",
"dwf.h"): exact_contexts[key]},
412 failures.append(
"wrong bare-file declaration source did not become stale")
413 if not _bare_declaration_findings(root, parsed, {key:
""}, exact_contexts):
414 failures.append(
"empty bare-file declaration reason did not become stale")
415 altered = {source: parse_document(
"Use `dwf.h` from this repository now.\n")}
416 if not _bare_declaration_findings(root, altered, exact, exact_contexts):
417 failures.append(
"changed semantic context left an absence declaration clean")
419 (root /
"external").mkdir()
420 (root /
"external" /
"dwf.h").write_text(
"/* now tracked */\n", encoding=
"ascii")
421 if _git(root,
"add",
"external/dwf.h").returncode != 0:
422 _fail(
"selftest newly-present bare file git add failed")
423 _tracked_basename_index.cache_clear()
424 if not _bare_declaration_findings(root, parsed, exact, exact_contexts):
425 failures.append(
"newly present bare filename left a stale declaration clean")
429 (
"[live](docs/live.md#live-heading)\n`tools/<name>/src`\n", 0,
"live link"),
430 (
"[dead](docs/dead.md)\n", 1,
"missing link"),
431 (
"[anchor](docs/live.md#dead-heading)\n", 1,
"missing anchor"),
432 (
"[defined][target]\n[target]: docs/live.md\n", 0,
"defined reference link"),
433 (
"[missing][target]\n", 1,
"missing reference definition"),
434 (
"[encoded](docs/live.md#live%2Dheading)\n", 0,
"percent-encoded anchor"),
435 (
"[site](/docs/live.md#live-heading)\n", 0,
"live repository-root link"),
436 (
"[site](/docs/dead.md)\n", 1,
"missing repository-root link"),
437 (
"`CMakeLists.txt`\n", 0,
"live repository-root authority"),
438 (
"See current.md for the live sibling.\n", 0,
"live lowercase sibling"),
439 (
"See missing.md for the absent sibling.\n", 1,
"missing lowercase sibling"),
440 (
"`missing.md`\n", 1,
"missing backticked lowercase sibling"),
441 (
"Use `live.sh`.\n", 0,
"bare script basename exists and may be ambiguous"),
442 (
"Use `absent.py`.\n", 1,
"missing bare Python basename"),
443 (
"Use `test_<name>.c`.\n", 0,
"bare placeholder basename has a match"),
444 (
"Use `missing_<name>.c`.\n", 1,
"bare placeholder basename has no match"),
446 "Ordinary prose mentions example.py without a code span.\n",
448 "dotted prose is ignored",
450 (
"See FOO.md for the missing policy.\n", 1,
"missing repository-root authority"),
451 (f
"`{_SCRIPT_ROOT}/live.sh`\n", 0,
"live code path"),
452 (f
"`{_SCRIPT_ROOT}/dead.sh`\n", 1,
"missing code path"),
454 f
"`{_SCRIPT_ROOT}/live.sh:999999`\n",
456 "rot-prone code-path line citation",
459 f
"`{_SCRIPT_ROOT}/live.sh:999999` {_CITES_MARKER}: captured tool output\n",
461 "reasoned line-citation transcript exception",
464 f
"`{_SCRIPT_ROOT}/live.sh:999999` {_CITES_MARKER}:\n",
466 "empty line-citation exception reason",
469 f
"[source]({_SCRIPT_ROOT}/live.sh#L999999)\n",
471 "beyond-EOF local source line link",
473 (f
"[source]({_SCRIPT_ROOT}/live.sh)\n", 0,
"local source link without line anchor"),
475 f
"[broken]({_SCRIPT_ROOT}/dead.sh\n",
477 "unbalanced inline-link opener preserves a missing path",
480 f
"[broken]({_SCRIPT_ROOT}/live.sh\n",
482 "unbalanced inline-link opener preserves a live path",
485 f
"[balanced]({_SCRIPT_ROOT}/live.sh) {_SCRIPT_ROOT}/dead.sh\n",
487 "balanced inline-link masking preserves adjacent prose",
490 f
"<code>{_SCRIPT_ROOT}/live.sh</code>\n",
492 "live path in HTML code markup",
495 f
"<code>{_SCRIPT_ROOT}/dead.sh</code>\n",
497 "missing path in HTML code markup",
500 f
"<code>{_SCRIPT_ROOT}/live.sh</code><code>{_SCRIPT_ROOT}/dead.sh</code>\n",
502 "adjacent HTML wrappers preserve the second missing path",
505 f
"<code>{_SCRIPT_ROOT}/live.sh</code><br><code>{_SCRIPT_ROOT}/dead.sh</code>\n",
507 "HTML break preserves the second missing path",
510 f
"<code>{_SCRIPT_ROOT}/live.sh</code><br><code>{_SCRIPT_ROOT}/live.sh</code>\n",
512 "adjacent live HTML-wrapped paths stay quiet",
515 f
"| executable | `{_SCRIPT_ROOT}/live.sh` |\n",
517 "live path in a Markdown table",
520 f
"| executable | `{_SCRIPT_ROOT}/dead.sh` |\n",
522 "missing path in a Markdown table",
525 f
'<a href="{_SCRIPT_ROOT}/live.sh">source</a>\n',
527 "live HTML link target",
530 f
'<a href="{_SCRIPT_ROOT}/dead.sh">source</a>\n',
532 "missing HTML link target",
534 (f
"`{_SCRIPT_ROOT}/*.sh`\n", 0,
"live glob has a current match"),
535 (f
"`{_SCRIPT_ROOT}/missing-*.sh`\n", 1,
"empty glob fails closed"),
537 "`tests/<category>/src/test_*.c`\n",
539 "mixed placeholder and glob has a current match",
542 "`tests/<category>/src/missing_*.c`\n",
544 "empty mixed placeholder and glob fails closed",
547 "`libs/board_<board>/{src/boot,ld}/`\n",
549 "placeholder plus multi-segment brace has a current match",
552 "`libs/board_alpha/{,src/boot}/`\n",
554 "brace glob with an empty alternative has a current match",
557 "`libs/missing_<board>/{src/boot,ld}/`\n",
559 "empty placeholder plus multi-segment brace fails closed",
561 (
"See docs/ABSENT.md.\n", 1,
"uppercase path is not a placeholder"),
562 (
"```sh\nscripts/dead.sh\n```\n", 1,
"missing fenced path"),
563 (
"`scripts/${NAME}.sh`\n", 0,
"dynamic path with live owner"),
564 (
"`tools/missing/${NAME}.py`\n", 1,
"dynamic path with missing nested owner"),
566 "`docs/qualification/release/<tag>/conformance.md`\n",
568 "planned path needs an authorized source",
570 (
"`docs/<tag>/ABSENT.md`\n", 1,
"placeholder without planned-root authority"),
571 (
"`libs/<component>/ABSENT.md`\n", 1,
"populated root needs a real match"),
572 (
"`tools/<tool>/ABSENT.c`\n", 1,
"tool template needs a real match"),
573 (
"docs/ABSENT.elf\n", 1,
"global ignored extension is not authority"),
574 (
"libs/ABSENT.pyc\n", 1,
"ignored bytecode extension is not authority"),
576 "tools/removed/build/output.elf\n",
578 "removed build owner does not inherit a broad ignore",
581 "tests/build-fuzz/crashes/<target>/crash-<sha1>\n",
583 "generated build namespace has a concrete owner",
585 (
"See docs/dead.md for details.\n", 1,
"missing bare prose path"),
586 (
"State: infra/live/private/state.yml\n", 0,
"owned ignored local state"),
587 (
"State: infra/removed/private/state.yml\n", 1,
"stale ignored owner"),
591def selftest() -> int:
592 """Prove every detector in both directions, including end-to-end scope."""
593 failures: list[str] = []
594 with isolated_git_environment(), tempfile.TemporaryDirectory()
as raw_tmp:
597 cases = _FIXTURE_CASES
598 inventory = _run_fixture_cases(root, cases, failures)
599 if not any(row[
"path"] ==
"docs/UPPER.MD" for row
in inventory):
600 failures.append(
"uppercase Markdown extension escaped the tracked inventory")
601 _check_component_cases(root, failures)
602 _check_tests_path_authority_cases(root)
603 _check_tests_path_live_cases(root, failures)
604 _check_tests_path_ambiguous_cases(root, failures)
605 _check_tests_path_hostile_cases(root, failures)
606 _check_soup_cases(root, failures)
607 _check_planned_path_cases(root, failures)
608 _check_vendor_scope_cases(root, failures)
609 _check_bare_declaration_cases(root, failures)
611 _check_parser_cases(failures)
612 _check_bare_parser_cases(failures)
615 for failure
in failures:
616 print(f
"selftest: check_markdown_references.py FAIL: {failure}", file=sys.stderr)
618 print(f
"selftest: check_markdown_references.py OK ({len(cases) + 45} both-direction cases)")