Source code for tests.test_sphinx_crossrefs

# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Render tests for Sphinx cross-references in the built documentation.

Build the docs once and assert against the real HTML that the generated summary
tables deep-link to the sections they describe, that each changelog release is
anchored on its version, and that intersphinx references to Click resolve to
the upstream site. This catches drift the moment a `{click:config}` or
`{click:tree}` directive stops wiring its anchors, or an `intersphinx_mapping`
URL goes stale, neither of which a mock-based test would notice.
"""

from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path

import pytest

from meta_package_manager import _docs

PROJECT_ROOT = Path(__file__).parent.parent

# The `docs` group declares `requires-python = ">= 3.14"` (see [tool.uv]), so it
# resolves nowhere else: below that, `uv run --group docs` installs nothing and
# `sphinx-build` is not on PATH. Building once on the newest supported Python
# also keeps this to a single full docs build across the CI matrix.
pytestmark = [
    pytest.mark.skipif(
        not sys.platform.startswith("linux"),
        reason="docs are built on Linux in CI",
    ),
    pytest.mark.skipif(
        sys.version_info < (3, 14),
        reason="build once on the newest supported Python",
    ),
    pytest.mark.skipif(
        shutil.which("uv") is None,
        reason="needs uv to build the docs",
    ),
    # The build installs the docs group as `uv.lock` pins it. The source
    # distribution ships no lockfile, so a packager running the suite from it
    # has nothing to build from.
    pytest.mark.skipif(
        not PROJECT_ROOT.joinpath("uv.lock").is_file(),
        reason="needs the lockfile of a repository checkout",
    ),
    # Sphinx crashes with a FileNotFoundError on searchindex.js.tmp when
    # concurrent builds share the same output directory (sphinx-doc/sphinx#13702).
    # Force all tests in this module onto a single xdist worker.
    pytest.mark.xdist_group("sphinx"),
]


[docs] @pytest.fixture(scope="module") def built_docs(tmp_path_factory: pytest.TempPathFactory) -> Path: """Build the HTML documentation once and return its output directory. Builds into a throwaway directory (rather than `docs/_build`) so the run is hermetic and never clobbers a developer's local build. """ out_dir = tmp_path_factory.mktemp("sphinx-html") subprocess.run( [ "uv", "--no-progress", "run", "--frozen", "--group", "docs", "sphinx-build", "--builder", "html", str(PROJECT_ROOT / "docs"), str(out_dir), ], check=True, cwd=PROJECT_ROOT, ) return out_dir
[docs] def read_html(built_docs: Path, filename: str) -> str: """Read a built HTML page.""" html_path = built_docs / filename assert html_path.exists(), f"HTML file not found: {html_path}" return html_path.read_text(encoding="utf-8")
[docs] def test_changelog_releases_are_anchored_on_their_version(built_docs): """Each release of the changelog has an anchor made from its version. docutils reduces a release heading to nothing and numbers its section from a per-page counter, so `#id94` names another release after each new one. `ReleaseAnchors` of `docs/conf.py` gives that section `#v3-6-0` instead, and the release in preparation the anchor it ships with. """ html = read_html(built_docs, "changelog.html") missing = [ version for version in _docs.changelog_releases() if f'<section id="v{version.replace(".", "-")}">' not in html ] assert not missing, f"changelog releases with no version anchor: {missing}"
[docs] def test_intersphinx_click_resolves(built_docs): """Click cross-references resolve to the upstream documentation site. The links land on the API autodoc pages (typed CLI signatures), not the CLI-parameters page, so scan the whole build rather than a single page. """ hits = [ page.name for page in sorted(built_docs.glob("*.html")) if "https://click.palletsprojects.com" in page.read_text(encoding="utf-8") ] assert hits, ( "no intersphinx link to Click found in the built docs; " "the mapping may be broken" )