From 909f94ec07a8f1743ceb4287baf9cc408687fc30 Mon Sep 17 00:00:00 2001 From: Aljes Date: Thu, 24 Sep 2026 12:46:06 +0100 Subject: [PATCH] Fix two links that 404 on the published benchmarks page GitHub Pages serves `docs/` as the site root, so a relative link out of it cannot resolve. Two went live with 0.6.0: ../.github/workflows/benchmark.yml -> 404 (outside the site root) benchmarks/ -> 404 (directory, no index page) Both were mine, in the page template rather than in the generated results, so they would have reappeared on every tag. They are now absolute URLs to GitHub. Everything else on the site already resolved -- Jekyll's relative-links plugin rewrites `TESTING.md` to `/TESTING.html` on its own, and `docs/benchmarks/0.6.0.json` is served as a file. The page is regenerated from the corrected template; the 0.6.0 measurements in it are untouched. The new test checks every relative link in `docs/*.md` against the filesystem: it must stay inside `docs/`, exist, and not be a directory without an index. Offline, so it costs nothing and needs no network, and it fails on the two links above against the previous template. Co-Authored-By: Claude Opus 5 --- benchmarks/page_template.md | 2 +- benchmarks/report.py | 6 +++- docs/BENCHMARKS.md | 4 +-- tests/test_docs_are_accurate.py | 54 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/benchmarks/page_template.md b/benchmarks/page_template.md index 4964e0e..edb7ca8 100644 --- a/benchmarks/page_template.md +++ b/benchmarks/page_template.md @@ -1,7 +1,7 @@ # Benchmarks adata-cli measured against anndata, and against scanpy wherever scanpy has a real -equivalent. The [`Benchmark`](../.github/workflows/benchmark.yml) workflow rewrites +equivalent. The [`Benchmark`](https://github.com/cellgeni/adata-cli/blob/main/.github/workflows/benchmark.yml) workflow rewrites this page on every tag and keeps each run's raw numbers in `docs/benchmarks/`. **Peak RSS is the headline, not wall time.** This tool exists so that memory is set diff --git a/benchmarks/report.py b/benchmarks/report.py index 9596dae..c49652f 100644 --- a/benchmarks/report.py +++ b/benchmarks/report.py @@ -20,6 +20,10 @@ from benchmarks.cases import by_name +#: Links out of `docs/` cannot be relative: GitHub Pages serves that +#: directory as the site root, so `../` and bare directory paths 404. +REPO = "https://github.com/cellgeni/adata-cli" + def _bytes(n: int) -> str: if not n: @@ -186,7 +190,7 @@ def history_table(docs: Path) -> str: "### History", "", "`concat-inner` on adata-cli, run by run. Full results for each are " - "in [`docs/benchmarks/`](benchmarks/).", + f"in [`docs/benchmarks/`]({REPO}/tree/main/docs/benchmarks).", "", "| Run | Ref | Tier | Wall time | Peak RSS | Raw |", "|---|---|---|---|---|---|", diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md index c327df0..d4d7086 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -1,7 +1,7 @@ # Benchmarks adata-cli measured against anndata, and against scanpy wherever scanpy has a real -equivalent. The [`Benchmark`](../.github/workflows/benchmark.yml) workflow rewrites +equivalent. The [`Benchmark`](https://github.com/cellgeni/adata-cli/blob/main/.github/workflows/benchmark.yml) workflow rewrites this page on every tag and keeps each run's raw numbers in `docs/benchmarks/`. **Peak RSS is the headline, not wall time.** This tool exists so that memory is set @@ -218,7 +218,7 @@ Rows marked `n/a` are operations the baseline does not offer; that is a result, ### History -`concat-inner` on adata-cli, run by run. Full results for each are in [`docs/benchmarks/`](benchmarks/). +`concat-inner` on adata-cli, run by run. Full results for each are in [`docs/benchmarks/`](https://github.com/cellgeni/adata-cli/tree/main/docs/benchmarks). | Run | Ref | Tier | Wall time | Peak RSS | Raw | |---|---|---|---|---|---| diff --git a/tests/test_docs_are_accurate.py b/tests/test_docs_are_accurate.py index f6f4608..a3455e3 100644 --- a/tests/test_docs_are_accurate.py +++ b/tests/test_docs_are_accurate.py @@ -211,3 +211,57 @@ def test_options_claimed_in_prose_exist(option, command, source): f"{source}: prose says {command} supports {option}, " f"but the command does not accept it" ) + + +# --------------------------------------------------------------------------- +# links that have to work on the published site +# +# GitHub Pages serves `docs/` as the site root, so a relative link out of it +# cannot resolve: `../.github/workflows/benchmark.yml` and a bare +# `benchmarks/` both went live as 404s on the 0.6.0 release page. Checked +# offline against the filesystem, so it costs nothing and needs no network. + + +def _relative_links(text: str): + """(link, target) for every relative markdown link, fragments stripped.""" + for match in re.finditer(r"\]\(([^)\s]+)\)", text): + link = match.group(1) + if link.startswith(("http://", "https://", "mailto:", "#")): + continue + target = link.split("#", 1)[0] + if target: + yield link, target + + +DOC_PAGES = sorted((REPO / "docs").glob("*.md")) + + +@pytest.mark.parametrize("page", DOC_PAGES, ids=lambda p: p.name) +def test_every_relative_link_resolves_inside_the_published_site(page): + """A relative link must point at something Pages actually serves. + + Jekyll rewrites `TESTING.md` to `/TESTING.html`, so a `.md` target is + fine; anything reached with `../`, or a bare directory with no index, + is not, and has to be an absolute URL to GitHub instead. + """ + broken = [] + for link, target in _relative_links(page.read_text()): + resolved = (page.parent / target).resolve() + try: + inside = resolved.is_relative_to((REPO / "docs").resolve()) + except AttributeError: # pragma: no cover - Python < 3.9 + inside = str(resolved).startswith(str((REPO / "docs").resolve())) + + if not inside: + broken.append(f"{link} -- leaves docs/, so Pages cannot serve it") + elif not resolved.exists(): + broken.append(f"{link} -- no such file") + elif resolved.is_dir() and not (resolved / "index.md").exists(): + broken.append(f"{link} -- a directory with no index page") + + assert not broken, ( + f"{page.name} has links that 404 on the published site:\n " + + "\n ".join(broken) + + "\nUse an absolute https://github.com/... URL for anything outside " + "docs/." + )