Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: collect methods from private superclasses #179

Merged
merged 1 commit into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)



Loading