scipy-doctest 1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scipy_doctest/__init__.py +12 -0
- scipy_doctest/__main__.py +6 -0
- scipy_doctest/conftest.py +5 -0
- scipy_doctest/frontend.py +468 -0
- scipy_doctest/impl.py +506 -0
- scipy_doctest/plugin.py +336 -0
- scipy_doctest/tests/__init__.py +0 -0
- scipy_doctest/tests/failure_cases.py +17 -0
- scipy_doctest/tests/failure_cases_2.py +27 -0
- scipy_doctest/tests/finder_cases.py +65 -0
- scipy_doctest/tests/local_file.txt +0 -0
- scipy_doctest/tests/local_file_cases.py +37 -0
- scipy_doctest/tests/module_cases.py +192 -0
- scipy_doctest/tests/octave_a.mat +0 -0
- scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst +2018 -0
- scipy_doctest/tests/stopwords_cases.py +9 -0
- scipy_doctest/tests/test_finder.py +170 -0
- scipy_doctest/tests/test_parser.py +36 -0
- scipy_doctest/tests/test_pytest_configuration.py +95 -0
- scipy_doctest/tests/test_runner.py +105 -0
- scipy_doctest/tests/test_skipmarkers.py +202 -0
- scipy_doctest/tests/test_testfile.py +24 -0
- scipy_doctest/tests/test_testmod.py +158 -0
- scipy_doctest/util.py +279 -0
- scipy_doctest-1.1.dist-info/LICENSE +29 -0
- scipy_doctest-1.1.dist-info/METADATA +390 -0
- scipy_doctest-1.1.dist-info/RECORD +29 -0
- scipy_doctest-1.1.dist-info/WHEEL +4 -0
- scipy_doctest-1.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from . import finder_cases
|
|
4
|
+
from ..util import get_all_list, get_public_objects
|
|
5
|
+
from ..impl import DTFinder, DTConfig
|
|
6
|
+
from ..frontend import find_doctests
|
|
7
|
+
|
|
8
|
+
def test_get_all_list():
|
|
9
|
+
items, depr, other = get_all_list(finder_cases)
|
|
10
|
+
assert sorted(items) == ['Klass', 'func']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_get_all_list_no_all():
|
|
14
|
+
# test get_all_list on a module which does not define all.
|
|
15
|
+
# Remove __all__, test, reload on exit to not depend on the test order.
|
|
16
|
+
try:
|
|
17
|
+
del finder_cases.__all__
|
|
18
|
+
items, depr, other = get_all_list(finder_cases)
|
|
19
|
+
assert items == []
|
|
20
|
+
finally:
|
|
21
|
+
from importlib import reload
|
|
22
|
+
reload(finder_cases)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_get_objects():
|
|
26
|
+
(items, names), failures = get_public_objects(finder_cases)
|
|
27
|
+
assert items == [finder_cases.func, finder_cases.Klass]
|
|
28
|
+
assert names == [obj.__name__ for obj in items]
|
|
29
|
+
assert failures == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_get_objects_extra_items():
|
|
33
|
+
# test get_all_list on a module which defines an incorrect all.
|
|
34
|
+
# Patch __all__, test, reload on exit to not depend on the test order.
|
|
35
|
+
try:
|
|
36
|
+
finder_cases.__all__ += ['spurious']
|
|
37
|
+
(items, names), failures = get_public_objects(finder_cases)
|
|
38
|
+
|
|
39
|
+
assert items == [finder_cases.func, finder_cases.Klass]
|
|
40
|
+
assert len(failures) == 1
|
|
41
|
+
|
|
42
|
+
failed = failures[0]
|
|
43
|
+
assert failed[0].endswith(".spurious")
|
|
44
|
+
assert failed[2].startswith("Missing item")
|
|
45
|
+
|
|
46
|
+
finally:
|
|
47
|
+
from importlib import reload
|
|
48
|
+
reload(finder_cases)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_find_doctests_extra_items():
|
|
52
|
+
# test find_doctests on a module which defines an incorrect all.
|
|
53
|
+
# Patch __all__, test, reload on exit to not depend on the test order.
|
|
54
|
+
try:
|
|
55
|
+
finder_cases.__all__ += ['spurious', 'missing']
|
|
56
|
+
with pytest.raises(ValueError):
|
|
57
|
+
find_doctests(finder_cases, strategy='api')
|
|
58
|
+
finally:
|
|
59
|
+
from importlib import reload
|
|
60
|
+
reload(finder_cases)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestSkiplist:
|
|
64
|
+
"""Test skipping items via skiplists."""
|
|
65
|
+
def test_get_objects_skiplist(self):
|
|
66
|
+
skips = [finder_cases.__name__ + '.' + 'func']
|
|
67
|
+
(items, name), failures = get_public_objects(finder_cases, skiplist=skips)
|
|
68
|
+
|
|
69
|
+
assert items == [finder_cases.Klass]
|
|
70
|
+
assert failures == []
|
|
71
|
+
|
|
72
|
+
def test_get_doctests_no_skiplist(self):
|
|
73
|
+
# strategy=None is equivalent to vanilla doctest module: finds all objects
|
|
74
|
+
tests = find_doctests(finder_cases)
|
|
75
|
+
names = [t.name for t in tests]
|
|
76
|
+
|
|
77
|
+
wanted_names = ['Klass', 'Klass.meth', 'Klass.meth_2', 'func',
|
|
78
|
+
'private_func', '_underscored_private_func']
|
|
79
|
+
base = finder_cases.__name__
|
|
80
|
+
wanted_names = [base] + [base + '.' + n for n in wanted_names]
|
|
81
|
+
|
|
82
|
+
assert sorted(names) == sorted(wanted_names)
|
|
83
|
+
|
|
84
|
+
def test_get_doctests_strategy_None(self):
|
|
85
|
+
# Add a skiplist: strategy=None skips listed items
|
|
86
|
+
base = finder_cases.__name__
|
|
87
|
+
skips = [base + '.func', base + '.Klass.meth_2']
|
|
88
|
+
config = DTConfig(skiplist=skips)
|
|
89
|
+
|
|
90
|
+
tests = find_doctests(finder_cases, config=config)
|
|
91
|
+
names = [t.name for t in tests]
|
|
92
|
+
|
|
93
|
+
# note the lack of `func` and `Klass.meth_2`, as requested
|
|
94
|
+
wanted_names = ['Klass', 'Klass.meth',
|
|
95
|
+
'private_func', '_underscored_private_func']
|
|
96
|
+
wanted_names = [base] + [base + '.' + n for n in wanted_names]
|
|
97
|
+
|
|
98
|
+
assert sorted(names) == sorted(wanted_names)
|
|
99
|
+
|
|
100
|
+
def test_get_doctests_strategy_api(self):
|
|
101
|
+
# Add a skiplist: strategy='api' skips listed items
|
|
102
|
+
base = finder_cases.__name__
|
|
103
|
+
skips = [base + '.func', base + '.Klass.meth_2']
|
|
104
|
+
config = DTConfig(skiplist=skips)
|
|
105
|
+
|
|
106
|
+
tests = find_doctests(finder_cases, strategy='api', config=config)
|
|
107
|
+
names = [t.name for t in tests]
|
|
108
|
+
|
|
109
|
+
# note the lack of
|
|
110
|
+
# - `func` and `Klass.meth_2`, as requested (via the skiplist)
|
|
111
|
+
# - *private* stuff, which is not in `__all__`
|
|
112
|
+
wanted_names = ['Klass', 'Klass.meth']
|
|
113
|
+
wanted_names = [base] + [base + '.' + n for n in wanted_names]
|
|
114
|
+
|
|
115
|
+
assert sorted(names) == sorted(wanted_names)
|
|
116
|
+
|
|
117
|
+
def test_get_doctests_strategy_list(self):
|
|
118
|
+
# Add a skiplist: strategy=<list> skips listed items
|
|
119
|
+
base = finder_cases.__name__
|
|
120
|
+
skips = [base + '.func', base + '.Klass.meth_2']
|
|
121
|
+
config = DTConfig(skiplist=skips)
|
|
122
|
+
|
|
123
|
+
tests = find_doctests(finder_cases,
|
|
124
|
+
strategy=[finder_cases.Klass], config=config)
|
|
125
|
+
names = [t.name for t in tests]
|
|
126
|
+
|
|
127
|
+
# note the lack of
|
|
128
|
+
# - `Klass.meth_2`, as requested (via the skiplist)
|
|
129
|
+
# - the 'base' module (via the strategy=<list>)
|
|
130
|
+
wanted_names = ['Klass', 'Klass.meth']
|
|
131
|
+
wanted_names = [base + '.' + n for n in wanted_names]
|
|
132
|
+
|
|
133
|
+
assert sorted(names) == sorted(wanted_names)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_explicit_object_list():
|
|
137
|
+
objs = [finder_cases.Klass]
|
|
138
|
+
tests = find_doctests(finder_cases, strategy=objs)
|
|
139
|
+
|
|
140
|
+
base = 'scipy_doctest.tests.finder_cases'
|
|
141
|
+
assert ([test.name for test in tests] ==
|
|
142
|
+
[base + '.Klass', base + '.Klass.meth', base + '.Klass.meth_2'])
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_explicit_object_list_with_module():
|
|
146
|
+
# Module docstrings are examined literally, without looking into other objects
|
|
147
|
+
# in the module. These other objects need to be listed explicitly.
|
|
148
|
+
# In the `doctest`-speak: do not recurse.
|
|
149
|
+
objs = [finder_cases, finder_cases.Klass]
|
|
150
|
+
tests = find_doctests(finder_cases, strategy=objs)
|
|
151
|
+
|
|
152
|
+
base = 'scipy_doctest.tests.finder_cases'
|
|
153
|
+
assert ([test.name for test in tests] ==
|
|
154
|
+
[base, base + '.Klass', base + '.Klass.meth', base + '.Klass.meth_2'])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_find_doctests_api():
|
|
158
|
+
# Test that the module itself is included with strategy='api'
|
|
159
|
+
tests = find_doctests(finder_cases, strategy='api')
|
|
160
|
+
|
|
161
|
+
base = 'scipy_doctest.tests.finder_cases'
|
|
162
|
+
assert ([test.name for test in tests] ==
|
|
163
|
+
[base + '.func', base + '.Klass', base + '.Klass.meth',
|
|
164
|
+
base + '.Klass.meth_2', base])
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_dtfinder_config():
|
|
168
|
+
config = DTConfig()
|
|
169
|
+
finder = DTFinder(config=config)
|
|
170
|
+
assert finder.config is config
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from ..impl import DTConfig, DTParser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_parser_default_config():
|
|
5
|
+
# Test that parser adds the _ignore marker for stopwords
|
|
6
|
+
parser = DTParser()
|
|
7
|
+
|
|
8
|
+
string = "Text text \n >>> 1 + plt.xlim([1, 2])\n\n More text"
|
|
9
|
+
examples = parser.get_examples(string)
|
|
10
|
+
|
|
11
|
+
assert len(examples) == 1
|
|
12
|
+
assert examples[0].source == "1 + plt.xlim([1, 2])\n"
|
|
13
|
+
assert examples[0].want == " # _ignore\n"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_parser_vanilla_config():
|
|
17
|
+
# Test that no stopwords means no markers
|
|
18
|
+
config = DTConfig()
|
|
19
|
+
config.stopwords = set()
|
|
20
|
+
parser = DTParser(config)
|
|
21
|
+
|
|
22
|
+
string = "Text text \n >>> 1 + plt.xlim([1, 2])\n\n More text"
|
|
23
|
+
examples = parser.get_examples(string)
|
|
24
|
+
|
|
25
|
+
assert len(examples) == 1
|
|
26
|
+
assert examples[0].source == "1 + plt.xlim([1, 2])\n"
|
|
27
|
+
assert examples[0].want == ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_config_nocopy():
|
|
31
|
+
config = DTConfig()
|
|
32
|
+
parser = DTParser(config)
|
|
33
|
+
assert parser.config is config
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import matplotlib.pyplot as plt # noqa
|
|
5
|
+
HAVE_MATPLOTLIB = True
|
|
6
|
+
except Exception:
|
|
7
|
+
HAVE_MATPLOTLIB = False
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import scipy # noqa
|
|
11
|
+
HAVE_SCIPY = True
|
|
12
|
+
except Exception:
|
|
13
|
+
HAVE_SCIPY = False
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from . import module_cases, failure_cases, failure_cases_2, stopwords_cases, local_file_cases
|
|
18
|
+
|
|
19
|
+
# XXX: this is a bit hacky and repetetive. Can rework?
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
pytest_plugins = ['pytester']
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
|
|
26
|
+
def test_module_cases(pytester):
|
|
27
|
+
"""Test that pytest uses the DTChecker for doctests."""
|
|
28
|
+
path_str = module_cases.__file__
|
|
29
|
+
python_file = Path(path_str)
|
|
30
|
+
result = pytester.inline_run(python_file, "--doctest-modules")
|
|
31
|
+
assert result.ret == pytest.ExitCode.OK
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_failure_cases(pytester):
|
|
35
|
+
file_list = [failure_cases, failure_cases_2]
|
|
36
|
+
for file in file_list:
|
|
37
|
+
path_str = file.__file__
|
|
38
|
+
python_file = Path(path_str)
|
|
39
|
+
result = pytester.inline_run(python_file, "--doctest-modules")
|
|
40
|
+
assert result.ret == pytest.ExitCode.TESTS_FAILED
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.skipif(not HAVE_MATPLOTLIB, reason='need matplotlib')
|
|
44
|
+
def test_stopword_cases(pytester):
|
|
45
|
+
"""Test that pytest uses the DTParser for doctests."""
|
|
46
|
+
path_str = stopwords_cases.__file__
|
|
47
|
+
python_file = Path(path_str)
|
|
48
|
+
result = pytester.inline_run(python_file, "--doctest-modules")
|
|
49
|
+
assert result.ret == pytest.ExitCode.OK
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.xfail(reason="XXX: passes locally, fails on CI")
|
|
53
|
+
@pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
|
|
54
|
+
def test_local_file_cases(pytester):
|
|
55
|
+
"""Test that local files are found for use in doctests.
|
|
56
|
+
"""
|
|
57
|
+
path_str = local_file_cases.__file__
|
|
58
|
+
python_file = Path(path_str)
|
|
59
|
+
result = pytester.inline_run(python_file, "--doctest-modules")
|
|
60
|
+
assert result.ret == pytest.ExitCode.OK
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_alt_checker(pytester):
|
|
64
|
+
"""Test an alternative Checker."""
|
|
65
|
+
|
|
66
|
+
# create a temporary conftest.py file
|
|
67
|
+
pytester.makeconftest(
|
|
68
|
+
"""
|
|
69
|
+
import doctest
|
|
70
|
+
from scipy_doctest.conftest import dt_config
|
|
71
|
+
|
|
72
|
+
class Vanilla(doctest.OutputChecker):
|
|
73
|
+
def __init__(self, config):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
dt_config.CheckerKlass = Vanilla
|
|
77
|
+
"""
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# create a temporary pytest test file
|
|
81
|
+
f = pytester.makepyfile(
|
|
82
|
+
"""
|
|
83
|
+
def func():
|
|
84
|
+
'''
|
|
85
|
+
>>> 2 / 3 # fails with vanilla doctest.OutputChecker
|
|
86
|
+
0.667
|
|
87
|
+
'''
|
|
88
|
+
pass
|
|
89
|
+
"""
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# run all tests with pytest
|
|
93
|
+
result = pytester.inline_run(f, '--doctest-modules')
|
|
94
|
+
assert result.ret == pytest.ExitCode.TESTS_FAILED
|
|
95
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
import doctest
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from . import (failure_cases as module,
|
|
8
|
+
finder_cases as finder_module,
|
|
9
|
+
module_cases)
|
|
10
|
+
from .. import DTFinder, DTRunner, DebugDTRunner, DTConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Smoke test DTRunner methods. Mainly to check that they are runnable.
|
|
14
|
+
|
|
15
|
+
def test_single_failure():
|
|
16
|
+
finder = DTFinder()
|
|
17
|
+
tests = finder.find(module.func9)
|
|
18
|
+
runner = DTRunner(verbose=False)
|
|
19
|
+
stream = io.StringIO()
|
|
20
|
+
for test in tests:
|
|
21
|
+
runner.run(test, out=stream.write)
|
|
22
|
+
|
|
23
|
+
stream.seek(0)
|
|
24
|
+
output = stream.read()
|
|
25
|
+
assert output.startswith('\n func9\n -----\n')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_exception():
|
|
29
|
+
finder = DTFinder()
|
|
30
|
+
tests = finder.find(module.func10)
|
|
31
|
+
runner = DTRunner(verbose=False)
|
|
32
|
+
stream = io.StringIO()
|
|
33
|
+
for test in tests:
|
|
34
|
+
runner.run(test, out=stream.write)
|
|
35
|
+
|
|
36
|
+
stream.seek(0)
|
|
37
|
+
output = stream.read()
|
|
38
|
+
assert output.startswith('\n func10\n ------\n')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_get_history():
|
|
42
|
+
finder = DTFinder()
|
|
43
|
+
tests = finder.find(finder_module)
|
|
44
|
+
runner = DTRunner(verbose=False)
|
|
45
|
+
for test in tests:
|
|
46
|
+
runner.run(test)
|
|
47
|
+
|
|
48
|
+
dct = runner.get_history()
|
|
49
|
+
assert len(dct) == 7
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestDebugDTRunner:
|
|
53
|
+
def test_debug_runner_failure(self):
|
|
54
|
+
finder = DTFinder()
|
|
55
|
+
tests = finder.find(module.func9)
|
|
56
|
+
runner = DebugDTRunner(verbose=False)
|
|
57
|
+
|
|
58
|
+
with pytest.raises(doctest.DocTestFailure) as exc:
|
|
59
|
+
for t in tests:
|
|
60
|
+
runner.run(t)
|
|
61
|
+
|
|
62
|
+
# pytest wraps the original exception, unwrap it
|
|
63
|
+
orig_exception = exc.value
|
|
64
|
+
|
|
65
|
+
# DocTestFailure carries the doctest and the run result
|
|
66
|
+
assert orig_exception.test is tests[0]
|
|
67
|
+
assert orig_exception.test.name == 'func9'
|
|
68
|
+
assert orig_exception.got == 'array([1, 2, 3])\n'
|
|
69
|
+
assert orig_exception.example.want == 'array([2, 3, 4])\n'
|
|
70
|
+
|
|
71
|
+
def test_debug_runner_exception(self):
|
|
72
|
+
finder = DTFinder()
|
|
73
|
+
tests = finder.find(module.func10)
|
|
74
|
+
runner = DebugDTRunner(verbose=False)
|
|
75
|
+
|
|
76
|
+
with pytest.raises(doctest.UnexpectedException) as exc:
|
|
77
|
+
for t in tests:
|
|
78
|
+
runner.run(t)
|
|
79
|
+
orig_exception = exc.value
|
|
80
|
+
|
|
81
|
+
# exception carries the original test
|
|
82
|
+
assert orig_exception.test is tests[0]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class VanillaOutputChecker(doctest.OutputChecker):
|
|
86
|
+
"""doctest.OutputChecker to drop in for DTChecker.
|
|
87
|
+
|
|
88
|
+
LSP break: OutputChecker does not have __init__,
|
|
89
|
+
here we add it to agree with DTChecker.
|
|
90
|
+
"""
|
|
91
|
+
def __init__(self, config):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
class TestCheckerDropIn:
|
|
95
|
+
"""Test DTChecker and vanilla doctest OutputChecker being drop-in replacements.
|
|
96
|
+
"""
|
|
97
|
+
def test_vanilla_checker(self):
|
|
98
|
+
config = DTConfig(CheckerKlass=VanillaOutputChecker)
|
|
99
|
+
runner = DebugDTRunner(config=config)
|
|
100
|
+
tests = DTFinder().find(module_cases.func)
|
|
101
|
+
|
|
102
|
+
with pytest.raises(doctest.DocTestFailure):
|
|
103
|
+
for t in tests:
|
|
104
|
+
runner.run(t)
|
|
105
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import doctest
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import matplotlib.pyplot as plt # noqa
|
|
5
|
+
HAVE_MATPLOTLIB = True
|
|
6
|
+
except Exception:
|
|
7
|
+
HAVE_MATPLOTLIB = False
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from ..impl import DTConfig, DTParser, DebugDTRunner
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestSyntaxErrors:
|
|
15
|
+
"""Syntax errors trigger a doctest failure *unless* marked.
|
|
16
|
+
|
|
17
|
+
Either mark it with +SKIP or as pseudocode.
|
|
18
|
+
"""
|
|
19
|
+
@pytest.mark.skipif(not HAVE_MATPLOTLIB, reason='need matplotlib')
|
|
20
|
+
def test_invalid_python(self):
|
|
21
|
+
# This string raises
|
|
22
|
+
# TypeError("unsupported operand type(s) for +: 'int' and 'tuple'")
|
|
23
|
+
string = ">>> import matplotlib.pyplot as plt; 1 + plt.xlim([1, 2])\n"
|
|
24
|
+
|
|
25
|
+
config = DTConfig()
|
|
26
|
+
parser = DTParser(config)
|
|
27
|
+
|
|
28
|
+
test = parser.get_doctest(string,
|
|
29
|
+
globs=config.default_namespace,
|
|
30
|
+
name='none: plain',
|
|
31
|
+
filename='none',
|
|
32
|
+
lineno=0)
|
|
33
|
+
runner = DebugDTRunner()
|
|
34
|
+
|
|
35
|
+
with pytest.raises(doctest.UnexpectedException) as exc_info:
|
|
36
|
+
runner.run(test)
|
|
37
|
+
|
|
38
|
+
orig_error = exc_info.value.exc_info # unwrap pytest -> doctest -> example
|
|
39
|
+
kind, value, traceback = orig_error
|
|
40
|
+
|
|
41
|
+
assert kind is TypeError
|
|
42
|
+
assert "unsupported operand type(s)" in value.args[0]
|
|
43
|
+
|
|
44
|
+
def test_invalid_python_plus_skip(self):
|
|
45
|
+
# Adding a '# doctest: +SKIP' turns it into pseudocode:
|
|
46
|
+
# example NOT CHECKED, at all
|
|
47
|
+
string = ">>> import matplotlib.pyplot as plt; 1 + plt.xlim([1, 2])"
|
|
48
|
+
string += " # doctest: +SKIP"
|
|
49
|
+
|
|
50
|
+
config = DTConfig()
|
|
51
|
+
parser = DTParser(config)
|
|
52
|
+
|
|
53
|
+
test = parser.get_doctest(string,
|
|
54
|
+
globs=config.default_namespace,
|
|
55
|
+
name='none : +SKIP',
|
|
56
|
+
filename='none',
|
|
57
|
+
lineno=0)
|
|
58
|
+
runner = DebugDTRunner()
|
|
59
|
+
runner.run(test)
|
|
60
|
+
assert runner.get_history() == {'none : +SKIP': (0, 0)}
|
|
61
|
+
|
|
62
|
+
def test_invalid_python_pseudocode(self):
|
|
63
|
+
# Marking a test as pseudocode is equivalent to a +SKIP:
|
|
64
|
+
# example NOT CHECKED, at all
|
|
65
|
+
string = ">>> import matplotlib.pyplot as plt; 1 + plt.xlim([1, 2])"
|
|
66
|
+
|
|
67
|
+
config = DTConfig(pseudocode=['plt.xlim'])
|
|
68
|
+
parser = DTParser(config)
|
|
69
|
+
|
|
70
|
+
test = parser.get_doctest(string,
|
|
71
|
+
globs=config.default_namespace,
|
|
72
|
+
name='none : pseudocode',
|
|
73
|
+
filename='none',
|
|
74
|
+
lineno=0)
|
|
75
|
+
runner = DebugDTRunner()
|
|
76
|
+
runner.run(test)
|
|
77
|
+
assert runner.get_history() == {'none : pseudocode': (0, 0)}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestPseudocodeMarkers:
|
|
81
|
+
"""Marking an example as pseudocode is exactly equivalent to a +SKIP."""
|
|
82
|
+
|
|
83
|
+
def test_pseudocode_markers(self):
|
|
84
|
+
# The first string has a +SKIP, the second one doesn't
|
|
85
|
+
string = ">>> oops #doctest: +SKIP\n>>> ouch\n"
|
|
86
|
+
|
|
87
|
+
parser = doctest.DocTestParser()
|
|
88
|
+
test = parser.get_doctest(string, globs={},
|
|
89
|
+
name='none', filename='none', lineno=0)
|
|
90
|
+
|
|
91
|
+
opts_when_skipped = {doctest.OPTIONFLAGS_BY_NAME['SKIP']: True}
|
|
92
|
+
assert len(test.examples) == 2
|
|
93
|
+
assert test.examples[0].options == opts_when_skipped
|
|
94
|
+
assert test.examples[1].options == {}
|
|
95
|
+
|
|
96
|
+
# Now mark the second example as pseudocode
|
|
97
|
+
config = DTConfig(pseudocode=['ouch'])
|
|
98
|
+
parser = DTParser(config=config)
|
|
99
|
+
test = parser.get_doctest(string, globs={},
|
|
100
|
+
name='none', filename='none', lineno=0)
|
|
101
|
+
|
|
102
|
+
assert len(test.examples) == 2
|
|
103
|
+
assert test.examples[0].options == opts_when_skipped
|
|
104
|
+
assert test.examples[1].options == opts_when_skipped
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestStopwords:
|
|
108
|
+
"""If an example contains a stopword, the example still needs to be a valid
|
|
109
|
+
python code, but the output is not checked.
|
|
110
|
+
"""
|
|
111
|
+
@pytest.mark.skipif(not HAVE_MATPLOTLIB, reason='need matplotlib')
|
|
112
|
+
def test_bogus_output(self):
|
|
113
|
+
# 'plt.xlim(' is a stopword by default in the DTParser. This is because
|
|
114
|
+
# it returns a tuple, which we don't want to litter our docstrings with.
|
|
115
|
+
string = ">>> import matplotlib.pyplot as plt; plt.xlim([1, 2])\n"
|
|
116
|
+
string += "bogus, not what plt.xlim(..) returns\n"
|
|
117
|
+
|
|
118
|
+
parser = DTParser()
|
|
119
|
+
test = parser.get_doctest(string, globs={},
|
|
120
|
+
name='stopwords_bogus_output',
|
|
121
|
+
filename='none', lineno=0)
|
|
122
|
+
assert "bogus" in test.examples[0].want
|
|
123
|
+
|
|
124
|
+
runner = DebugDTRunner()
|
|
125
|
+
runner.run(test)
|
|
126
|
+
|
|
127
|
+
# one example tried, of which zero failed
|
|
128
|
+
assert runner.get_history() == {'stopwords_bogus_output': (0, 1)}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TestMayVary:
|
|
132
|
+
"""The '# may vary' markers are applied to the example output, not source.
|
|
133
|
+
|
|
134
|
+
Otherwise they are equivalent to declaring an example to have a stopword:
|
|
135
|
+
the source needs to be valid python code, but the output is not checked.
|
|
136
|
+
"""
|
|
137
|
+
def test_may_vary(self):
|
|
138
|
+
string = ">>> 1 + 2\n"
|
|
139
|
+
string += "uhm, not sure # may vary\n"
|
|
140
|
+
|
|
141
|
+
parser = DTParser()
|
|
142
|
+
test = parser.get_doctest(string, globs={},
|
|
143
|
+
name='may_vary_markers',
|
|
144
|
+
filename='none', lineno=0)
|
|
145
|
+
assert "uhm" in test.examples[0].want
|
|
146
|
+
|
|
147
|
+
runner = DebugDTRunner()
|
|
148
|
+
runner.run(test)
|
|
149
|
+
|
|
150
|
+
# one example tried, of which zero failed
|
|
151
|
+
assert runner.get_history() == {'may_vary_markers': (0, 1)}
|
|
152
|
+
|
|
153
|
+
def test_may_vary_source(self):
|
|
154
|
+
# The marker needs to be added to the example output, not source.
|
|
155
|
+
string = ">>> 1 + 2 # may vary\n"
|
|
156
|
+
string += "uhm, can't say\n"
|
|
157
|
+
|
|
158
|
+
parser = DTParser()
|
|
159
|
+
test = parser.get_doctest(string, globs={},
|
|
160
|
+
name='may_vary_source',
|
|
161
|
+
filename='none', lineno=0)
|
|
162
|
+
|
|
163
|
+
runner = DebugDTRunner()
|
|
164
|
+
with pytest.raises(doctest.DocTestFailure):
|
|
165
|
+
runner.run(test)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
string='''\
|
|
169
|
+
|
|
170
|
+
This is an example string with doctests and skipblocks. A block is a sequence
|
|
171
|
+
of examples (which start with a >>> marker) without an intervening text, like
|
|
172
|
+
below
|
|
173
|
+
|
|
174
|
+
>>> from some_module import some_function # doctest: +SKIPBLOCK
|
|
175
|
+
>>> some_function(42)
|
|
176
|
+
|
|
177
|
+
Note how the block above will fail doctesting unless the second line is
|
|
178
|
+
skipped. A standard solution is to add a +SKIP marker to every line, but this
|
|
179
|
+
is ugly and we skip the whole block instead.
|
|
180
|
+
|
|
181
|
+
Once the block is over, we get back to usual doctests, which are not skipped
|
|
182
|
+
|
|
183
|
+
>>> 1 + 2
|
|
184
|
+
3
|
|
185
|
+
|
|
186
|
+
'''
|
|
187
|
+
|
|
188
|
+
def test_SKIPBLOCK():
|
|
189
|
+
parser = DTParser()
|
|
190
|
+
test = parser.get_doctest(string,
|
|
191
|
+
globs={},
|
|
192
|
+
name='SKIPBLOCK test',
|
|
193
|
+
filename='none',
|
|
194
|
+
lineno=0)
|
|
195
|
+
|
|
196
|
+
SKIP = doctest.OPTIONFLAGS_BY_NAME['SKIP']
|
|
197
|
+
|
|
198
|
+
assert len(test.examples) == 3
|
|
199
|
+
assert test.examples[0].options[SKIP] is True
|
|
200
|
+
assert test.examples[1].options[SKIP] is True
|
|
201
|
+
assert test.examples[2].options == {} # not skipped
|
|
202
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ..frontend import testfile as doctestfile
|
|
2
|
+
from ..impl import DTConfig
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.xfail(True, reason="needs the scipy repo at a fixed location")
|
|
8
|
+
def test_one_scipy_tutorial():
|
|
9
|
+
# FIXME: HACK, will not work if scipy is not installed,
|
|
10
|
+
path = '/home/br/repos/scipy/scipy/doc/source/tutorial/ndimage.rst'
|
|
11
|
+
|
|
12
|
+
config = DTConfig()
|
|
13
|
+
config.stopwords = {}
|
|
14
|
+
|
|
15
|
+
doctestfile(path,
|
|
16
|
+
module_relative=False, verbose=2, raise_on_error=False,
|
|
17
|
+
config=config)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_linalg_clone():
|
|
21
|
+
# run on a clone of the scipy linalg tutorial
|
|
22
|
+
path = 'scipy_ndimage_tutorial_clone.rst'
|
|
23
|
+
doctestfile(path, package='scipy_doctest.tests', verbose=2,
|
|
24
|
+
raise_on_error=False)
|