ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
markdown_reference_selftest.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Both-direction selftests for the Markdown reference validator."""
4
5from __future__ import annotations
6
7import sys
8import tempfile
9import time
10from collections.abc import Callable
11from pathlib import Path
12
13sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "dev"))
14
15import markdown_references as core
16from git_environment import isolated_git_environment
17from markdown_reference_policy import (
18 BARE_FILE_SUFFIXES,
19 LIBWEBP_ABSENCE_CLAUSE,
20 PARSER_RUNTIME_LIMIT_SECONDS,
21)
22
23AnchorRef = core.AnchorRef
24LinkRef = core.LinkRef
25PathRef = core.PathRef
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
31_fail = core.fail
32_git = core.git
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
38
39_SCRIPT_ROOT = "scripts"
40_CITES_MARKER = "CITES" + "-OK"
41
42
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"
59 )
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"
67 )
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",
73 encoding="ascii",
74 )
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"
80 )
81 (root / "docs" / "qualification" / "release" / "README.md").write_text(
82 "# Planned qualification releases\n", encoding="ascii"
83 )
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"
87 )
88
89
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"
99 )
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}")
106 return inventory
107
108
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}")
121
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)
124 if findings:
125 failures.append(f"nested upstream Markdown was unexpectedly parsed: {findings}")
126
127
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"
135 )
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"
139 )
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}")
146
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"
151 )
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")
155
156
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"
166 )
167 (root / "tests" / "shared" / "src").mkdir(parents=True)
168 (root / "tests" / "shared" / "src" / "test_shared.c").write_text(
169 "/* root */\n", encoding="ascii"
170 )
171 (root / "tests" / "root-owner").mkdir()
172
173
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"
177
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])
184
185 return reason
186
187
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)
191 quiet = (
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",
197 )
198 failures.extend(
199 f"live root/component tests path was rejected: {token}"
200 for token in quiet
201 if reason(token) is not None
202 )
203
204 if reason("tests/local/src/absent.c") is None:
205 failures.append("absent component-local tests path escaped")
206
207
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"
215 )
216 (component / "tests" / "root-owner").mkdir()
217 ambiguous = (
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",
222 )
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}")
227
228
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")
233 hostile = (
234 "tests/local/../../../outside.md",
235 "tests/${NAME}/../../outside.md",
236 "tests/${lower}/src/test.c",
237 )
238 failures.extend(
239 f"hostile tests path escaped: {token}" for token in hostile if reason(token) is None
240 )
241
242
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")
246 if (
247 "same-1" not in parsed.anchors
248 or parsed.anchor_collisions
249 or parsed.links != (LinkRef(3, "#same-1"),)
250 ):
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")
256 if footnote.links:
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")
272 )
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")
285
286
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)")
300
301
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",
308 encoding="ascii",
309 )
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(
314 3,
315 0,
316 "src/ABSENT.c",
317 "`src/ABSENT.c` is required; `src/enc/*.c` are **not** vendored",
318 )
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(
328 3,
329 0,
330 "src/enc/*.c",
331 "`src/enc/*.c` is required; documentation files are **not** vendored",
332 )
333 if _path_reason(root, source, malicious_whitelisted) is None:
334 failures.append("a whitelisted SOUP token escaped through an unrelated negative clause")
335
336
337def _check_planned_path_cases(root: Path, failures: list[str]) -> None:
338 """Prove only named future namespaces can rely on policy authority."""
339 release = PathRef(
340 1,
341 0,
342 "docs/qualification/release/<tag>/conformance.md",
343 "will be added under `docs/qualification/release/<tag>/conformance.md`",
344 )
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")
349
350 tool_private = PathRef(
351 1,
352 0,
353 "tools/<tool>/third_party/<component>",
354 "`tools/<tool>/third_party/<component>` subtree is reserved for a dependency "
355 "used exclusively by that tool.",
356 )
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(
360 1,
361 0,
362 "tools/<tool>/third_party/<component>",
363 "Use `tools/<tool>/third_party/<component>` now.",
364 )
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(
368 1,
369 0,
370 "tools/<tool>/third_party/<component>",
371 "Future work is separate; use `tools/<tool>/third_party/<component>` now.",
372 )
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(
376 1,
377 0,
378 "tools/<tool>/third_party/<component>",
379 "The current dependency may use `tools/<tool>/third_party/<component>` now.",
380 )
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")
385
386
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(
400 root,
401 parsed,
402 {(source, "other.h"): "wrong token"},
403 {(source, "other.h"): exact_contexts[key]},
404 ):
405 failures.append("wrong bare-file declaration token did not become stale")
406 if not _bare_declaration_findings(
407 root,
408 parsed,
409 {("docs/OTHER.md", "dwf.h"): "wrong"},
410 {("docs/OTHER.md", "dwf.h"): exact_contexts[key]},
411 ):
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")
418
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")
426
427
428_FIXTURE_CASES = (
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"),
445 (
446 "Ordinary prose mentions example.py without a code span.\n",
447 0,
448 "dotted prose is ignored",
449 ),
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"),
453 (
454 f"`{_SCRIPT_ROOT}/live.sh:999999`\n",
455 1,
456 "rot-prone code-path line citation",
457 ),
458 (
459 f"`{_SCRIPT_ROOT}/live.sh:999999` {_CITES_MARKER}: captured tool output\n",
460 0,
461 "reasoned line-citation transcript exception",
462 ),
463 (
464 f"`{_SCRIPT_ROOT}/live.sh:999999` {_CITES_MARKER}:\n",
465 1,
466 "empty line-citation exception reason",
467 ),
468 (
469 f"[source]({_SCRIPT_ROOT}/live.sh#L999999)\n",
470 1,
471 "beyond-EOF local source line link",
472 ),
473 (f"[source]({_SCRIPT_ROOT}/live.sh)\n", 0, "local source link without line anchor"),
474 (
475 f"[broken]({_SCRIPT_ROOT}/dead.sh\n",
476 1,
477 "unbalanced inline-link opener preserves a missing path",
478 ),
479 (
480 f"[broken]({_SCRIPT_ROOT}/live.sh\n",
481 0,
482 "unbalanced inline-link opener preserves a live path",
483 ),
484 (
485 f"[balanced]({_SCRIPT_ROOT}/live.sh) {_SCRIPT_ROOT}/dead.sh\n",
486 1,
487 "balanced inline-link masking preserves adjacent prose",
488 ),
489 (
490 f"<code>{_SCRIPT_ROOT}/live.sh</code>\n",
491 0,
492 "live path in HTML code markup",
493 ),
494 (
495 f"<code>{_SCRIPT_ROOT}/dead.sh</code>\n",
496 1,
497 "missing path in HTML code markup",
498 ),
499 (
500 f"<code>{_SCRIPT_ROOT}/live.sh</code><code>{_SCRIPT_ROOT}/dead.sh</code>\n",
501 1,
502 "adjacent HTML wrappers preserve the second missing path",
503 ),
504 (
505 f"<code>{_SCRIPT_ROOT}/live.sh</code><br><code>{_SCRIPT_ROOT}/dead.sh</code>\n",
506 1,
507 "HTML break preserves the second missing path",
508 ),
509 (
510 f"<code>{_SCRIPT_ROOT}/live.sh</code><br><code>{_SCRIPT_ROOT}/live.sh</code>\n",
511 0,
512 "adjacent live HTML-wrapped paths stay quiet",
513 ),
514 (
515 f"| executable | `{_SCRIPT_ROOT}/live.sh` |\n",
516 0,
517 "live path in a Markdown table",
518 ),
519 (
520 f"| executable | `{_SCRIPT_ROOT}/dead.sh` |\n",
521 1,
522 "missing path in a Markdown table",
523 ),
524 (
525 f'<a href="{_SCRIPT_ROOT}/live.sh">source</a>\n',
526 0,
527 "live HTML link target",
528 ),
529 (
530 f'<a href="{_SCRIPT_ROOT}/dead.sh">source</a>\n',
531 1,
532 "missing HTML link target",
533 ),
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"),
536 (
537 "`tests/<category>/src/test_*.c`\n",
538 0,
539 "mixed placeholder and glob has a current match",
540 ),
541 (
542 "`tests/<category>/src/missing_*.c`\n",
543 1,
544 "empty mixed placeholder and glob fails closed",
545 ),
546 (
547 "`libs/board_<board>/{src/boot,ld}/`\n",
548 0,
549 "placeholder plus multi-segment brace has a current match",
550 ),
551 (
552 "`libs/board_alpha/{,src/boot}/`\n",
553 0,
554 "brace glob with an empty alternative has a current match",
555 ),
556 (
557 "`libs/missing_<board>/{src/boot,ld}/`\n",
558 1,
559 "empty placeholder plus multi-segment brace fails closed",
560 ),
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"),
565 (
566 "`docs/qualification/release/<tag>/conformance.md`\n",
567 1,
568 "planned path needs an authorized source",
569 ),
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"),
575 (
576 "tools/removed/build/output.elf\n",
577 1,
578 "removed build owner does not inherit a broad ignore",
579 ),
580 (
581 "tests/build-fuzz/crashes/<target>/crash-<sha1>\n",
582 0,
583 "generated build namespace has a concrete owner",
584 ),
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"),
588)
589
590
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:
595 root = Path(raw_tmp)
596 _init_fixture(root)
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)
610
611 _check_parser_cases(failures)
612 _check_bare_parser_cases(failures)
613
614 if failures:
615 for failure in failures:
616 print(f"selftest: check_markdown_references.py FAIL: {failure}", file=sys.stderr)
617 return 1
618 print(f"selftest: check_markdown_references.py OK ({len(cases) + 45} both-direction cases)")
619 return 0