Repository navigation
Expand file tree
/
Copy pathtest_sphinxdocs.py
More file actions
139 lines (113 loc) · 4.66 KB
/
Copy pathtest_sphinxdocs.py
File metadata and controls
139 lines (113 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# test_sphinxdocs.py - pytest checks for user guide
# RMM, 23 Dec 2024
#
# This set of tests is used to make sure that all primary functions are
# referenced in the documentation.
import inspect
import os
import sys
import warnings
from importlib import resources
import pytest
import numpydoc.docscrape as npd
import control
import control.flatsys
# Location of the documentation and files to check
sphinx_dir = str(resources.files('control')) + '/../doc/generated/'
# Functions that should not be referenced
legacy_functions = [
'FRD', # FrequencyResponseData (or frd)
'balred', # balanced_reduction
'bode', # bode_plot
'c2d', # sample_system
'era', # eigensys_realization
'evalfr', # use __call__()
'find_eqpt', # find_operating_point
'gangof4', # gangof4_plot
'hsvd', # hankel_singular_values
'minreal', # minimal_realization
'modred', # model_reduction
'nichols', # nichols_plot
'nyquist', # nyquist_plot
'pzmap', # pole_zero_plot
'rlocus', # root_locus_plot
'rlocus', # root_locus_plot
'root_locus', # root_locus_plot
]
# Functons that we can skip
object_skiplist = [
control.NamedSignal, # np.ndarray members cause errors
control.common_timebase, # mainly internal use
control.cvxopt_check, # mainly internal use
control.pandas_check, # mainly internal use
control.slycot_check, # mainly internal use
]
# Global list of objects we have checked
checked = set()
# Decide on the level of verbosity (use -rP when running pytest)
verbose = 0
standalone = False
control_module_list = [
control, control.flatsys, control.optimal, control.phaseplot
]
@pytest.mark.parametrize("module", control_module_list)
def test_sphinx_functions(module):
# Look through every object in the package
_info(f"Checking module {module}", 1)
for name, obj in inspect.getmembers(module):
objname = ".".join([module.__name__, name])
# Skip anything that is outside of this module
if inspect.getmodule(obj) is not None and \
not inspect.getmodule(obj).__name__.startswith('control'):
# Skip anything that isn't part of the control package
continue
elif inspect.isclass(obj) and issubclass(obj, Exception):
continue
elif inspect.isclass(obj) or inspect.isfunction(obj):
# Skip anything that is inherited, hidden, deprecated, or checked
if inspect.isclass(module) and name not in module.__dict__ \
or name.startswith('_') or obj in checked:
continue
else:
checked.add(obj)
# Get the relevant information about this object
exists = os.path.exists(sphinx_dir + objname + ".rst")
deprecated = _check_deprecated(obj)
skip = obj in object_skiplist
referenced = f" {objname} referenced in sphinx docs"
legacy = name in legacy_functions
_info(f" Checking {objname}", 2)
match exists, skip, deprecated, legacy:
case True, True, _, _:
_info(f"skipped object" + referenced, -1)
case True, _, True, _:
_warn(f"deprecated object" + referenced)
case True, _, _, True:
_warn(f"legacy object" + referenced)
case False, False, False, False:
_fail(f"{objname} not referenced in sphinx docs")
def _check_deprecated(obj):
with warnings.catch_warnings():
warnings.simplefilter('ignore') # debug via sphinx, not here
doc = npd.FunctionDoc(obj)
doc_extended = "" if doc is None else "\n".join(doc["Extended Summary"])
return ".. deprecated::" in doc_extended
# Utility function to warn with verbose output
def _info(str, level):
if verbose > level:
print(("INFO: " if level < 0 else " " * level) + str)
def _warn(str, level=-1):
if verbose > level:
print("WARN: " + " " * level + str)
if not standalone:
warnings.warn(str, stacklevel=2)
def _fail(str, level=-1):
if verbose > level:
print("FAIL: " + " " * level + str)
if not standalone:
pytest.fail(str)
if __name__ == "__main__":
verbose = 0 if len(sys.argv) == 1 else int(sys.argv[1])
standalone = True
for module in control_module_list:
test_sphinx_functions(module)