Skip to content

Commit 03ab28d

Browse files
committed
Add test to ensure documentation site coverage
1 parent 2ebd1e5 commit 03ab28d

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

python/tests/test_docs_coverage.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
"""Drift guards between the public API surface and the mkdocs reference site.
19+
20+
Two checks:
21+
22+
1. Every symbol in ``datafusion.__all__`` is covered by an ``mkdocstrings``
23+
``:::`` directive somewhere under ``docs/source/reference/``. Coverage may
24+
be direct (``::: datafusion.dataframe.DataFrame``) or by whole-module
25+
autodoc (``::: datafusion.functions``).
26+
2. Every ``:::`` directive in the reference pages resolves to a real,
27+
importable Python object. Renames or removals that orphan a stub fail the
28+
suite instead of silently producing an empty doc page.
29+
"""
30+
31+
from __future__ import annotations
32+
33+
import importlib
34+
import inspect
35+
import re
36+
from pathlib import Path
37+
38+
import datafusion
39+
40+
REF_DIR = Path(__file__).resolve().parents[2] / "docs" / "source" / "reference"
41+
_DIRECTIVE_RE = re.compile(r"^:::\s+(\S+)\s*$", re.MULTILINE)
42+
43+
44+
def _all_directives() -> set[str]:
45+
paths: set[str] = set()
46+
for md in REF_DIR.rglob("*.md"):
47+
paths.update(_DIRECTIVE_RE.findall(md.read_text()))
48+
return paths
49+
50+
51+
def _is_covered(qual: str, directives: set[str]) -> bool:
52+
if qual in directives:
53+
return True
54+
parent = qual.rsplit(".", 1)[0] if "." in qual else None
55+
return parent in directives if parent else False
56+
57+
58+
def test_public_api_documented() -> None:
59+
directives = _all_directives()
60+
assert directives, f"no ::: directives found under {REF_DIR}"
61+
62+
missing: list[str] = []
63+
for name in datafusion.__all__:
64+
obj = getattr(datafusion, name)
65+
if inspect.ismodule(obj):
66+
mod_name = obj.__name__
67+
if mod_name in directives or any(
68+
d.startswith(mod_name + ".") for d in directives
69+
):
70+
continue
71+
missing.append(f"{name} (module {mod_name})")
72+
continue
73+
74+
module = getattr(obj, "__module__", None) or "datafusion"
75+
qual = f"{module}.{name}"
76+
if _is_covered(qual, directives) or module in directives:
77+
continue
78+
missing.append(f"{name} (expected '::: {qual}' or '::: {module}')")
79+
80+
assert not missing, (
81+
"Public API symbols missing from docs/source/reference/*.md:\n "
82+
+ "\n ".join(missing)
83+
)
84+
85+
86+
def test_directive_targets_resolve() -> None:
87+
bad: list[str] = []
88+
for path in sorted(_all_directives()):
89+
parts = path.split(".")
90+
obj = None
91+
remainder: list[str] = []
92+
for i in range(len(parts), 0, -1):
93+
try:
94+
obj = importlib.import_module(".".join(parts[:i]))
95+
remainder = parts[i:]
96+
break
97+
except ImportError:
98+
continue
99+
if obj is None:
100+
bad.append(f"{path} (no importable prefix)")
101+
continue
102+
try:
103+
for attr in remainder:
104+
obj = getattr(obj, attr)
105+
except AttributeError:
106+
bad.append(f"{path} (attribute chain broken)")
107+
108+
assert not bad, (
109+
"Doc ::: directives reference symbols that no longer exist:\n "
110+
+ "\n ".join(bad)
111+
)

0 commit comments

Comments
 (0)