Skip to content

Commit 4838bbf

Browse files
committed
add unit test for testing documentation
1 parent f078b3e commit 4838bbf

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

control/tests/docstrings_test.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# docstrings_test.py - test for undocumented arguments
2+
# RMM, 28 Jul 2024
3+
#
4+
# This unit test looks through all functions in the package and attempts to
5+
# identify arguments that are not documented. It will check anything that
6+
# is an explicitly listed argument, as well as attempt to find keyword
7+
# arguments that are extracted using kwargs.pop(), config._get_param(), or
8+
# config.use_legacy_defaults.
9+
10+
import inspect
11+
import warnings
12+
13+
import pytest
14+
import re
15+
16+
import control
17+
import control.flatsys
18+
19+
# List of functions that we can skip testing (special cases)
20+
skiplist = [
21+
control.ControlPlot.reshape, # needed for legacy interface
22+
]
23+
24+
@pytest.mark.parametrize("module, prefix", [
25+
(control, ""), (control.flatsys, "flatsys."),
26+
(control.optimal, "optimal."), (control.phaseplot, "phaseplot.")
27+
])
28+
def test_docstrings(module, prefix):
29+
# Look through every object in the package
30+
print(f"Checking module {module}")
31+
for name, obj in inspect.getmembers(module):
32+
# Skip anything that is outside of this module
33+
if inspect.getmodule(obj) is not None and \
34+
not inspect.getmodule(obj).__name__.startswith('control'):
35+
# Skip anything that isn't part of the control package
36+
continue
37+
38+
if inspect.isclass(obj):
39+
print(f" Checking class {name}")
40+
# Check member functions within the class
41+
test_docstrings(obj, prefix + obj.__name__ + '.')
42+
43+
if inspect.isfunction(obj):
44+
# Skip anything that is inherited or hidden
45+
if inspect.isclass(module) and obj.__name__ not in module.__dict__ \
46+
or obj.__name__.startswith('_') or obj in skiplist:
47+
continue
48+
49+
# Make sure there is a docstring
50+
print(f" Checking function {name}")
51+
if obj.__doc__ is None:
52+
warnings.warn(
53+
f"{module.__name__}.{obj.__name__} is missing docstring")
54+
continue
55+
56+
# Get the signature for the function
57+
sig = inspect.signature(obj)
58+
59+
# Go through each parameter and make sure it is in the docstring
60+
for argname, par in sig.parameters.items():
61+
if argname == 'self':
62+
continue
63+
64+
if par.kind == inspect.Parameter.VAR_KEYWORD:
65+
# Found a keyword argument; look at code for parsing
66+
warnings.warn("keyword argument checks not yet implemented")
67+
68+
# Make sure this argument is documented properly in docstring
69+
else:
70+
assert _check_docstring(obj.__name__, argname, obj.__doc__)
71+
72+
73+
# Utility function to check for an argument in a docstring
74+
def _check_docstring(funcname, argname, docstring):
75+
if re.search(f" ([ \\w]+, )*{argname}(,[ \\w]+)*[^ ]:", docstring):
76+
# Found the string, but not in numpydoc form
77+
warnings.warn(f"{funcname} '{argname}' docstring missing space")
78+
return True
79+
80+
elif not re.search(f" ([ \\w]+, )*{argname}(,[ \\w]+)* :", docstring):
81+
# return False
82+
#
83+
# Just issue a warning for now
84+
warnings.warn(f"{funcname} '{argname}' not documented")
85+
return True
86+
87+
return True

0 commit comments

Comments
 (0)