|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +"""Exercise the configured Lychee pre-commit hook without scanning external links.""" |
| 5 | + |
| 6 | +from __future__ import annotations |
| 7 | + |
| 8 | +import os |
| 9 | +import subprocess |
| 10 | +import sys |
| 11 | +from pathlib import Path |
| 12 | +from tempfile import TemporaryDirectory |
| 13 | +from urllib.parse import quote |
| 14 | + |
| 15 | +ROOT = Path(__file__).resolve().parents[2] |
| 16 | + |
| 17 | + |
| 18 | +def run_hook(sources: dict[Path, bytes], missing: Path | None = None) -> None: |
| 19 | + result = subprocess.run( # noqa: S603 |
| 20 | + [ |
| 21 | + sys.executable, |
| 22 | + "-m", |
| 23 | + "pre_commit", |
| 24 | + "run", |
| 25 | + "lychee", |
| 26 | + "--verbose", |
| 27 | + "--color", |
| 28 | + "never", |
| 29 | + "--files", |
| 30 | + *(source.relative_to(ROOT).as_posix() for source in sources), |
| 31 | + ], |
| 32 | + cwd=ROOT, |
| 33 | + env=dict(os.environ, SKIP=""), |
| 34 | + capture_output=True, |
| 35 | + encoding="utf-8", |
| 36 | + timeout=120, |
| 37 | + ) |
| 38 | + output = result.stdout + result.stderr |
| 39 | + print(output, end="", flush=True) |
| 40 | + for source, original in sources.items(): |
| 41 | + if source.read_bytes() != original: |
| 42 | + raise RuntimeError(f"The Lychee hook changed {source}") |
| 43 | + if "- hook id: lychee" not in output or "Skipped" in output: |
| 44 | + raise RuntimeError("The configured Lychee hook did not run") |
| 45 | + if missing is None: |
| 46 | + if result.returncode != 0: |
| 47 | + raise RuntimeError("The Lychee hook rejected existing checkout targets") |
| 48 | + elif ( |
| 49 | + result.returncode == 0 |
| 50 | + or "File not found" not in output |
| 51 | + or missing.as_uri() not in output |
| 52 | + or "- exit code: 2" not in output |
| 53 | + ): |
| 54 | + raise RuntimeError(f"Expected Lychee to report the missing local target {missing}") |
| 55 | + |
| 56 | + |
| 57 | +def main() -> None: |
| 58 | + # Generate sources inside the checkout so pre-commit applies the real file filters. |
| 59 | + with TemporaryDirectory(prefix="lychee hook smoke ", dir=ROOT) as temporary: |
| 60 | + fixture = Path(temporary) |
| 61 | + directory = fixture / "new directory" |
| 62 | + directory.mkdir() |
| 63 | + target = fixture / "new source #$.py" |
| 64 | + target.write_text("value = 1\n", encoding="utf-8") |
| 65 | + relative = fixture / "relative guide.rst" |
| 66 | + relative.write_text("Relative target\n", encoding="utf-8") |
| 67 | + prefix = "https://github.com/NVIDIA/cuda-python" |
| 68 | + file_url = f"{prefix}/blob/main/{quote(target.relative_to(ROOT).as_posix())}" |
| 69 | + directory_url = f"{prefix}/tree/main/{quote(directory.relative_to(ROOT).as_posix())}/" |
| 70 | + sources = { |
| 71 | + fixture / "source guide.md": ( |
| 72 | + f"[File]({file_url})\r\n[Directory]({directory_url})\r\n[Relative](relative%20guide.rst)\r\n" |
| 73 | + ).encode(), |
| 74 | + fixture / "source guide.rst": ( |
| 75 | + f"`File <{file_url}>`_\n`Directory <{directory_url}>`_\n`Relative <relative%20guide.rst>`_\n" |
| 76 | + ).encode(), |
| 77 | + } |
| 78 | + for source, content in sources.items(): |
| 79 | + source.write_bytes(content) |
| 80 | + |
| 81 | + print("Checking existing local file, directory, and relative targets", flush=True) |
| 82 | + run_hook(sources) |
| 83 | + target.unlink() |
| 84 | + print("Checking that a previously valid file is rejected after deletion", flush=True) |
| 85 | + run_hook(sources, missing=target) |
| 86 | + target.write_text("value = 1\n", encoding="utf-8") |
| 87 | + directory.rmdir() |
| 88 | + print("Checking that a previously valid directory is rejected after deletion", flush=True) |
| 89 | + run_hook(sources, missing=directory) |
| 90 | + |
| 91 | + print("Configured Lychee hook checks passed; documentation sources were unchanged.") |
| 92 | + |
| 93 | + |
| 94 | +if __name__ == "__main__": |
| 95 | + main() |
0 commit comments