|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +"""Offline smoke check for the configured native Lychee hook. |
| 5 | +
|
| 6 | +Exercise the hook's environment, Python launcher, and native executable against |
| 7 | +the README's local links without making external link requests. As an accepted |
| 8 | +tradeoff for using a real document, a broken local README link also fails this |
| 9 | +check even when the hook itself works. |
| 10 | +""" |
| 11 | + |
| 12 | +from __future__ import annotations |
| 13 | + |
| 14 | +import argparse |
| 15 | +import shutil |
| 16 | +from pathlib import Path |
| 17 | + |
| 18 | +import yaml |
| 19 | +from retry_lychee import read_report |
| 20 | + |
| 21 | +ROOT = Path(__file__).resolve().parents[2] |
| 22 | +REPORT_NAME = "lychee.json" |
| 23 | + |
| 24 | + |
| 25 | +def write_config(directory: Path) -> None: |
| 26 | + # Use the checked-in hook, including its entry point and pinned dependency. |
| 27 | + # PyYAML is already installed as a dependency of pre-commit. |
| 28 | + config = yaml.safe_load((ROOT / ".pre-commit-config.yaml").read_text(encoding="utf-8")) |
| 29 | + [hook] = [hook for repo in config["repos"] for hook in repo["hooks"] if hook["id"] == "lychee"] |
| 30 | + report = directory / REPORT_NAME |
| 31 | + hook.setdefault("args", []).extend(["--offline", "--format", "json", "--output", str(report)]) |
| 32 | + config_path = directory / "pre-commit.yaml" |
| 33 | + config_path.write_text(yaml.safe_dump(config), encoding="utf-8") |
| 34 | + |
| 35 | + |
| 36 | +def verify(log: Path, exit_code: int) -> int: |
| 37 | + report = log.with_name(REPORT_NAME) |
| 38 | + try: |
| 39 | + print(log.read_text(encoding="utf-8"), end="") |
| 40 | + if exit_code != 0: |
| 41 | + return exit_code |
| 42 | + if not report.is_file(): |
| 43 | + raise SystemExit(f"The configured Lychee hook produced no report ({report}); was it skipped?") |
| 44 | + read_report(report, exit_code) |
| 45 | + return 0 |
| 46 | + finally: |
| 47 | + # Consume each shell's report so a later skipped hook cannot reuse it. |
| 48 | + report.unlink(missing_ok=True) |
| 49 | + |
| 50 | + |
| 51 | +def main() -> int: |
| 52 | + # CI uses separate phases so each shell invokes pre-commit directly. |
| 53 | + parser = argparse.ArgumentParser(description=__doc__) |
| 54 | + mode = parser.add_mutually_exclusive_group(required=True) |
| 55 | + mode.add_argument("--prepare", type=Path, metavar="DIRECTORY") |
| 56 | + mode.add_argument("--verify", nargs=2, metavar=("LOG", "EXIT_CODE")) |
| 57 | + mode.add_argument("--cleanup", type=Path, metavar="DIRECTORY") |
| 58 | + args = parser.parse_args() |
| 59 | + if args.prepare is not None: |
| 60 | + write_config(ROOT / args.prepare) |
| 61 | + return 0 |
| 62 | + if args.verify is not None: |
| 63 | + log, exit_code = args.verify |
| 64 | + return verify(ROOT / log, int(exit_code)) |
| 65 | + directory = (ROOT / args.cleanup).resolve() |
| 66 | + if directory.parent != ROOT or not directory.name.startswith("lychee offline smoke "): |
| 67 | + raise SystemExit("Refusing to remove a directory outside the smoke check's temporary files") |
| 68 | + shutil.rmtree(directory) |
| 69 | + return 0 |
| 70 | + |
| 71 | + |
| 72 | +if __name__ == "__main__": |
| 73 | + raise SystemExit(main()) |
0 commit comments