Skip to content

Commit

Permalink
BUG: collect methods from private superclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
ev-br committed Dec 17, 2024
1 parent 855b4da commit 1e97c50
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 10 deletions.
14 changes: 12 additions & 2 deletions scipy_doctest/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,24 @@ def find_doctests(module, strategy=None,
# Having collected the list of objects, extract doctests
tests = []
for item, name in zip(items, names):
full_name = module.__name__ + '.' + name
if inspect.ismodule(item):
# do not recurse, only inspect the module docstring
_finder = DTFinder(recurse=False, config=config)
t = _finder.find(item, name, globs=globs, extraglobs=extraglobs)
unique_t = set(t)
else:
full_name = module.__name__ + '.' + name
t = finder.find(item, full_name, globs=globs, extraglobs=extraglobs)
tests += t

unique_t = set(t)
if hasattr(item, '__mro__'):
# is a class, inspect superclasses
# cf https://github.com/scipy/scipy_doctest/issues/177
# item.__mro__ starts with itself, ends with `object`
for item_ in item.__mro__[1:-1]:
t_ = finder.find(item_, full_name, globs=globs, extraglobs=extraglobs)
unique_t.update(set(t_))
tests += list(unique_t)

# If the skiplist contains methods of objects, their doctests may have been
# left in the `tests` list. Remove them.
Expand Down
22 changes: 22 additions & 0 deletions scipy_doctest/tests/finder_cases_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Private method in subclasses
"""

__all__ = ["Klass"]

class _PrivateKlass:
def private_method(self):
"""
>>> 2 / 3
0.667
"""
pass


class Klass(_PrivateKlass):
def public_method(self):
"""
>>> 3 / 4
0.74
"""
pass
53 changes: 45 additions & 8 deletions scipy_doctest/tests/test_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from . import finder_cases
from . import finder_cases_2
from ..util import get_all_list, get_public_objects
from ..impl import DTFinder, DTConfig
from ..frontend import find_doctests
Expand Down Expand Up @@ -142,10 +143,13 @@ def test_explicit_object_list():
objs = [finder_cases.Klass]
tests = find_doctests(finder_cases, strategy=objs)

names = sorted([test.name for test in tests])

base = 'scipy_doctest.tests.finder_cases'
assert ([test.name for test in tests] ==
[f'{base}.Klass', f'{base}.Klass.meth', f'{base}.Klass.meth_2',
f'{base}.Klass.__weakref__'])
expected = sorted([f'{base}.Klass', f'{base}.Klass.__weakref__',
f'{base}.Klass.meth', f'{base}.Klass.meth_2',])

assert names == expected


def test_explicit_object_list_with_module():
Expand All @@ -155,21 +159,27 @@ def test_explicit_object_list_with_module():
objs = [finder_cases, finder_cases.Klass]
tests = find_doctests(finder_cases, strategy=objs)

names = sorted([test.name for test in tests])

base = 'scipy_doctest.tests.finder_cases'
assert ([test.name for test in tests] ==
[base, f'{base}.Klass', f'{base}.Klass.meth', f'{base}.Klass.meth_2',
f'{base}.Klass.__weakref__'])
expected = sorted([base, f'{base}.Klass', f'{base}.Klass.__weakref__',
f'{base}.Klass.meth', f'{base}.Klass.meth_2'])

assert names == expected


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

names = sorted([test.name for test in tests])

base = 'scipy_doctest.tests.finder_cases'
assert ([test.name for test in tests] ==
[base + '.func', base + '.Klass', base + '.Klass.meth',
expected = sorted([base + '.func', base + '.Klass', base + '.Klass.meth',
base + '.Klass.meth_2', base + '.Klass.__weakref__', base])

assert names == expected


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


@pytest.mark.parametrize('strategy', [None, 'api'])
def test_private_superclasses(strategy):
# Test that methods from inherited private superclasses get collected
tests = find_doctests(finder_cases_2, strategy=strategy)

names = set(test.name.split('.')[-1] for test in tests)
expected_names = ['finder_cases_2', 'public_method', 'private_method']
if strategy == 'api':
expected_names += ['__weakref__']

assert len(tests) == len(expected_names)
assert names == set(expected_names)


def test_private_superclasses_2():
# similar to test_private_superclass, only with an explicit strategy=list
tests = find_doctests(finder_cases_2, strategy=[finder_cases_2.Klass])

names = set(test.name.split('.')[-1] for test in tests)
expected_names = ['public_method', 'private_method', '__weakref__']

assert len(tests) == len(expected_names)
assert names == set(expected_names)



0 comments on commit 1e97c50

Please sign in to comment.