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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
from __future__ import print_function
import inspect
from inspect import getsource
import os.path as op
from pkgutil import walk_packages
import re
import sys
from unittest import SkipTest
import pytest
import mne
from mne.utils import (run_tests_if_main, _doc_special_members,
requires_numpydoc)
from mne.fixes import _get_args
public_modules = [
# the list of modules users need to access for all functionality
'mne',
'mne.beamformer',
'mne.chpi',
'mne.connectivity',
'mne.cuda',
'mne.datasets',
'mne.datasets.brainstorm',
'mne.datasets.hf_sef',
'mne.datasets.megsim',
'mne.datasets.sample',
'mne.decoding',
'mne.dipole',
'mne.filter',
'mne.inverse_sparse',
'mne.io',
'mne.io.kit',
'mne.minimum_norm',
'mne.preprocessing',
'mne.realtime',
'mne.report',
'mne.simulation',
'mne.source_estimate',
'mne.source_space',
'mne.stats',
'mne.time_frequency',
'mne.time_frequency.tfr',
'mne.viz',
]
def get_name(func):
"""Get the name."""
parts = []
module = inspect.getmodule(func)
if module:
parts.append(module.__name__)
if hasattr(func, 'im_class'):
parts.append(func.im_class.__name__)
parts.append(func.__name__)
return '.'.join(parts)
# functions to ignore args / docstring of
_docstring_ignores = [
'mne.io.Info', # Parameters
'mne.io.write', # always ignore these
'mne.datasets.sample.sample.requires_sample_data',
# Deprecations
]
_tab_ignores = [
]
def check_parameters_match(func, doc=None):
"""Check docstring, return list of incorrect results."""
from numpydoc import docscrape
incorrect = []
name_ = get_name(func)
if not name_.startswith('mne.') or name_.startswith('mne.externals'):
return incorrect
if inspect.isdatadescriptor(func):
return incorrect
args = _get_args(func)
# drop self
if len(args) > 0 and args[0] == 'self':
args = args[1:]
if doc is None:
with pytest.warns(None) as w:
try:
doc = docscrape.FunctionDoc(func)
except Exception as exp:
incorrect += [name_ + ' parsing error: ' + str(exp)]
return incorrect
if len(w):
raise RuntimeError('Error for %s:\n%s' % (name_, w[0]))
# check set
param_names = [name for name, _, _ in doc['Parameters']]
# clean up some docscrape output:
param_names = [name.split(':')[0].strip('` ') for name in param_names]
param_names = [name for name in param_names if '*' not in name]
if len(param_names) != len(args):
bad = str(sorted(list(set(param_names) - set(args)) +
list(set(args) - set(param_names))))
if not any(re.match(d, name_) for d in _docstring_ignores) and \
'deprecation_wrapped' not in func.__code__.co_name:
incorrect += [name_ + ' arg mismatch: ' + bad]
else:
for n1, n2 in zip(param_names, args):
if n1 != n2:
incorrect += [name_ + ' ' + n1 + ' != ' + n2]
return incorrect
@requires_numpydoc
def test_docstring_parameters():
"""Test module docstring formatting."""
from numpydoc import docscrape
# skip modules that require mayavi if mayavi is not installed
public_modules_ = public_modules[:]
try:
import mayavi # noqa: F401 analysis:ignore
public_modules_.append('mne.gui')
except ImportError:
pass
incorrect = []
for name in public_modules_:
with pytest.warns(None): # traits warnings
module = __import__(name, globals())
for submod in name.split('.')[1:]:
module = getattr(module, submod)
classes = inspect.getmembers(module, inspect.isclass)
for cname, cls in classes:
if cname.startswith('_') and cname not in _doc_special_members:
continue
with pytest.warns(None) as w:
cdoc = docscrape.ClassDoc(cls)
if len(w):
raise RuntimeError('Error for __init__ of %s in %s:\n%s'
% (cls, name, w[0]))
if hasattr(cls, '__init__'):
incorrect += check_parameters_match(cls.__init__, cdoc)
for method_name in cdoc.methods:
method = getattr(cls, method_name)
incorrect += check_parameters_match(method)
if hasattr(cls, '__call__'):
incorrect += check_parameters_match(cls.__call__)
functions = inspect.getmembers(module, inspect.isfunction)
for fname, func in functions:
if fname.startswith('_'):
continue
incorrect += check_parameters_match(func)
msg = '\n' + '\n'.join(sorted(list(set(incorrect))))
if len(incorrect) > 0:
raise AssertionError(msg)
def test_tabs():
"""Test that there are no tabs in our source files."""
# avoid importing modules that require mayavi if mayavi is not installed
ignore = _tab_ignores[:]
try:
import mayavi # noqa: F401 analysis:ignore
except ImportError:
ignore.extend('mne.gui.' + name for name in
('_coreg_gui', '_fiducials_gui', '_file_traits', '_help',
'_kit2fiff_gui', '_marker_gui', '_viewer'))
for importer, modname, ispkg in walk_packages(mne.__path__, prefix='mne.'):
# because we don't import e.g. mne.tests w/mne
if not ispkg and modname not in ignore:
# mod = importlib.import_module(modname) # not py26 compatible!
try:
with pytest.warns(None):
__import__(modname)
except Exception: # can't import properly
continue
mod = sys.modules[modname]
try:
source = getsource(mod)
except IOError: # user probably should have run "make clean"
continue
assert '\t' not in source, ('"%s" has tabs, please remove them '
'or add it to the ignore list'
% modname)
documented_ignored_mods = (
'mne.fixes',
'mne.io.write',
'mne.utils',
'mne.viz.utils',
)
documented_ignored_names = """
BaseEstimator
ContainsMixin
CrossSpectralDensity
FilterMixin
GeneralizationAcrossTime
RawFIF
TimeMixin
ToDataFrameMixin
TransformerMixin
UpdateChannelsMixin
adjust_axes
apply_maxfilter
apply_trans
channel_type
check_n_jobs
combine_kit_markers
combine_tfr
combine_transforms
design_mne_c_filter
detrend
dir_tree_find
fast_cross_3d
fiff_open
find_outliers
find_source_space_hemi
find_tag
get_score_funcs
get_version
invert_transform
is_power2
iter_topography
kit2fiff
label_src_vertno_sel
make_eeg_average_ref_proj
make_projector
mesh_dist
mesh_edges
next_fast_len
parallel_func
pick_channels_evoked
plot_epochs_psd
plot_epochs_psd_topomap
plot_raw_psd_topo
plot_source_spectrogram
prepare_inverse_operator
read_fiducials
read_tag
requires_sample_data
rescale
simulate_noise_evoked
source_estimate_quantification
whiten_evoked
write_fiducials
write_info
""".split('\n')
def test_documented():
"""Test that public functions and classes are documented."""
# skip modules that require mayavi if mayavi is not installed
public_modules_ = public_modules[:]
try:
import mayavi # noqa: F401, analysis:ignore
except ImportError:
pass
else:
public_modules_.append('mne.gui')
doc_file = op.abspath(op.join(op.dirname(__file__), '..', '..', 'doc',
'python_reference.rst'))
if not op.isfile(doc_file):
raise SkipTest('Documentation file not found: %s' % doc_file)
known_names = list()
with open(doc_file, 'rb') as fid:
for line in fid:
line = line.decode('utf-8')
if not line.startswith(' '): # at least two spaces
continue
line = line.split()
if len(line) == 1 and line[0] != ':':
known_names.append(line[0].split('.')[-1])
known_names = set(known_names)
missing = []
for name in public_modules_:
with pytest.warns(None): # traits warnings
module = __import__(name, globals())
for submod in name.split('.')[1:]:
module = getattr(module, submod)
classes = inspect.getmembers(module, inspect.isclass)
functions = inspect.getmembers(module, inspect.isfunction)
checks = list(classes) + list(functions)
for name, cf in checks:
if not name.startswith('_') and name not in known_names:
from_mod = inspect.getmodule(cf).__name__
if (from_mod.startswith('mne') and
not from_mod.startswith('mne.externals') and
from_mod not in documented_ignored_mods and
name not in documented_ignored_names):
missing.append('%s (%s.%s)' % (name, from_mod, name))
if len(missing) > 0:
raise AssertionError('\n\nFound new public members missing from '
'doc/python_reference.rst:\n\n* ' +
'\n* '.join(sorted(set(missing))))
run_tests_if_main()
|