Skip to content

Commit 1e97c50

Browse files
committed
BUG: collect methods from private superclasses
1 parent 855b4da commit 1e97c50

File tree

3 files changed

+79
-10
lines changed

3 files changed

+79
-10
lines changed

scipy_doctest/frontend.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,24 @@ def find_doctests(module, strategy=None,
9090
# Having collected the list of objects, extract doctests
9191
tests = []
9292
for item, name in zip(items, names):
93-
full_name = module.__name__ + '.' + name
9493
if inspect.ismodule(item):
9594
# do not recurse, only inspect the module docstring
9695
_finder = DTFinder(recurse=False, config=config)
9796
t = _finder.find(item, name, globs=globs, extraglobs=extraglobs)
97+
unique_t = set(t)
9898
else:
99+
full_name = module.__name__ + '.' + name
99100
t = finder.find(item, full_name, globs=globs, extraglobs=extraglobs)
100-
tests += t
101+
102+
unique_t = set(t)
103+
if hasattr(item, '__mro__'):
104+
# is a class, inspect superclasses
105+
# cf https://github.com/scipy/scipy_doctest/issues/177
106+
# item.__mro__ starts with itself, ends with `object`
107+
for item_ in item.__mro__[1:-1]:
108+
t_ = finder.find(item_, full_name, globs=globs, extraglobs=extraglobs)
109+
unique_t.update(set(t_))
110+
tests += list(unique_t)
101111

102112
# If the skiplist contains methods of objects, their doctests may have been
103113
# left in the `tests` list. Remove them.

scipy_doctest/tests/finder_cases_2.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
Private method in subclasses
3+
"""
4+
5+
__all__ = ["Klass"]
6+
7+
class _PrivateKlass:
8+
def private_method(self):
9+
"""
10+
>>> 2 / 3
11+
0.667
12+
"""
13+
pass
14+
15+
16+
class Klass(_PrivateKlass):
17+
def public_method(self):
18+
"""
19+
>>> 3 / 4
20+
0.74
21+
"""
22+
pass

scipy_doctest/tests/test_finder.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44

55
from . import finder_cases
6+
from . import finder_cases_2
67
from ..util import get_all_list, get_public_objects
78
from ..impl import DTFinder, DTConfig
89
from ..frontend import find_doctests
@@ -142,10 +143,13 @@ def test_explicit_object_list():
142143
objs = [finder_cases.Klass]
143144
tests = find_doctests(finder_cases, strategy=objs)
144145

146+
names = sorted([test.name for test in tests])
147+
145148
base = 'scipy_doctest.tests.finder_cases'
146-
assert ([test.name for test in tests] ==
147-
[f'{base}.Klass', f'{base}.Klass.meth', f'{base}.Klass.meth_2',
148-
f'{base}.Klass.__weakref__'])
149+
expected = sorted([f'{base}.Klass', f'{base}.Klass.__weakref__',
150+
f'{base}.Klass.meth', f'{base}.Klass.meth_2',])
151+
152+
assert names == expected
149153

150154

151155
def test_explicit_object_list_with_module():
@@ -155,21 +159,27 @@ def test_explicit_object_list_with_module():
155159
objs = [finder_cases, finder_cases.Klass]
156160
tests = find_doctests(finder_cases, strategy=objs)
157161

162+
names = sorted([test.name for test in tests])
163+
158164
base = 'scipy_doctest.tests.finder_cases'
159-
assert ([test.name for test in tests] ==
160-
[base, f'{base}.Klass', f'{base}.Klass.meth', f'{base}.Klass.meth_2',
161-
f'{base}.Klass.__weakref__'])
165+
expected = sorted([base, f'{base}.Klass', f'{base}.Klass.__weakref__',
166+
f'{base}.Klass.meth', f'{base}.Klass.meth_2'])
167+
168+
assert names == expected
162169

163170

164171
def test_find_doctests_api():
165172
# Test that the module itself is included with strategy='api'
166173
tests = find_doctests(finder_cases, strategy='api')
167174

175+
names = sorted([test.name for test in tests])
176+
168177
base = 'scipy_doctest.tests.finder_cases'
169-
assert ([test.name for test in tests] ==
170-
[base + '.func', base + '.Klass', base + '.Klass.meth',
178+
expected = sorted([base + '.func', base + '.Klass', base + '.Klass.meth',
171179
base + '.Klass.meth_2', base + '.Klass.__weakref__', base])
172180

181+
assert names == expected
182+
173183

174184
def test_dtfinder_config():
175185
config = DTConfig()
@@ -183,3 +193,30 @@ def test_descriptors_get_collected():
183193
names = [test.name for test in tests]
184194
assert 'numpy.dtype.kind' in names # was previously missing
185195

196+
197+
@pytest.mark.parametrize('strategy', [None, 'api'])
198+
def test_private_superclasses(strategy):
199+
# Test that methods from inherited private superclasses get collected
200+
tests = find_doctests(finder_cases_2, strategy=strategy)
201+
202+
names = set(test.name.split('.')[-1] for test in tests)
203+
expected_names = ['finder_cases_2', 'public_method', 'private_method']
204+
if strategy == 'api':
205+
expected_names += ['__weakref__']
206+
207+
assert len(tests) == len(expected_names)
208+
assert names == set(expected_names)
209+
210+
211+
def test_private_superclasses_2():
212+
# similar to test_private_superclass, only with an explicit strategy=list
213+
tests = find_doctests(finder_cases_2, strategy=[finder_cases_2.Klass])
214+
215+
names = set(test.name.split('.')[-1] for test in tests)
216+
expected_names = ['public_method', 'private_method', '__weakref__']
217+
218+
assert len(tests) == len(expected_names)
219+
assert names == set(expected_names)
220+
221+
222+

0 commit comments

Comments
 (0)