Skip to content

Commit 1fc21b2

Browse files
committed
test(core): add cuda.core.__all__ vs public docs consistency check
Closes #2326. Parses docs/source/api.rst (autosummary entries and data directives while cuda.core is the active module) and compares the flat public names against cuda.core.__all__ in both directions. Dotted entries such as graph.Graph or checkpoint.Process are submodule namespaces and are excluded. Symbols documented in api_private.rst are accepted as documented so returned-helper docs do not fail the check. The tests skip when cuda.core.__all__ is not defined, so this lands independently of #2300 and activates once #2300 merges. Also adds the __all__-names-resolve guard suggested in the #2300 review. Signed-off-by: Aryan <[email protected]>
1 parent 060578a commit 1fc21b2

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""Consistency check between ``cuda.core.__all__`` and the public API docs.
6+
7+
Compares the public symbols exported by ``cuda.core.__all__`` against the
8+
public entries in ``docs/source/api.rst``. Symbols documented in
9+
``docs/source/api_private.rst`` (returned helpers users cannot instantiate)
10+
are accepted as documented. Dotted entries such as ``graph.Graph`` or
11+
``checkpoint.Process`` describe submodule namespaces, not the flat
12+
``cuda.core`` namespace, and are excluded from the comparison.
13+
"""
14+
15+
import pathlib
16+
17+
import pytest
18+
19+
import cuda.core
20+
21+
DOCS_SOURCE_DIR = pathlib.Path(__file__).resolve().parent.parent / "docs" / "source"
22+
23+
24+
def _iter_directive_blocks(text):
25+
"""Yield (module, directive, argument, body_lines) for each RST directive.
26+
27+
``module`` is the module active at the directive, tracked from
28+
``.. module::`` and ``.. currentmodule::`` directives. ``body_lines`` is
29+
the indented block that follows the directive.
30+
"""
31+
module = None
32+
lines = text.splitlines()
33+
i = 0
34+
while i < len(lines):
35+
line = lines[i]
36+
i += 1
37+
if not line.startswith(".. "):
38+
continue
39+
head, _, argument = line[3:].partition("::")
40+
directive = head.strip()
41+
argument = argument.strip()
42+
if directive in ("module", "currentmodule"):
43+
module = argument
44+
continue
45+
body = []
46+
while i < len(lines):
47+
body_line = lines[i]
48+
if body_line.strip() and not body_line.startswith(" "):
49+
break
50+
body.append(body_line)
51+
i += 1
52+
yield module, directive, argument, body
53+
54+
55+
def _documented_names(rst_path, module="cuda.core"):
56+
"""Collect names documented for ``module`` in an RST file.
57+
58+
Returns the entries of ``autosummary`` blocks plus ``.. data::``
59+
arguments that appear while ``module`` is the active module.
60+
"""
61+
names = set()
62+
for active_module, directive, argument, body in _iter_directive_blocks(rst_path.read_text()):
63+
if active_module != module:
64+
continue
65+
if directive == "data":
66+
names.add(argument)
67+
elif directive == "autosummary":
68+
for line in body:
69+
entry = line.strip()
70+
if not entry or entry.startswith(":"):
71+
continue
72+
names.add(entry)
73+
return names
74+
75+
76+
def _flat_names(names):
77+
"""Filter out dotted (submodule-namespace) entries."""
78+
return {name for name in names if "." not in name}
79+
80+
81+
@pytest.fixture(scope="module")
82+
def exported():
83+
if not hasattr(cuda.core, "__all__"):
84+
pytest.skip("cuda.core does not define __all__")
85+
return set(cuda.core.__all__)
86+
87+
88+
@pytest.fixture(scope="module")
89+
def docs_dir():
90+
if not (DOCS_SOURCE_DIR / "api.rst").is_file():
91+
pytest.skip("docs sources not available (not running from a source checkout)")
92+
return DOCS_SOURCE_DIR
93+
94+
95+
def test_all_exports_resolve():
96+
if not hasattr(cuda.core, "__all__"):
97+
pytest.skip("cuda.core does not define __all__")
98+
missing = [name for name in cuda.core.__all__ if not hasattr(cuda.core, name)]
99+
assert missing == [], f"cuda.core.__all__ lists names that do not resolve: {missing}"
100+
101+
102+
def test_public_symbols_are_documented(exported, docs_dir):
103+
documented = _flat_names(_documented_names(docs_dir / "api.rst"))
104+
# Returned helpers are deliberately documented in api_private.rst; accept
105+
# them by their trailing name (e.g. _device_resources.DeviceResources).
106+
private_documented = {name.rsplit(".", 1)[-1] for name in _documented_names(docs_dir / "api_private.rst")}
107+
undocumented = exported - documented - private_documented
108+
assert not undocumented, (
109+
f"public by cuda.core.__all__ but missing from public docs (api.rst): {sorted(undocumented)}"
110+
)
111+
112+
113+
def test_documented_symbols_are_exported(exported, docs_dir):
114+
documented = _flat_names(_documented_names(docs_dir / "api.rst"))
115+
unexported = documented - exported
116+
assert not unexported, (
117+
f"documented as public in api.rst but not exported by cuda.core.__all__: {sorted(unexported)}"
118+
)

0 commit comments

Comments
 (0)