|
@@
-1,850
+1,877
|
|
1
|
"""Nose Plugin that supports IPython doctests.
|
|
1
|
"""Nose Plugin that supports IPython doctests.
|
|
2
|
|
|
2
|
|
|
3
|
Limitations:
|
|
3
|
Limitations:
|
|
4
|
|
|
4
|
|
|
5
|
- When generating examples for use as doctests, make sure that you have
|
|
5
|
- When generating examples for use as doctests, make sure that you have
|
|
6
|
pretty-printing OFF. This can be done either by starting ipython with the
|
|
6
|
pretty-printing OFF. This can be done either by starting ipython with the
|
|
7
|
flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by
|
|
7
|
flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by
|
|
8
|
interactively disabling it with %Pprint. This is required so that IPython
|
|
8
|
interactively disabling it with %Pprint. This is required so that IPython
|
|
9
|
output matches that of normal Python, which is used by doctest for internal
|
|
9
|
output matches that of normal Python, which is used by doctest for internal
|
|
10
|
execution.
|
|
10
|
execution.
|
|
11
|
|
|
11
|
|
|
12
|
- Do not rely on specific prompt numbers for results (such as using
|
|
12
|
- Do not rely on specific prompt numbers for results (such as using
|
|
13
|
'_34==True', for example). For IPython tests run via an external process the
|
|
13
|
'_34==True', for example). For IPython tests run via an external process the
|
|
14
|
prompt numbers may be different, and IPython tests run as normal python code
|
|
14
|
prompt numbers may be different, and IPython tests run as normal python code
|
|
15
|
won't even have these special _NN variables set at all.
|
|
15
|
won't even have these special _NN variables set at all.
|
|
16
|
"""
|
|
16
|
"""
|
|
17
|
|
|
17
|
|
|
18
|
|
|
18
|
|
|
19
|
#-----------------------------------------------------------------------------
|
|
19
|
#-----------------------------------------------------------------------------
|
|
20
|
# Module imports
|
|
20
|
# Module imports
|
|
21
|
|
|
21
|
|
|
22
|
# From the standard library
|
|
22
|
# From the standard library
|
|
23
|
import __builtin__
|
|
23
|
import __builtin__
|
|
24
|
import commands
|
|
24
|
import commands
|
|
25
|
import doctest
|
|
25
|
import doctest
|
|
26
|
import inspect
|
|
26
|
import inspect
|
|
27
|
import logging
|
|
27
|
import logging
|
|
28
|
import os
|
|
28
|
import os
|
|
29
|
import re
|
|
29
|
import re
|
|
30
|
import sys
|
|
30
|
import sys
|
|
31
|
import traceback
|
|
31
|
import traceback
|
|
32
|
import unittest
|
|
32
|
import unittest
|
|
33
|
|
|
33
|
|
|
34
|
from inspect import getmodule
|
|
34
|
from inspect import getmodule
|
|
35
|
from StringIO import StringIO
|
|
35
|
from StringIO import StringIO
|
|
36
|
|
|
36
|
|
|
37
|
# We are overriding the default doctest runner, so we need to import a few
|
|
37
|
# We are overriding the default doctest runner, so we need to import a few
|
|
38
|
# things from doctest directly
|
|
38
|
# things from doctest directly
|
|
39
|
from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
|
|
39
|
from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
|
|
40
|
_unittest_reportflags, DocTestRunner,
|
|
40
|
_unittest_reportflags, DocTestRunner,
|
|
41
|
_extract_future_flags, pdb, _OutputRedirectingPdb,
|
|
41
|
_extract_future_flags, pdb, _OutputRedirectingPdb,
|
|
42
|
_exception_traceback,
|
|
42
|
_exception_traceback,
|
|
43
|
linecache)
|
|
43
|
linecache)
|
|
44
|
|
|
44
|
|
|
45
|
# Third-party modules
|
|
45
|
# Third-party modules
|
|
46
|
import nose.core
|
|
46
|
import nose.core
|
|
47
|
|
|
47
|
|
|
48
|
from nose.plugins import doctests, Plugin
|
|
48
|
from nose.plugins import doctests, Plugin
|
|
49
|
from nose.util import anyp, getpackage, test_address, resolve_name, tolist
|
|
49
|
from nose.util import anyp, getpackage, test_address, resolve_name, tolist
|
|
50
|
|
|
50
|
|
|
51
|
#-----------------------------------------------------------------------------
|
|
51
|
#-----------------------------------------------------------------------------
|
|
52
|
# Module globals and other constants
|
|
52
|
# Module globals and other constants
|
|
53
|
|
|
53
|
|
|
54
|
log = logging.getLogger(__name__)
|
|
54
|
log = logging.getLogger(__name__)
|
|
55
|
|
|
55
|
|
|
56
|
###########################################################################
|
|
56
|
###########################################################################
|
|
57
|
# *** HACK ***
|
|
57
|
# *** HACK ***
|
|
58
|
# We must start our own ipython object and heavily muck with it so that all the
|
|
58
|
# We must start our own ipython object and heavily muck with it so that all the
|
|
59
|
# modifications IPython makes to system behavior don't send the doctest
|
|
59
|
# modifications IPython makes to system behavior don't send the doctest
|
|
60
|
# machinery into a fit. This code should be considered a gross hack, but it
|
|
60
|
# machinery into a fit. This code should be considered a gross hack, but it
|
|
61
|
# gets the job done.
|
|
61
|
# gets the job done.
|
|
62
|
|
|
62
|
|
|
63
|
|
|
63
|
|
|
64
|
# Hack to modify the %run command so we can sync the user's namespace with the
|
|
64
|
# Hack to modify the %run command so we can sync the user's namespace with the
|
|
65
|
# test globals. Once we move over to a clean magic system, this will be done
|
|
65
|
# test globals. Once we move over to a clean magic system, this will be done
|
|
66
|
# with much less ugliness.
|
|
66
|
# with much less ugliness.
|
|
67
|
|
|
67
|
|
|
68
|
class py_file_finder(object):
|
|
68
|
class py_file_finder(object):
|
|
69
|
def __init__(self,test_filename):
|
|
69
|
def __init__(self,test_filename):
|
|
70
|
self.test_filename = test_filename
|
|
70
|
self.test_filename = test_filename
|
|
71
|
|
|
71
|
|
|
72
|
def __call__(self,name):
|
|
72
|
def __call__(self,name):
|
|
73
|
from IPython.genutils import get_py_filename
|
|
73
|
from IPython.genutils import get_py_filename
|
|
74
|
try:
|
|
74
|
try:
|
|
75
|
return get_py_filename(name)
|
|
75
|
return get_py_filename(name)
|
|
76
|
except IOError:
|
|
76
|
except IOError:
|
|
77
|
test_dir = os.path.dirname(self.test_filename)
|
|
77
|
test_dir = os.path.dirname(self.test_filename)
|
|
78
|
new_path = os.path.join(test_dir,name)
|
|
78
|
new_path = os.path.join(test_dir,name)
|
|
79
|
return get_py_filename(new_path)
|
|
79
|
return get_py_filename(new_path)
|
|
80
|
|
|
80
|
|
|
81
|
|
|
81
|
|
|
82
|
def _run_ns_sync(self,arg_s,runner=None):
|
|
82
|
def _run_ns_sync(self,arg_s,runner=None):
|
|
83
|
"""Modified version of %run that syncs testing namespaces.
|
|
83
|
"""Modified version of %run that syncs testing namespaces.
|
|
84
|
|
|
84
|
|
|
85
|
This is strictly needed for running doctests that call %run.
|
|
85
|
This is strictly needed for running doctests that call %run.
|
|
86
|
"""
|
|
86
|
"""
|
|
87
|
|
|
87
|
|
|
88
|
finder = py_file_finder(_run_ns_sync.test_filename)
|
|
88
|
finder = py_file_finder(_run_ns_sync.test_filename)
|
|
89
|
out = _ip.IP.magic_run_ori(arg_s,runner,finder)
|
|
89
|
out = _ip.IP.magic_run_ori(arg_s,runner,finder)
|
|
90
|
_run_ns_sync.test_globs.update(_ip.user_ns)
|
|
90
|
_run_ns_sync.test_globs.update(_ip.user_ns)
|
|
91
|
return out
|
|
91
|
return out
|
|
92
|
|
|
92
|
|
|
93
|
|
|
93
|
|
|
94
|
class ipnsdict(dict):
|
|
94
|
class ipnsdict(dict):
|
|
95
|
"""A special subclass of dict for use as an IPython namespace in doctests.
|
|
95
|
"""A special subclass of dict for use as an IPython namespace in doctests.
|
|
96
|
|
|
96
|
|
|
97
|
This subclass adds a simple checkpointing capability so that when testing
|
|
97
|
This subclass adds a simple checkpointing capability so that when testing
|
|
98
|
machinery clears it (we use it as the test execution context), it doesn't
|
|
98
|
machinery clears it (we use it as the test execution context), it doesn't
|
|
99
|
get completely destroyed.
|
|
99
|
get completely destroyed.
|
|
100
|
"""
|
|
100
|
"""
|
|
101
|
|
|
101
|
|
|
102
|
def __init__(self,*a):
|
|
102
|
def __init__(self,*a):
|
|
103
|
dict.__init__(self,*a)
|
|
103
|
dict.__init__(self,*a)
|
|
104
|
self._savedict = {}
|
|
104
|
self._savedict = {}
|
|
105
|
|
|
105
|
|
|
106
|
def clear(self):
|
|
106
|
def clear(self):
|
|
107
|
dict.clear(self)
|
|
107
|
dict.clear(self)
|
|
108
|
self.update(self._savedict)
|
|
108
|
self.update(self._savedict)
|
|
109
|
|
|
109
|
|
|
110
|
def _checkpoint(self):
|
|
110
|
def _checkpoint(self):
|
|
111
|
self._savedict.clear()
|
|
111
|
self._savedict.clear()
|
|
112
|
self._savedict.update(self)
|
|
112
|
self._savedict.update(self)
|
|
113
|
|
|
113
|
|
|
114
|
def update(self,other):
|
|
114
|
def update(self,other):
|
|
115
|
self._checkpoint()
|
|
115
|
self._checkpoint()
|
|
116
|
dict.update(self,other)
|
|
116
|
dict.update(self,other)
|
|
117
|
# If '_' is in the namespace, python won't set it when executing code,
|
|
117
|
# If '_' is in the namespace, python won't set it when executing code,
|
|
118
|
# and we have examples that test it. So we ensure that the namespace
|
|
118
|
# and we have examples that test it. So we ensure that the namespace
|
|
119
|
# is always 'clean' of it before it's used for test code execution.
|
|
119
|
# is always 'clean' of it before it's used for test code execution.
|
|
120
|
self.pop('_',None)
|
|
120
|
self.pop('_',None)
|
|
121
|
|
|
121
|
|
|
122
|
|
|
122
|
|
|
123
|
def start_ipython():
|
|
123
|
def start_ipython():
|
|
124
|
"""Start a global IPython shell, which we need for IPython-specific syntax.
|
|
124
|
"""Start a global IPython shell, which we need for IPython-specific syntax.
|
|
125
|
"""
|
|
125
|
"""
|
|
|
|
|
126
|
|
|
|
|
|
127
|
# This function should only ever run once!
|
|
|
|
|
128
|
if hasattr(start_ipython,'already_called'):
|
|
|
|
|
129
|
return
|
|
|
|
|
130
|
start_ipython.already_called = True
|
|
|
|
|
131
|
|
|
|
|
|
132
|
# Ok, first time we're called, go ahead
|
|
126
|
import new
|
|
133
|
import new
|
|
127
|
|
|
134
|
|
|
128
|
import IPython
|
|
135
|
import IPython
|
|
129
|
|
|
136
|
|
|
130
|
def xsys(cmd):
|
|
137
|
def xsys(cmd):
|
|
131
|
"""Execute a command and print its output.
|
|
138
|
"""Execute a command and print its output.
|
|
132
|
|
|
139
|
|
|
133
|
This is just a convenience function to replace the IPython system call
|
|
140
|
This is just a convenience function to replace the IPython system call
|
|
134
|
with one that is more doctest-friendly.
|
|
141
|
with one that is more doctest-friendly.
|
|
135
|
"""
|
|
142
|
"""
|
|
136
|
cmd = _ip.IP.var_expand(cmd,depth=1)
|
|
143
|
cmd = _ip.IP.var_expand(cmd,depth=1)
|
|
137
|
sys.stdout.write(commands.getoutput(cmd))
|
|
144
|
sys.stdout.write(commands.getoutput(cmd))
|
|
138
|
sys.stdout.flush()
|
|
145
|
sys.stdout.flush()
|
|
139
|
|
|
146
|
|
|
140
|
# Store certain global objects that IPython modifies
|
|
147
|
# Store certain global objects that IPython modifies
|
|
141
|
_displayhook = sys.displayhook
|
|
148
|
_displayhook = sys.displayhook
|
|
142
|
_excepthook = sys.excepthook
|
|
149
|
_excepthook = sys.excepthook
|
|
143
|
_main = sys.modules.get('__main__')
|
|
150
|
_main = sys.modules.get('__main__')
|
|
144
|
|
|
151
|
|
|
145
|
# Start IPython instance. We customize it to start with minimal frills.
|
|
152
|
# Start IPython instance. We customize it to start with minimal frills.
|
|
146
|
user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict())
|
|
153
|
user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict())
|
|
147
|
IPython.Shell.IPShell(['--colors=NoColor','--noterm_title'],
|
|
154
|
IPython.Shell.IPShell(['--colors=NoColor','--noterm_title'],
|
|
148
|
user_ns,global_ns)
|
|
155
|
user_ns,global_ns)
|
|
149
|
|
|
156
|
|
|
150
|
# Deactivate the various python system hooks added by ipython for
|
|
157
|
# Deactivate the various python system hooks added by ipython for
|
|
151
|
# interactive convenience so we don't confuse the doctest system
|
|
158
|
# interactive convenience so we don't confuse the doctest system
|
|
152
|
sys.modules['__main__'] = _main
|
|
159
|
sys.modules['__main__'] = _main
|
|
153
|
sys.displayhook = _displayhook
|
|
160
|
sys.displayhook = _displayhook
|
|
154
|
sys.excepthook = _excepthook
|
|
161
|
sys.excepthook = _excepthook
|
|
155
|
|
|
162
|
|
|
156
|
# So that ipython magics and aliases can be doctested (they work by making
|
|
163
|
# So that ipython magics and aliases can be doctested (they work by making
|
|
157
|
# a call into a global _ip object)
|
|
164
|
# a call into a global _ip object)
|
|
158
|
_ip = IPython.ipapi.get()
|
|
165
|
_ip = IPython.ipapi.get()
|
|
159
|
__builtin__._ip = _ip
|
|
166
|
__builtin__._ip = _ip
|
|
160
|
|
|
167
|
|
|
161
|
# Modify the IPython system call with one that uses getoutput, so that we
|
|
168
|
# Modify the IPython system call with one that uses getoutput, so that we
|
|
162
|
# can capture subcommands and print them to Python's stdout, otherwise the
|
|
169
|
# can capture subcommands and print them to Python's stdout, otherwise the
|
|
163
|
# doctest machinery would miss them.
|
|
170
|
# doctest machinery would miss them.
|
|
164
|
_ip.system = xsys
|
|
171
|
_ip.system = xsys
|
|
165
|
|
|
172
|
|
|
166
|
# Also patch our %run function in.
|
|
173
|
# Also patch our %run function in.
|
|
167
|
im = new.instancemethod(_run_ns_sync,_ip.IP, _ip.IP.__class__)
|
|
174
|
im = new.instancemethod(_run_ns_sync,_ip.IP, _ip.IP.__class__)
|
|
168
|
_ip.IP.magic_run_ori = _ip.IP.magic_run
|
|
175
|
_ip.IP.magic_run_ori = _ip.IP.magic_run
|
|
169
|
_ip.IP.magic_run = im
|
|
176
|
_ip.IP.magic_run = im
|
|
170
|
|
|
177
|
|
|
171
|
# The start call MUST be made here. I'm not sure yet why it doesn't work if
|
|
178
|
# The start call MUST be made here. I'm not sure yet why it doesn't work if
|
|
172
|
# it is made later, at plugin initialization time, but in all my tests, that's
|
|
179
|
# it is made later, at plugin initialization time, but in all my tests, that's
|
|
173
|
# the case.
|
|
180
|
# the case.
|
|
174
|
start_ipython()
|
|
181
|
start_ipython()
|
|
175
|
|
|
182
|
|
|
176
|
# *** END HACK ***
|
|
183
|
# *** END HACK ***
|
|
177
|
###########################################################################
|
|
184
|
###########################################################################
|
|
178
|
|
|
185
|
|
|
179
|
# Classes and functions
|
|
186
|
# Classes and functions
|
|
180
|
|
|
187
|
|
|
181
|
def is_extension_module(filename):
|
|
188
|
def is_extension_module(filename):
|
|
182
|
"""Return whether the given filename is an extension module.
|
|
189
|
"""Return whether the given filename is an extension module.
|
|
183
|
|
|
190
|
|
|
184
|
This simply checks that the extension is either .so or .pyd.
|
|
191
|
This simply checks that the extension is either .so or .pyd.
|
|
185
|
"""
|
|
192
|
"""
|
|
186
|
return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
|
|
193
|
return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
|
|
187
|
|
|
194
|
|
|
188
|
|
|
195
|
|
|
189
|
class DocTestSkip(object):
|
|
196
|
class DocTestSkip(object):
|
|
190
|
"""Object wrapper for doctests to be skipped."""
|
|
197
|
"""Object wrapper for doctests to be skipped."""
|
|
191
|
|
|
198
|
|
|
192
|
ds_skip = """Doctest to skip.
|
|
199
|
ds_skip = """Doctest to skip.
|
|
193
|
>>> 1 #doctest: +SKIP
|
|
200
|
>>> 1 #doctest: +SKIP
|
|
194
|
"""
|
|
201
|
"""
|
|
195
|
|
|
202
|
|
|
196
|
def __init__(self,obj):
|
|
203
|
def __init__(self,obj):
|
|
197
|
self.obj = obj
|
|
204
|
self.obj = obj
|
|
198
|
|
|
205
|
|
|
199
|
def __getattribute__(self,key):
|
|
206
|
def __getattribute__(self,key):
|
|
200
|
if key == '__doc__':
|
|
207
|
if key == '__doc__':
|
|
201
|
return DocTestSkip.ds_skip
|
|
208
|
return DocTestSkip.ds_skip
|
|
202
|
else:
|
|
209
|
else:
|
|
203
|
return getattr(object.__getattribute__(self,'obj'),key)
|
|
210
|
return getattr(object.__getattribute__(self,'obj'),key)
|
|
204
|
|
|
211
|
|
|
205
|
# Modified version of the one in the stdlib, that fixes a python bug (doctests
|
|
212
|
# Modified version of the one in the stdlib, that fixes a python bug (doctests
|
|
206
|
# not found in extension modules, http://bugs.python.org/issue3158)
|
|
213
|
# not found in extension modules, http://bugs.python.org/issue3158)
|
|
207
|
class DocTestFinder(doctest.DocTestFinder):
|
|
214
|
class DocTestFinder(doctest.DocTestFinder):
|
|
208
|
|
|
215
|
|
|
209
|
def _from_module(self, module, object):
|
|
216
|
def _from_module(self, module, object):
|
|
210
|
"""
|
|
217
|
"""
|
|
211
|
Return true if the given object is defined in the given
|
|
218
|
Return true if the given object is defined in the given
|
|
212
|
module.
|
|
219
|
module.
|
|
213
|
"""
|
|
220
|
"""
|
|
214
|
if module is None:
|
|
221
|
if module is None:
|
|
215
|
return True
|
|
222
|
return True
|
|
216
|
elif inspect.isfunction(object):
|
|
223
|
elif inspect.isfunction(object):
|
|
217
|
return module.__dict__ is object.func_globals
|
|
224
|
return module.__dict__ is object.func_globals
|
|
218
|
elif inspect.isbuiltin(object):
|
|
225
|
elif inspect.isbuiltin(object):
|
|
219
|
return module.__name__ == object.__module__
|
|
226
|
return module.__name__ == object.__module__
|
|
220
|
elif inspect.isclass(object):
|
|
227
|
elif inspect.isclass(object):
|
|
221
|
return module.__name__ == object.__module__
|
|
228
|
return module.__name__ == object.__module__
|
|
222
|
elif inspect.ismethod(object):
|
|
229
|
elif inspect.ismethod(object):
|
|
223
|
# This one may be a bug in cython that fails to correctly set the
|
|
230
|
# This one may be a bug in cython that fails to correctly set the
|
|
224
|
# __module__ attribute of methods, but since the same error is easy
|
|
231
|
# __module__ attribute of methods, but since the same error is easy
|
|
225
|
# to make by extension code writers, having this safety in place
|
|
232
|
# to make by extension code writers, having this safety in place
|
|
226
|
# isn't such a bad idea
|
|
233
|
# isn't such a bad idea
|
|
227
|
return module.__name__ == object.im_class.__module__
|
|
234
|
return module.__name__ == object.im_class.__module__
|
|
228
|
elif inspect.getmodule(object) is not None:
|
|
235
|
elif inspect.getmodule(object) is not None:
|
|
229
|
return module is inspect.getmodule(object)
|
|
236
|
return module is inspect.getmodule(object)
|
|
230
|
elif hasattr(object, '__module__'):
|
|
237
|
elif hasattr(object, '__module__'):
|
|
231
|
return module.__name__ == object.__module__
|
|
238
|
return module.__name__ == object.__module__
|
|
232
|
elif isinstance(object, property):
|
|
239
|
elif isinstance(object, property):
|
|
233
|
return True # [XX] no way not be sure.
|
|
240
|
return True # [XX] no way not be sure.
|
|
234
|
else:
|
|
241
|
else:
|
|
235
|
raise ValueError("object must be a class or function")
|
|
242
|
raise ValueError("object must be a class or function")
|
|
236
|
|
|
243
|
|
|
237
|
def _find(self, tests, obj, name, module, source_lines, globs, seen):
|
|
244
|
def _find(self, tests, obj, name, module, source_lines, globs, seen):
|
|
238
|
"""
|
|
245
|
"""
|
|
239
|
Find tests for the given object and any contained objects, and
|
|
246
|
Find tests for the given object and any contained objects, and
|
|
240
|
add them to `tests`.
|
|
247
|
add them to `tests`.
|
|
241
|
"""
|
|
248
|
"""
|
|
242
|
|
|
249
|
|
|
243
|
if hasattr(obj,"skip_doctest"):
|
|
250
|
if hasattr(obj,"skip_doctest"):
|
|
244
|
#print 'SKIPPING DOCTEST FOR:',obj # dbg
|
|
251
|
#print 'SKIPPING DOCTEST FOR:',obj # dbg
|
|
245
|
obj = DocTestSkip(obj)
|
|
252
|
obj = DocTestSkip(obj)
|
|
246
|
|
|
253
|
|
|
247
|
doctest.DocTestFinder._find(self,tests, obj, name, module,
|
|
254
|
doctest.DocTestFinder._find(self,tests, obj, name, module,
|
|
248
|
source_lines, globs, seen)
|
|
255
|
source_lines, globs, seen)
|
|
249
|
|
|
256
|
|
|
250
|
# Below we re-run pieces of the above method with manual modifications,
|
|
257
|
# Below we re-run pieces of the above method with manual modifications,
|
|
251
|
# because the original code is buggy and fails to correctly identify
|
|
258
|
# because the original code is buggy and fails to correctly identify
|
|
252
|
# doctests in extension modules.
|
|
259
|
# doctests in extension modules.
|
|
253
|
|
|
260
|
|
|
254
|
# Local shorthands
|
|
261
|
# Local shorthands
|
|
255
|
from inspect import isroutine, isclass, ismodule
|
|
262
|
from inspect import isroutine, isclass, ismodule
|
|
256
|
|
|
263
|
|
|
257
|
# Look for tests in a module's contained objects.
|
|
264
|
# Look for tests in a module's contained objects.
|
|
258
|
if inspect.ismodule(obj) and self._recurse:
|
|
265
|
if inspect.ismodule(obj) and self._recurse:
|
|
259
|
for valname, val in obj.__dict__.items():
|
|
266
|
for valname, val in obj.__dict__.items():
|
|
260
|
valname1 = '%s.%s' % (name, valname)
|
|
267
|
valname1 = '%s.%s' % (name, valname)
|
|
261
|
if ( (isroutine(val) or isclass(val))
|
|
268
|
if ( (isroutine(val) or isclass(val))
|
|
262
|
and self._from_module(module, val) ):
|
|
269
|
and self._from_module(module, val) ):
|
|
263
|
|
|
270
|
|
|
264
|
self._find(tests, val, valname1, module, source_lines,
|
|
271
|
self._find(tests, val, valname1, module, source_lines,
|
|
265
|
globs, seen)
|
|
272
|
globs, seen)
|
|
266
|
|
|
273
|
|
|
267
|
# Look for tests in a class's contained objects.
|
|
274
|
# Look for tests in a class's contained objects.
|
|
268
|
if inspect.isclass(obj) and self._recurse:
|
|
275
|
if inspect.isclass(obj) and self._recurse:
|
|
269
|
#print 'RECURSE into class:',obj # dbg
|
|
276
|
#print 'RECURSE into class:',obj # dbg
|
|
270
|
for valname, val in obj.__dict__.items():
|
|
277
|
for valname, val in obj.__dict__.items():
|
|
271
|
# Special handling for staticmethod/classmethod.
|
|
278
|
# Special handling for staticmethod/classmethod.
|
|
272
|
if isinstance(val, staticmethod):
|
|
279
|
if isinstance(val, staticmethod):
|
|
273
|
val = getattr(obj, valname)
|
|
280
|
val = getattr(obj, valname)
|
|
274
|
if isinstance(val, classmethod):
|
|
281
|
if isinstance(val, classmethod):
|
|
275
|
val = getattr(obj, valname).im_func
|
|
282
|
val = getattr(obj, valname).im_func
|
|
276
|
|
|
283
|
|
|
277
|
# Recurse to methods, properties, and nested classes.
|
|
284
|
# Recurse to methods, properties, and nested classes.
|
|
278
|
if ((inspect.isfunction(val) or inspect.isclass(val) or
|
|
285
|
if ((inspect.isfunction(val) or inspect.isclass(val) or
|
|
279
|
inspect.ismethod(val) or
|
|
286
|
inspect.ismethod(val) or
|
|
280
|
isinstance(val, property)) and
|
|
287
|
isinstance(val, property)) and
|
|
281
|
self._from_module(module, val)):
|
|
288
|
self._from_module(module, val)):
|
|
282
|
valname = '%s.%s' % (name, valname)
|
|
289
|
valname = '%s.%s' % (name, valname)
|
|
283
|
self._find(tests, val, valname, module, source_lines,
|
|
290
|
self._find(tests, val, valname, module, source_lines,
|
|
284
|
globs, seen)
|
|
291
|
globs, seen)
|
|
285
|
|
|
292
|
|
|
286
|
|
|
293
|
|
|
287
|
class IPDoctestOutputChecker(doctest.OutputChecker):
|
|
294
|
class IPDoctestOutputChecker(doctest.OutputChecker):
|
|
288
|
"""Second-chance checker with support for random tests.
|
|
295
|
"""Second-chance checker with support for random tests.
|
|
289
|
|
|
296
|
|
|
290
|
If the default comparison doesn't pass, this checker looks in the expected
|
|
297
|
If the default comparison doesn't pass, this checker looks in the expected
|
|
291
|
output string for flags that tell us to ignore the output.
|
|
298
|
output string for flags that tell us to ignore the output.
|
|
292
|
"""
|
|
299
|
"""
|
|
293
|
|
|
300
|
|
|
294
|
random_re = re.compile(r'#\s*random\s+')
|
|
301
|
random_re = re.compile(r'#\s*random\s+')
|
|
295
|
|
|
302
|
|
|
296
|
def check_output(self, want, got, optionflags):
|
|
303
|
def check_output(self, want, got, optionflags):
|
|
297
|
"""Check output, accepting special markers embedded in the output.
|
|
304
|
"""Check output, accepting special markers embedded in the output.
|
|
298
|
|
|
305
|
|
|
299
|
If the output didn't pass the default validation but the special string
|
|
306
|
If the output didn't pass the default validation but the special string
|
|
300
|
'#random' is included, we accept it."""
|
|
307
|
'#random' is included, we accept it."""
|
|
301
|
|
|
308
|
|
|
302
|
# Let the original tester verify first, in case people have valid tests
|
|
309
|
# Let the original tester verify first, in case people have valid tests
|
|
303
|
# that happen to have a comment saying '#random' embedded in.
|
|
310
|
# that happen to have a comment saying '#random' embedded in.
|
|
304
|
ret = doctest.OutputChecker.check_output(self, want, got,
|
|
311
|
ret = doctest.OutputChecker.check_output(self, want, got,
|
|
305
|
optionflags)
|
|
312
|
optionflags)
|
|
306
|
if not ret and self.random_re.search(want):
|
|
313
|
if not ret and self.random_re.search(want):
|
|
307
|
#print >> sys.stderr, 'RANDOM OK:',want # dbg
|
|
314
|
#print >> sys.stderr, 'RANDOM OK:',want # dbg
|
|
308
|
return True
|
|
315
|
return True
|
|
309
|
|
|
316
|
|
|
310
|
return ret
|
|
317
|
return ret
|
|
311
|
|
|
318
|
|
|
312
|
|
|
319
|
|
|
313
|
class DocTestCase(doctests.DocTestCase):
|
|
320
|
class DocTestCase(doctests.DocTestCase):
|
|
314
|
"""Proxy for DocTestCase: provides an address() method that
|
|
321
|
"""Proxy for DocTestCase: provides an address() method that
|
|
315
|
returns the correct address for the doctest case. Otherwise
|
|
322
|
returns the correct address for the doctest case. Otherwise
|
|
316
|
acts as a proxy to the test case. To provide hints for address(),
|
|
323
|
acts as a proxy to the test case. To provide hints for address(),
|
|
317
|
an obj may also be passed -- this will be used as the test object
|
|
324
|
an obj may also be passed -- this will be used as the test object
|
|
318
|
for purposes of determining the test address, if it is provided.
|
|
325
|
for purposes of determining the test address, if it is provided.
|
|
319
|
"""
|
|
326
|
"""
|
|
320
|
|
|
327
|
|
|
321
|
# Note: this method was taken from numpy's nosetester module.
|
|
328
|
# Note: this method was taken from numpy's nosetester module.
|
|
322
|
|
|
329
|
|
|
323
|
# Subclass nose.plugins.doctests.DocTestCase to work around a bug in
|
|
330
|
# Subclass nose.plugins.doctests.DocTestCase to work around a bug in
|
|
324
|
# its constructor that blocks non-default arguments from being passed
|
|
331
|
# its constructor that blocks non-default arguments from being passed
|
|
325
|
# down into doctest.DocTestCase
|
|
332
|
# down into doctest.DocTestCase
|
|
326
|
|
|
333
|
|
|
327
|
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
|
|
334
|
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
|
|
328
|
checker=None, obj=None, result_var='_'):
|
|
335
|
checker=None, obj=None, result_var='_'):
|
|
329
|
self._result_var = result_var
|
|
336
|
self._result_var = result_var
|
|
330
|
doctests.DocTestCase.__init__(self, test,
|
|
337
|
doctests.DocTestCase.__init__(self, test,
|
|
331
|
optionflags=optionflags,
|
|
338
|
optionflags=optionflags,
|
|
332
|
setUp=setUp, tearDown=tearDown,
|
|
339
|
setUp=setUp, tearDown=tearDown,
|
|
333
|
checker=checker)
|
|
340
|
checker=checker)
|
|
334
|
# Now we must actually copy the original constructor from the stdlib
|
|
341
|
# Now we must actually copy the original constructor from the stdlib
|
|
335
|
# doctest class, because we can't call it directly and a bug in nose
|
|
342
|
# doctest class, because we can't call it directly and a bug in nose
|
|
336
|
# means it never gets passed the right arguments.
|
|
343
|
# means it never gets passed the right arguments.
|
|
337
|
|
|
344
|
|
|
338
|
self._dt_optionflags = optionflags
|
|
345
|
self._dt_optionflags = optionflags
|
|
339
|
self._dt_checker = checker
|
|
346
|
self._dt_checker = checker
|
|
340
|
self._dt_test = test
|
|
347
|
self._dt_test = test
|
|
341
|
self._dt_setUp = setUp
|
|
348
|
self._dt_setUp = setUp
|
|
342
|
self._dt_tearDown = tearDown
|
|
349
|
self._dt_tearDown = tearDown
|
|
343
|
|
|
350
|
|
|
344
|
# XXX - store this runner once in the object!
|
|
351
|
# XXX - store this runner once in the object!
|
|
345
|
runner = IPDocTestRunner(optionflags=optionflags,
|
|
352
|
runner = IPDocTestRunner(optionflags=optionflags,
|
|
346
|
checker=checker, verbose=False)
|
|
353
|
checker=checker, verbose=False)
|
|
347
|
self._dt_runner = runner
|
|
354
|
self._dt_runner = runner
|
|
348
|
|
|
355
|
|
|
349
|
|
|
356
|
|
|
350
|
# Each doctest should remember what directory it was loaded from...
|
|
357
|
# Each doctest should remember what directory it was loaded from...
|
|
351
|
self._ori_dir = os.getcwd()
|
|
358
|
self._ori_dir = os.getcwd()
|
|
352
|
|
|
359
|
|
|
353
|
# Modified runTest from the default stdlib
|
|
360
|
# Modified runTest from the default stdlib
|
|
354
|
def runTest(self):
|
|
361
|
def runTest(self):
|
|
355
|
test = self._dt_test
|
|
362
|
test = self._dt_test
|
|
356
|
runner = self._dt_runner
|
|
363
|
runner = self._dt_runner
|
|
357
|
|
|
364
|
|
|
358
|
old = sys.stdout
|
|
365
|
old = sys.stdout
|
|
359
|
new = StringIO()
|
|
366
|
new = StringIO()
|
|
360
|
optionflags = self._dt_optionflags
|
|
367
|
optionflags = self._dt_optionflags
|
|
361
|
|
|
368
|
|
|
362
|
if not (optionflags & REPORTING_FLAGS):
|
|
369
|
if not (optionflags & REPORTING_FLAGS):
|
|
363
|
# The option flags don't include any reporting flags,
|
|
370
|
# The option flags don't include any reporting flags,
|
|
364
|
# so add the default reporting flags
|
|
371
|
# so add the default reporting flags
|
|
365
|
optionflags |= _unittest_reportflags
|
|
372
|
optionflags |= _unittest_reportflags
|
|
366
|
|
|
373
|
|
|
367
|
try:
|
|
374
|
try:
|
|
368
|
# Save our current directory and switch out to the one where the
|
|
375
|
# Save our current directory and switch out to the one where the
|
|
369
|
# test was originally created, in case another doctest did a
|
|
376
|
# test was originally created, in case another doctest did a
|
|
370
|
# directory change. We'll restore this in the finally clause.
|
|
377
|
# directory change. We'll restore this in the finally clause.
|
|
371
|
curdir = os.getcwd()
|
|
378
|
curdir = os.getcwd()
|
|
372
|
os.chdir(self._ori_dir)
|
|
379
|
os.chdir(self._ori_dir)
|
|
373
|
|
|
380
|
|
|
374
|
runner.DIVIDER = "-"*70
|
|
381
|
runner.DIVIDER = "-"*70
|
|
375
|
failures, tries = runner.run(test,out=new.write,
|
|
382
|
failures, tries = runner.run(test,out=new.write,
|
|
376
|
clear_globs=False)
|
|
383
|
clear_globs=False)
|
|
377
|
finally:
|
|
384
|
finally:
|
|
378
|
sys.stdout = old
|
|
385
|
sys.stdout = old
|
|
379
|
os.chdir(curdir)
|
|
386
|
os.chdir(curdir)
|
|
380
|
|
|
387
|
|
|
381
|
if failures:
|
|
388
|
if failures:
|
|
382
|
raise self.failureException(self.format_failure(new.getvalue()))
|
|
389
|
raise self.failureException(self.format_failure(new.getvalue()))
|
|
383
|
|
|
390
|
|
|
384
|
def setUp(self):
|
|
391
|
def setUp(self):
|
|
385
|
"""Modified test setup that syncs with ipython namespace"""
|
|
392
|
"""Modified test setup that syncs with ipython namespace"""
|
|
386
|
|
|
393
|
|
|
387
|
if isinstance(self._dt_test.examples[0],IPExample):
|
|
394
|
if isinstance(self._dt_test.examples[0],IPExample):
|
|
388
|
# for IPython examples *only*, we swap the globals with the ipython
|
|
395
|
# for IPython examples *only*, we swap the globals with the ipython
|
|
389
|
# namespace, after updating it with the globals (which doctest
|
|
396
|
# namespace, after updating it with the globals (which doctest
|
|
390
|
# fills with the necessary info from the module being tested).
|
|
397
|
# fills with the necessary info from the module being tested).
|
|
391
|
_ip.IP.user_ns.update(self._dt_test.globs)
|
|
398
|
_ip.IP.user_ns.update(self._dt_test.globs)
|
|
392
|
self._dt_test.globs = _ip.IP.user_ns
|
|
399
|
self._dt_test.globs = _ip.IP.user_ns
|
|
393
|
|
|
400
|
|
|
394
|
doctests.DocTestCase.setUp(self)
|
|
401
|
doctests.DocTestCase.setUp(self)
|
|
395
|
|
|
402
|
|
|
396
|
|
|
403
|
|
|
397
|
# A simple subclassing of the original with a different class name, so we can
|
|
404
|
# A simple subclassing of the original with a different class name, so we can
|
|
398
|
# distinguish and treat differently IPython examples from pure python ones.
|
|
405
|
# distinguish and treat differently IPython examples from pure python ones.
|
|
399
|
class IPExample(doctest.Example): pass
|
|
406
|
class IPExample(doctest.Example): pass
|
|
400
|
|
|
407
|
|
|
401
|
|
|
408
|
|
|
402
|
class IPExternalExample(doctest.Example):
|
|
409
|
class IPExternalExample(doctest.Example):
|
|
403
|
"""Doctest examples to be run in an external process."""
|
|
410
|
"""Doctest examples to be run in an external process."""
|
|
404
|
|
|
411
|
|
|
405
|
def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
|
|
412
|
def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
|
|
406
|
options=None):
|
|
413
|
options=None):
|
|
407
|
# Parent constructor
|
|
414
|
# Parent constructor
|
|
408
|
doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
|
|
415
|
doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
|
|
409
|
|
|
416
|
|
|
410
|
# An EXTRA newline is needed to prevent pexpect hangs
|
|
417
|
# An EXTRA newline is needed to prevent pexpect hangs
|
|
411
|
self.source += '\n'
|
|
418
|
self.source += '\n'
|
|
412
|
|
|
419
|
|
|
413
|
|
|
420
|
|
|
414
|
class IPDocTestParser(doctest.DocTestParser):
|
|
421
|
class IPDocTestParser(doctest.DocTestParser):
|
|
415
|
"""
|
|
422
|
"""
|
|
416
|
A class used to parse strings containing doctest examples.
|
|
423
|
A class used to parse strings containing doctest examples.
|
|
417
|
|
|
424
|
|
|
418
|
Note: This is a version modified to properly recognize IPython input and
|
|
425
|
Note: This is a version modified to properly recognize IPython input and
|
|
419
|
convert any IPython examples into valid Python ones.
|
|
426
|
convert any IPython examples into valid Python ones.
|
|
420
|
"""
|
|
427
|
"""
|
|
421
|
# This regular expression is used to find doctest examples in a
|
|
428
|
# This regular expression is used to find doctest examples in a
|
|
422
|
# string. It defines three groups: `source` is the source code
|
|
429
|
# string. It defines three groups: `source` is the source code
|
|
423
|
# (including leading indentation and prompts); `indent` is the
|
|
430
|
# (including leading indentation and prompts); `indent` is the
|
|
424
|
# indentation of the first (PS1) line of the source code; and
|
|
431
|
# indentation of the first (PS1) line of the source code; and
|
|
425
|
# `want` is the expected output (including leading indentation).
|
|
432
|
# `want` is the expected output (including leading indentation).
|
|
426
|
|
|
433
|
|
|
427
|
# Classic Python prompts or default IPython ones
|
|
434
|
# Classic Python prompts or default IPython ones
|
|
428
|
_PS1_PY = r'>>>'
|
|
435
|
_PS1_PY = r'>>>'
|
|
429
|
_PS2_PY = r'\.\.\.'
|
|
436
|
_PS2_PY = r'\.\.\.'
|
|
430
|
|
|
437
|
|
|
431
|
_PS1_IP = r'In\ \[\d+\]:'
|
|
438
|
_PS1_IP = r'In\ \[\d+\]:'
|
|
432
|
_PS2_IP = r'\ \ \ \.\.\.+:'
|
|
439
|
_PS2_IP = r'\ \ \ \.\.\.+:'
|
|
433
|
|
|
440
|
|
|
434
|
_RE_TPL = r'''
|
|
441
|
_RE_TPL = r'''
|
|
435
|
# Source consists of a PS1 line followed by zero or more PS2 lines.
|
|
442
|
# Source consists of a PS1 line followed by zero or more PS2 lines.
|
|
436
|
(?P<source>
|
|
443
|
(?P<source>
|
|
437
|
(?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
|
|
444
|
(?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
|
|
438
|
(?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
|
|
445
|
(?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
|
|
439
|
\n? # a newline
|
|
446
|
\n? # a newline
|
|
440
|
# Want consists of any non-blank lines that do not start with PS1.
|
|
447
|
# Want consists of any non-blank lines that do not start with PS1.
|
|
441
|
(?P<want> (?:(?![ ]*$) # Not a blank line
|
|
448
|
(?P<want> (?:(?![ ]*$) # Not a blank line
|
|
442
|
(?![ ]*%s) # Not a line starting with PS1
|
|
449
|
(?![ ]*%s) # Not a line starting with PS1
|
|
443
|
(?![ ]*%s) # Not a line starting with PS2
|
|
450
|
(?![ ]*%s) # Not a line starting with PS2
|
|
444
|
.*$\n? # But any other line
|
|
451
|
.*$\n? # But any other line
|
|
445
|
)*)
|
|
452
|
)*)
|
|
446
|
'''
|
|
453
|
'''
|
|
447
|
|
|
454
|
|
|
448
|
_EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
|
|
455
|
_EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
|
|
449
|
re.MULTILINE | re.VERBOSE)
|
|
456
|
re.MULTILINE | re.VERBOSE)
|
|
450
|
|
|
457
|
|
|
451
|
_EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
|
|
458
|
_EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
|
|
452
|
re.MULTILINE | re.VERBOSE)
|
|
459
|
re.MULTILINE | re.VERBOSE)
|
|
453
|
|
|
460
|
|
|
454
|
# Mark a test as being fully random. In this case, we simply append the
|
|
461
|
# Mark a test as being fully random. In this case, we simply append the
|
|
455
|
# random marker ('#random') to each individual example's output. This way
|
|
462
|
# random marker ('#random') to each individual example's output. This way
|
|
456
|
# we don't need to modify any other code.
|
|
463
|
# we don't need to modify any other code.
|
|
457
|
_RANDOM_TEST = re.compile(r'#\s*all-random\s+')
|
|
464
|
_RANDOM_TEST = re.compile(r'#\s*all-random\s+')
|
|
458
|
|
|
465
|
|
|
459
|
# Mark tests to be executed in an external process - currently unsupported.
|
|
466
|
# Mark tests to be executed in an external process - currently unsupported.
|
|
460
|
_EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
|
|
467
|
_EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
|
|
461
|
|
|
468
|
|
|
462
|
def ip2py(self,source):
|
|
469
|
def ip2py(self,source):
|
|
463
|
"""Convert input IPython source into valid Python."""
|
|
470
|
"""Convert input IPython source into valid Python."""
|
|
464
|
out = []
|
|
471
|
out = []
|
|
465
|
newline = out.append
|
|
472
|
newline = out.append
|
|
466
|
#print 'IPSRC:\n',source,'\n###' # dbg
|
|
473
|
#print 'IPSRC:\n',source,'\n###' # dbg
|
|
467
|
# The input source must be first stripped of all bracketing whitespace
|
|
474
|
# The input source must be first stripped of all bracketing whitespace
|
|
468
|
# and turned into lines, so it looks to the parser like regular user
|
|
475
|
# and turned into lines, so it looks to the parser like regular user
|
|
469
|
# input
|
|
476
|
# input
|
|
470
|
for lnum,line in enumerate(source.strip().splitlines()):
|
|
477
|
for lnum,line in enumerate(source.strip().splitlines()):
|
|
471
|
newline(_ip.IP.prefilter(line,lnum>0))
|
|
478
|
newline(_ip.IP.prefilter(line,lnum>0))
|
|
472
|
newline('') # ensure a closing newline, needed by doctest
|
|
479
|
newline('') # ensure a closing newline, needed by doctest
|
|
473
|
#print "PYSRC:", '\n'.join(out) # dbg
|
|
480
|
#print "PYSRC:", '\n'.join(out) # dbg
|
|
474
|
return '\n'.join(out)
|
|
481
|
return '\n'.join(out)
|
|
475
|
|
|
482
|
|
|
476
|
def parse(self, string, name='<string>'):
|
|
483
|
def parse(self, string, name='<string>'):
|
|
477
|
"""
|
|
484
|
"""
|
|
478
|
Divide the given string into examples and intervening text,
|
|
485
|
Divide the given string into examples and intervening text,
|
|
479
|
and return them as a list of alternating Examples and strings.
|
|
486
|
and return them as a list of alternating Examples and strings.
|
|
480
|
Line numbers for the Examples are 0-based. The optional
|
|
487
|
Line numbers for the Examples are 0-based. The optional
|
|
481
|
argument `name` is a name identifying this string, and is only
|
|
488
|
argument `name` is a name identifying this string, and is only
|
|
482
|
used for error messages.
|
|
489
|
used for error messages.
|
|
483
|
"""
|
|
490
|
"""
|
|
484
|
|
|
491
|
|
|
485
|
#print 'Parse string:\n',string # dbg
|
|
492
|
#print 'Parse string:\n',string # dbg
|
|
486
|
|
|
493
|
|
|
487
|
string = string.expandtabs()
|
|
494
|
string = string.expandtabs()
|
|
488
|
# If all lines begin with the same indentation, then strip it.
|
|
495
|
# If all lines begin with the same indentation, then strip it.
|
|
489
|
min_indent = self._min_indent(string)
|
|
496
|
min_indent = self._min_indent(string)
|
|
490
|
if min_indent > 0:
|
|
497
|
if min_indent > 0:
|
|
491
|
string = '\n'.join([l[min_indent:] for l in string.split('\n')])
|
|
498
|
string = '\n'.join([l[min_indent:] for l in string.split('\n')])
|
|
492
|
|
|
499
|
|
|
493
|
output = []
|
|
500
|
output = []
|
|
494
|
charno, lineno = 0, 0
|
|
501
|
charno, lineno = 0, 0
|
|
495
|
|
|
502
|
|
|
496
|
# We make 'all random' tests by adding the '# random' mark to every
|
|
503
|
# We make 'all random' tests by adding the '# random' mark to every
|
|
497
|
# block of output in the test.
|
|
504
|
# block of output in the test.
|
|
498
|
if self._RANDOM_TEST.search(string):
|
|
505
|
if self._RANDOM_TEST.search(string):
|
|
499
|
random_marker = '\n# random'
|
|
506
|
random_marker = '\n# random'
|
|
500
|
else:
|
|
507
|
else:
|
|
501
|
random_marker = ''
|
|
508
|
random_marker = ''
|
|
502
|
|
|
509
|
|
|
503
|
# Whether to convert the input from ipython to python syntax
|
|
510
|
# Whether to convert the input from ipython to python syntax
|
|
504
|
ip2py = False
|
|
511
|
ip2py = False
|
|
505
|
# Find all doctest examples in the string. First, try them as Python
|
|
512
|
# Find all doctest examples in the string. First, try them as Python
|
|
506
|
# examples, then as IPython ones
|
|
513
|
# examples, then as IPython ones
|
|
507
|
terms = list(self._EXAMPLE_RE_PY.finditer(string))
|
|
514
|
terms = list(self._EXAMPLE_RE_PY.finditer(string))
|
|
508
|
if terms:
|
|
515
|
if terms:
|
|
509
|
# Normal Python example
|
|
516
|
# Normal Python example
|
|
510
|
#print '-'*70 # dbg
|
|
517
|
#print '-'*70 # dbg
|
|
511
|
#print 'PyExample, Source:\n',string # dbg
|
|
518
|
#print 'PyExample, Source:\n',string # dbg
|
|
512
|
#print '-'*70 # dbg
|
|
519
|
#print '-'*70 # dbg
|
|
513
|
Example = doctest.Example
|
|
520
|
Example = doctest.Example
|
|
514
|
else:
|
|
521
|
else:
|
|
515
|
# It's an ipython example. Note that IPExamples are run
|
|
522
|
# It's an ipython example. Note that IPExamples are run
|
|
516
|
# in-process, so their syntax must be turned into valid python.
|
|
523
|
# in-process, so their syntax must be turned into valid python.
|
|
517
|
# IPExternalExamples are run out-of-process (via pexpect) so they
|
|
524
|
# IPExternalExamples are run out-of-process (via pexpect) so they
|
|
518
|
# don't need any filtering (a real ipython will be executing them).
|
|
525
|
# don't need any filtering (a real ipython will be executing them).
|
|
519
|
terms = list(self._EXAMPLE_RE_IP.finditer(string))
|
|
526
|
terms = list(self._EXAMPLE_RE_IP.finditer(string))
|
|
520
|
if self._EXTERNAL_IP.search(string):
|
|
527
|
if self._EXTERNAL_IP.search(string):
|
|
521
|
#print '-'*70 # dbg
|
|
528
|
#print '-'*70 # dbg
|
|
522
|
#print 'IPExternalExample, Source:\n',string # dbg
|
|
529
|
#print 'IPExternalExample, Source:\n',string # dbg
|
|
523
|
#print '-'*70 # dbg
|
|
530
|
#print '-'*70 # dbg
|
|
524
|
Example = IPExternalExample
|
|
531
|
Example = IPExternalExample
|
|
525
|
else:
|
|
532
|
else:
|
|
526
|
#print '-'*70 # dbg
|
|
533
|
#print '-'*70 # dbg
|
|
527
|
#print 'IPExample, Source:\n',string # dbg
|
|
534
|
#print 'IPExample, Source:\n',string # dbg
|
|
528
|
#print '-'*70 # dbg
|
|
535
|
#print '-'*70 # dbg
|
|
529
|
Example = IPExample
|
|
536
|
Example = IPExample
|
|
530
|
ip2py = True
|
|
537
|
ip2py = True
|
|
531
|
|
|
538
|
|
|
532
|
for m in terms:
|
|
539
|
for m in terms:
|
|
533
|
# Add the pre-example text to `output`.
|
|
540
|
# Add the pre-example text to `output`.
|
|
534
|
output.append(string[charno:m.start()])
|
|
541
|
output.append(string[charno:m.start()])
|
|
535
|
# Update lineno (lines before this example)
|
|
542
|
# Update lineno (lines before this example)
|
|
536
|
lineno += string.count('\n', charno, m.start())
|
|
543
|
lineno += string.count('\n', charno, m.start())
|
|
537
|
# Extract info from the regexp match.
|
|
544
|
# Extract info from the regexp match.
|
|
538
|
(source, options, want, exc_msg) = \
|
|
545
|
(source, options, want, exc_msg) = \
|
|
539
|
self._parse_example(m, name, lineno,ip2py)
|
|
546
|
self._parse_example(m, name, lineno,ip2py)
|
|
540
|
|
|
547
|
|
|
541
|
# Append the random-output marker (it defaults to empty in most
|
|
548
|
# Append the random-output marker (it defaults to empty in most
|
|
542
|
# cases, it's only non-empty for 'all-random' tests):
|
|
549
|
# cases, it's only non-empty for 'all-random' tests):
|
|
543
|
want += random_marker
|
|
550
|
want += random_marker
|
|
544
|
|
|
551
|
|
|
545
|
if Example is IPExternalExample:
|
|
552
|
if Example is IPExternalExample:
|
|
546
|
options[doctest.NORMALIZE_WHITESPACE] = True
|
|
553
|
options[doctest.NORMALIZE_WHITESPACE] = True
|
|
547
|
want += '\n'
|
|
554
|
want += '\n'
|
|
548
|
|
|
555
|
|
|
549
|
# Create an Example, and add it to the list.
|
|
556
|
# Create an Example, and add it to the list.
|
|
550
|
if not self._IS_BLANK_OR_COMMENT(source):
|
|
557
|
if not self._IS_BLANK_OR_COMMENT(source):
|
|
551
|
output.append(Example(source, want, exc_msg,
|
|
558
|
output.append(Example(source, want, exc_msg,
|
|
552
|
lineno=lineno,
|
|
559
|
lineno=lineno,
|
|
553
|
indent=min_indent+len(m.group('indent')),
|
|
560
|
indent=min_indent+len(m.group('indent')),
|
|
554
|
options=options))
|
|
561
|
options=options))
|
|
555
|
# Update lineno (lines inside this example)
|
|
562
|
# Update lineno (lines inside this example)
|
|
556
|
lineno += string.count('\n', m.start(), m.end())
|
|
563
|
lineno += string.count('\n', m.start(), m.end())
|
|
557
|
# Update charno.
|
|
564
|
# Update charno.
|
|
558
|
charno = m.end()
|
|
565
|
charno = m.end()
|
|
559
|
# Add any remaining post-example text to `output`.
|
|
566
|
# Add any remaining post-example text to `output`.
|
|
560
|
output.append(string[charno:])
|
|
567
|
output.append(string[charno:])
|
|
561
|
return output
|
|
568
|
return output
|
|
562
|
|
|
569
|
|
|
563
|
def _parse_example(self, m, name, lineno,ip2py=False):
|
|
570
|
def _parse_example(self, m, name, lineno,ip2py=False):
|
|
564
|
"""
|
|
571
|
"""
|
|
565
|
Given a regular expression match from `_EXAMPLE_RE` (`m`),
|
|
572
|
Given a regular expression match from `_EXAMPLE_RE` (`m`),
|
|
566
|
return a pair `(source, want)`, where `source` is the matched
|
|
573
|
return a pair `(source, want)`, where `source` is the matched
|
|
567
|
example's source code (with prompts and indentation stripped);
|
|
574
|
example's source code (with prompts and indentation stripped);
|
|
568
|
and `want` is the example's expected output (with indentation
|
|
575
|
and `want` is the example's expected output (with indentation
|
|
569
|
stripped).
|
|
576
|
stripped).
|
|
570
|
|
|
577
|
|
|
571
|
`name` is the string's name, and `lineno` is the line number
|
|
578
|
`name` is the string's name, and `lineno` is the line number
|
|
572
|
where the example starts; both are used for error messages.
|
|
579
|
where the example starts; both are used for error messages.
|
|
573
|
|
|
580
|
|
|
574
|
Optional:
|
|
581
|
Optional:
|
|
575
|
`ip2py`: if true, filter the input via IPython to convert the syntax
|
|
582
|
`ip2py`: if true, filter the input via IPython to convert the syntax
|
|
576
|
into valid python.
|
|
583
|
into valid python.
|
|
577
|
"""
|
|
584
|
"""
|
|
578
|
|
|
585
|
|
|
579
|
# Get the example's indentation level.
|
|
586
|
# Get the example's indentation level.
|
|
580
|
indent = len(m.group('indent'))
|
|
587
|
indent = len(m.group('indent'))
|
|
581
|
|
|
588
|
|
|
582
|
# Divide source into lines; check that they're properly
|
|
589
|
# Divide source into lines; check that they're properly
|
|
583
|
# indented; and then strip their indentation & prompts.
|
|
590
|
# indented; and then strip their indentation & prompts.
|
|
584
|
source_lines = m.group('source').split('\n')
|
|
591
|
source_lines = m.group('source').split('\n')
|
|
585
|
|
|
592
|
|
|
586
|
# We're using variable-length input prompts
|
|
593
|
# We're using variable-length input prompts
|
|
587
|
ps1 = m.group('ps1')
|
|
594
|
ps1 = m.group('ps1')
|
|
588
|
ps2 = m.group('ps2')
|
|
595
|
ps2 = m.group('ps2')
|
|
589
|
ps1_len = len(ps1)
|
|
596
|
ps1_len = len(ps1)
|
|
590
|
|
|
597
|
|
|
591
|
self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
|
|
598
|
self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
|
|
592
|
if ps2:
|
|
599
|
if ps2:
|
|
593
|
self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
|
|
600
|
self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
|
|
594
|
|
|
601
|
|
|
595
|
source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
|
|
602
|
source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
|
|
596
|
|
|
603
|
|
|
597
|
if ip2py:
|
|
604
|
if ip2py:
|
|
598
|
# Convert source input from IPython into valid Python syntax
|
|
605
|
# Convert source input from IPython into valid Python syntax
|
|
599
|
source = self.ip2py(source)
|
|
606
|
source = self.ip2py(source)
|
|
600
|
|
|
607
|
|
|
601
|
# Divide want into lines; check that it's properly indented; and
|
|
608
|
# Divide want into lines; check that it's properly indented; and
|
|
602
|
# then strip the indentation. Spaces before the last newline should
|
|
609
|
# then strip the indentation. Spaces before the last newline should
|
|
603
|
# be preserved, so plain rstrip() isn't good enough.
|
|
610
|
# be preserved, so plain rstrip() isn't good enough.
|
|
604
|
want = m.group('want')
|
|
611
|
want = m.group('want')
|
|
605
|
want_lines = want.split('\n')
|
|
612
|
want_lines = want.split('\n')
|
|
606
|
if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
|
|
613
|
if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
|
|
607
|
del want_lines[-1] # forget final newline & spaces after it
|
|
614
|
del want_lines[-1] # forget final newline & spaces after it
|
|
608
|
self._check_prefix(want_lines, ' '*indent, name,
|
|
615
|
self._check_prefix(want_lines, ' '*indent, name,
|
|
609
|
lineno + len(source_lines))
|
|
616
|
lineno + len(source_lines))
|
|
610
|
|
|
617
|
|
|
611
|
# Remove ipython output prompt that might be present in the first line
|
|
618
|
# Remove ipython output prompt that might be present in the first line
|
|
612
|
want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
|
|
619
|
want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
|
|
613
|
|
|
620
|
|
|
614
|
want = '\n'.join([wl[indent:] for wl in want_lines])
|
|
621
|
want = '\n'.join([wl[indent:] for wl in want_lines])
|
|
615
|
|
|
622
|
|
|
616
|
# If `want` contains a traceback message, then extract it.
|
|
623
|
# If `want` contains a traceback message, then extract it.
|
|
617
|
m = self._EXCEPTION_RE.match(want)
|
|
624
|
m = self._EXCEPTION_RE.match(want)
|
|
618
|
if m:
|
|
625
|
if m:
|
|
619
|
exc_msg = m.group('msg')
|
|
626
|
exc_msg = m.group('msg')
|
|
620
|
else:
|
|
627
|
else:
|
|
621
|
exc_msg = None
|
|
628
|
exc_msg = None
|
|
622
|
|
|
629
|
|
|
623
|
# Extract options from the source.
|
|
630
|
# Extract options from the source.
|
|
624
|
options = self._find_options(source, name, lineno)
|
|
631
|
options = self._find_options(source, name, lineno)
|
|
625
|
|
|
632
|
|
|
626
|
return source, options, want, exc_msg
|
|
633
|
return source, options, want, exc_msg
|
|
627
|
|
|
634
|
|
|
628
|
def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
|
|
635
|
def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
|
|
629
|
"""
|
|
636
|
"""
|
|
630
|
Given the lines of a source string (including prompts and
|
|
637
|
Given the lines of a source string (including prompts and
|
|
631
|
leading indentation), check to make sure that every prompt is
|
|
638
|
leading indentation), check to make sure that every prompt is
|
|
632
|
followed by a space character. If any line is not followed by
|
|
639
|
followed by a space character. If any line is not followed by
|
|
633
|
a space character, then raise ValueError.
|
|
640
|
a space character, then raise ValueError.
|
|
634
|
|
|
641
|
|
|
635
|
Note: IPython-modified version which takes the input prompt length as a
|
|
642
|
Note: IPython-modified version which takes the input prompt length as a
|
|
636
|
parameter, so that prompts of variable length can be dealt with.
|
|
643
|
parameter, so that prompts of variable length can be dealt with.
|
|
637
|
"""
|
|
644
|
"""
|
|
638
|
space_idx = indent+ps1_len
|
|
645
|
space_idx = indent+ps1_len
|
|
639
|
min_len = space_idx+1
|
|
646
|
min_len = space_idx+1
|
|
640
|
for i, line in enumerate(lines):
|
|
647
|
for i, line in enumerate(lines):
|
|
641
|
if len(line) >= min_len and line[space_idx] != ' ':
|
|
648
|
if len(line) >= min_len and line[space_idx] != ' ':
|
|
642
|
raise ValueError('line %r of the docstring for %s '
|
|
649
|
raise ValueError('line %r of the docstring for %s '
|
|
643
|
'lacks blank after %s: %r' %
|
|
650
|
'lacks blank after %s: %r' %
|
|
644
|
(lineno+i+1, name,
|
|
651
|
(lineno+i+1, name,
|
|
645
|
line[indent:space_idx], line))
|
|
652
|
line[indent:space_idx], line))
|
|
646
|
|
|
653
|
|
|
647
|
|
|
654
|
|
|
648
|
SKIP = doctest.register_optionflag('SKIP')
|
|
655
|
SKIP = doctest.register_optionflag('SKIP')
|
|
649
|
|
|
656
|
|
|
650
|
|
|
657
|
|
|
651
|
class IPDocTestRunner(doctest.DocTestRunner,object):
|
|
658
|
class IPDocTestRunner(doctest.DocTestRunner,object):
|
|
652
|
"""Test runner that synchronizes the IPython namespace with test globals.
|
|
659
|
"""Test runner that synchronizes the IPython namespace with test globals.
|
|
653
|
"""
|
|
660
|
"""
|
|
654
|
|
|
661
|
|
|
655
|
def run(self, test, compileflags=None, out=None, clear_globs=True):
|
|
662
|
def run(self, test, compileflags=None, out=None, clear_globs=True):
|
|
656
|
|
|
663
|
|
|
657
|
# Hack: ipython needs access to the execution context of the example,
|
|
664
|
# Hack: ipython needs access to the execution context of the example,
|
|
658
|
# so that it can propagate user variables loaded by %run into
|
|
665
|
# so that it can propagate user variables loaded by %run into
|
|
659
|
# test.globs. We put them here into our modified %run as a function
|
|
666
|
# test.globs. We put them here into our modified %run as a function
|
|
660
|
# attribute. Our new %run will then only make the namespace update
|
|
667
|
# attribute. Our new %run will then only make the namespace update
|
|
661
|
# when called (rather than unconconditionally updating test.globs here
|
|
668
|
# when called (rather than unconconditionally updating test.globs here
|
|
662
|
# for all examples, most of which won't be calling %run anyway).
|
|
669
|
# for all examples, most of which won't be calling %run anyway).
|
|
663
|
_run_ns_sync.test_globs = test.globs
|
|
670
|
_run_ns_sync.test_globs = test.globs
|
|
664
|
_run_ns_sync.test_filename = test.filename
|
|
671
|
_run_ns_sync.test_filename = test.filename
|
|
665
|
|
|
672
|
|
|
666
|
return super(IPDocTestRunner,self).run(test,
|
|
673
|
return super(IPDocTestRunner,self).run(test,
|
|
667
|
compileflags,out,clear_globs)
|
|
674
|
compileflags,out,clear_globs)
|
|
668
|
|
|
675
|
|
|
669
|
|
|
676
|
|
|
670
|
class DocFileCase(doctest.DocFileCase):
|
|
677
|
class DocFileCase(doctest.DocFileCase):
|
|
671
|
"""Overrides to provide filename
|
|
678
|
"""Overrides to provide filename
|
|
672
|
"""
|
|
679
|
"""
|
|
673
|
def address(self):
|
|
680
|
def address(self):
|
|
674
|
return (self._dt_test.filename, None, None)
|
|
681
|
return (self._dt_test.filename, None, None)
|
|
675
|
|
|
682
|
|
|
676
|
|
|
683
|
|
|
677
|
class ExtensionDoctest(doctests.Doctest):
|
|
684
|
class ExtensionDoctest(doctests.Doctest):
|
|
678
|
"""Nose Plugin that supports doctests in extension modules.
|
|
685
|
"""Nose Plugin that supports doctests in extension modules.
|
|
679
|
"""
|
|
686
|
"""
|
|
680
|
name = 'extdoctest' # call nosetests with --with-extdoctest
|
|
687
|
name = 'extdoctest' # call nosetests with --with-extdoctest
|
|
681
|
enabled = True
|
|
688
|
enabled = True
|
|
682
|
|
|
689
|
|
|
683
|
def __init__(self,exclude_patterns=None):
|
|
690
|
def __init__(self,exclude_patterns=None):
|
|
684
|
"""Create a new ExtensionDoctest plugin.
|
|
691
|
"""Create a new ExtensionDoctest plugin.
|
|
685
|
|
|
692
|
|
|
686
|
Parameters
|
|
693
|
Parameters
|
|
687
|
----------
|
|
694
|
----------
|
|
688
|
|
|
695
|
|
|
689
|
exclude_patterns : sequence of strings, optional
|
|
696
|
exclude_patterns : sequence of strings, optional
|
|
690
|
These patterns are compiled as regular expressions, subsequently used
|
|
697
|
These patterns are compiled as regular expressions, subsequently used
|
|
691
|
to exclude any filename which matches them from inclusion in the test
|
|
698
|
to exclude any filename which matches them from inclusion in the test
|
|
692
|
suite (using pattern.search(), NOT pattern.match() ).
|
|
699
|
suite (using pattern.search(), NOT pattern.match() ).
|
|
693
|
"""
|
|
700
|
"""
|
|
|
|
|
701
|
|
|
694
|
if exclude_patterns is None:
|
|
702
|
if exclude_patterns is None:
|
|
695
|
exclude_patterns = []
|
|
703
|
exclude_patterns = []
|
|
696
|
self.exclude_patterns = map(re.compile,exclude_patterns)
|
|
704
|
self.exclude_patterns = map(re.compile,exclude_patterns)
|
|
697
|
doctests.Doctest.__init__(self)
|
|
705
|
doctests.Doctest.__init__(self)
|
|
698
|
|
|
706
|
|
|
699
|
def options(self, parser, env=os.environ):
|
|
707
|
def options(self, parser, env=os.environ):
|
|
700
|
Plugin.options(self, parser, env)
|
|
708
|
Plugin.options(self, parser, env)
|
|
701
|
parser.add_option('--doctest-tests', action='store_true',
|
|
709
|
parser.add_option('--doctest-tests', action='store_true',
|
|
702
|
dest='doctest_tests',
|
|
710
|
dest='doctest_tests',
|
|
703
|
default=env.get('NOSE_DOCTEST_TESTS',True),
|
|
711
|
default=env.get('NOSE_DOCTEST_TESTS',True),
|
|
704
|
help="Also look for doctests in test modules. "
|
|
712
|
help="Also look for doctests in test modules. "
|
|
705
|
"Note that classes, methods and functions should "
|
|
713
|
"Note that classes, methods and functions should "
|
|
706
|
"have either doctests or non-doctest tests, "
|
|
714
|
"have either doctests or non-doctest tests, "
|
|
707
|
"not both. [NOSE_DOCTEST_TESTS]")
|
|
715
|
"not both. [NOSE_DOCTEST_TESTS]")
|
|
708
|
parser.add_option('--doctest-extension', action="append",
|
|
716
|
parser.add_option('--doctest-extension', action="append",
|
|
709
|
dest="doctestExtension",
|
|
717
|
dest="doctestExtension",
|
|
710
|
help="Also look for doctests in files with "
|
|
718
|
help="Also look for doctests in files with "
|
|
711
|
"this extension [NOSE_DOCTEST_EXTENSION]")
|
|
719
|
"this extension [NOSE_DOCTEST_EXTENSION]")
|
|
712
|
# Set the default as a list, if given in env; otherwise
|
|
720
|
# Set the default as a list, if given in env; otherwise
|
|
713
|
# an additional value set on the command line will cause
|
|
721
|
# an additional value set on the command line will cause
|
|
714
|
# an error.
|
|
722
|
# an error.
|
|
715
|
env_setting = env.get('NOSE_DOCTEST_EXTENSION')
|
|
723
|
env_setting = env.get('NOSE_DOCTEST_EXTENSION')
|
|
716
|
if env_setting is not None:
|
|
724
|
if env_setting is not None:
|
|
717
|
parser.set_defaults(doctestExtension=tolist(env_setting))
|
|
725
|
parser.set_defaults(doctestExtension=tolist(env_setting))
|
|
718
|
|
|
726
|
|
|
719
|
|
|
727
|
|
|
720
|
def configure(self, options, config):
|
|
728
|
def configure(self, options, config):
|
|
721
|
Plugin.configure(self, options, config)
|
|
729
|
Plugin.configure(self, options, config)
|
|
722
|
self.doctest_tests = options.doctest_tests
|
|
730
|
self.doctest_tests = options.doctest_tests
|
|
723
|
self.extension = tolist(options.doctestExtension)
|
|
731
|
self.extension = tolist(options.doctestExtension)
|
|
724
|
|
|
732
|
|
|
725
|
self.parser = doctest.DocTestParser()
|
|
733
|
self.parser = doctest.DocTestParser()
|
|
726
|
self.finder = DocTestFinder()
|
|
734
|
self.finder = DocTestFinder()
|
|
727
|
self.checker = IPDoctestOutputChecker()
|
|
735
|
self.checker = IPDoctestOutputChecker()
|
|
728
|
self.globs = None
|
|
736
|
self.globs = None
|
|
729
|
self.extraglobs = None
|
|
737
|
self.extraglobs = None
|
|
730
|
|
|
738
|
|
|
731
|
|
|
739
|
|
|
732
|
def loadTestsFromExtensionModule(self,filename):
|
|
740
|
def loadTestsFromExtensionModule(self,filename):
|
|
733
|
bpath,mod = os.path.split(filename)
|
|
741
|
bpath,mod = os.path.split(filename)
|
|
734
|
modname = os.path.splitext(mod)[0]
|
|
742
|
modname = os.path.splitext(mod)[0]
|
|
735
|
try:
|
|
743
|
try:
|
|
736
|
sys.path.append(bpath)
|
|
744
|
sys.path.append(bpath)
|
|
737
|
module = __import__(modname)
|
|
745
|
module = __import__(modname)
|
|
738
|
tests = list(self.loadTestsFromModule(module))
|
|
746
|
tests = list(self.loadTestsFromModule(module))
|
|
739
|
finally:
|
|
747
|
finally:
|
|
740
|
sys.path.pop()
|
|
748
|
sys.path.pop()
|
|
741
|
return tests
|
|
749
|
return tests
|
|
742
|
|
|
750
|
|
|
743
|
# NOTE: the method below is almost a copy of the original one in nose, with
|
|
751
|
# NOTE: the method below is almost a copy of the original one in nose, with
|
|
744
|
# a few modifications to control output checking.
|
|
752
|
# a few modifications to control output checking.
|
|
745
|
|
|
753
|
|
|
746
|
def loadTestsFromModule(self, module):
|
|
754
|
def loadTestsFromModule(self, module):
|
|
747
|
#print '*** ipdoctest - lTM',module # dbg
|
|
755
|
#print '*** ipdoctest - lTM',module # dbg
|
|
748
|
|
|
756
|
|
|
749
|
if not self.matches(module.__name__):
|
|
757
|
if not self.matches(module.__name__):
|
|
750
|
log.debug("Doctest doesn't want module %s", module)
|
|
758
|
log.debug("Doctest doesn't want module %s", module)
|
|
751
|
return
|
|
759
|
return
|
|
752
|
|
|
760
|
|
|
753
|
tests = self.finder.find(module,globs=self.globs,
|
|
761
|
tests = self.finder.find(module,globs=self.globs,
|
|
754
|
extraglobs=self.extraglobs)
|
|
762
|
extraglobs=self.extraglobs)
|
|
755
|
if not tests:
|
|
763
|
if not tests:
|
|
756
|
return
|
|
764
|
return
|
|
757
|
|
|
765
|
|
|
758
|
# always use whitespace and ellipsis options
|
|
766
|
# always use whitespace and ellipsis options
|
|
759
|
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
|
767
|
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
|
760
|
|
|
768
|
|
|
761
|
tests.sort()
|
|
769
|
tests.sort()
|
|
762
|
module_file = module.__file__
|
|
770
|
module_file = module.__file__
|
|
763
|
if module_file[-4:] in ('.pyc', '.pyo'):
|
|
771
|
if module_file[-4:] in ('.pyc', '.pyo'):
|
|
764
|
module_file = module_file[:-1]
|
|
772
|
module_file = module_file[:-1]
|
|
765
|
for test in tests:
|
|
773
|
for test in tests:
|
|
766
|
if not test.examples:
|
|
774
|
if not test.examples:
|
|
767
|
continue
|
|
775
|
continue
|
|
768
|
if not test.filename:
|
|
776
|
if not test.filename:
|
|
769
|
test.filename = module_file
|
|
777
|
test.filename = module_file
|
|
770
|
|
|
778
|
|
|
771
|
yield DocTestCase(test,
|
|
779
|
yield DocTestCase(test,
|
|
772
|
optionflags=optionflags,
|
|
780
|
optionflags=optionflags,
|
|
773
|
checker=self.checker)
|
|
781
|
checker=self.checker)
|
|
774
|
|
|
782
|
|
|
775
|
|
|
783
|
|
|
776
|
def loadTestsFromFile(self, filename):
|
|
784
|
def loadTestsFromFile(self, filename):
|
|
777
|
if is_extension_module(filename):
|
|
785
|
if is_extension_module(filename):
|
|
778
|
for t in self.loadTestsFromExtensionModule(filename):
|
|
786
|
for t in self.loadTestsFromExtensionModule(filename):
|
|
779
|
yield t
|
|
787
|
yield t
|
|
780
|
else:
|
|
788
|
else:
|
|
781
|
if self.extension and anyp(filename.endswith, self.extension):
|
|
789
|
if self.extension and anyp(filename.endswith, self.extension):
|
|
782
|
name = os.path.basename(filename)
|
|
790
|
name = os.path.basename(filename)
|
|
783
|
dh = open(filename)
|
|
791
|
dh = open(filename)
|
|
784
|
try:
|
|
792
|
try:
|
|
785
|
doc = dh.read()
|
|
793
|
doc = dh.read()
|
|
786
|
finally:
|
|
794
|
finally:
|
|
787
|
dh.close()
|
|
795
|
dh.close()
|
|
788
|
test = self.parser.get_doctest(
|
|
796
|
test = self.parser.get_doctest(
|
|
789
|
doc, globs={'__file__': filename}, name=name,
|
|
797
|
doc, globs={'__file__': filename}, name=name,
|
|
790
|
filename=filename, lineno=0)
|
|
798
|
filename=filename, lineno=0)
|
|
791
|
if test.examples:
|
|
799
|
if test.examples:
|
|
792
|
#print 'FileCase:',test.examples # dbg
|
|
800
|
#print 'FileCase:',test.examples # dbg
|
|
793
|
yield DocFileCase(test)
|
|
801
|
yield DocFileCase(test)
|
|
794
|
else:
|
|
802
|
else:
|
|
795
|
yield False # no tests to load
|
|
803
|
yield False # no tests to load
|
|
796
|
|
|
804
|
|
|
797
|
def wantFile(self,filename):
|
|
805
|
def wantFile(self,filename):
|
|
798
|
"""Return whether the given filename should be scanned for tests.
|
|
806
|
"""Return whether the given filename should be scanned for tests.
|
|
799
|
|
|
807
|
|
|
800
|
Modified version that accepts extension modules as valid containers for
|
|
808
|
Modified version that accepts extension modules as valid containers for
|
|
801
|
doctests.
|
|
809
|
doctests.
|
|
802
|
"""
|
|
810
|
"""
|
|
803
|
#print '*** ipdoctest- wantFile:',filename # dbg
|
|
811
|
#print '*** ipdoctest- wantFile:',filename # dbg
|
|
804
|
|
|
812
|
|
|
805
|
for pat in self.exclude_patterns:
|
|
813
|
for pat in self.exclude_patterns:
|
|
806
|
if pat.search(filename):
|
|
814
|
if pat.search(filename):
|
|
807
|
#print '###>>> SKIP:',filename # dbg
|
|
815
|
#print '###>>> SKIP:',filename # dbg
|
|
808
|
return False
|
|
816
|
return False
|
|
809
|
|
|
817
|
|
|
810
|
if is_extension_module(filename):
|
|
818
|
if is_extension_module(filename):
|
|
811
|
return True
|
|
819
|
return True
|
|
812
|
else:
|
|
820
|
else:
|
|
813
|
return doctests.Doctest.wantFile(self,filename)
|
|
821
|
return doctests.Doctest.wantFile(self,filename)
|
|
814
|
|
|
822
|
|
|
815
|
|
|
823
|
|
|
816
|
class IPythonDoctest(ExtensionDoctest):
|
|
824
|
class IPythonDoctest(ExtensionDoctest):
|
|
817
|
"""Nose Plugin that supports doctests in extension modules.
|
|
825
|
"""Nose Plugin that supports doctests in extension modules.
|
|
818
|
"""
|
|
826
|
"""
|
|
819
|
name = 'ipdoctest' # call nosetests with --with-ipdoctest
|
|
827
|
name = 'ipdoctest' # call nosetests with --with-ipdoctest
|
|
820
|
enabled = True
|
|
828
|
enabled = True
|
|
821
|
|
|
829
|
|
|
822
|
def makeTest(self, obj, parent):
|
|
830
|
def makeTest(self, obj, parent):
|
|
823
|
"""Look for doctests in the given object, which will be a
|
|
831
|
"""Look for doctests in the given object, which will be a
|
|
824
|
function, method or class.
|
|
832
|
function, method or class.
|
|
825
|
"""
|
|
833
|
"""
|
|
826
|
# always use whitespace and ellipsis options
|
|
834
|
# always use whitespace and ellipsis options
|
|
827
|
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
|
835
|
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
|
828
|
|
|
836
|
|
|
829
|
doctests = self.finder.find(obj, module=getmodule(parent))
|
|
837
|
doctests = self.finder.find(obj, module=getmodule(parent))
|
|
830
|
if doctests:
|
|
838
|
if doctests:
|
|
831
|
for test in doctests:
|
|
839
|
for test in doctests:
|
|
832
|
if len(test.examples) == 0:
|
|
840
|
if len(test.examples) == 0:
|
|
833
|
continue
|
|
841
|
continue
|
|
834
|
|
|
842
|
|
|
835
|
yield DocTestCase(test, obj=obj,
|
|
843
|
yield DocTestCase(test, obj=obj,
|
|
836
|
optionflags=optionflags,
|
|
844
|
optionflags=optionflags,
|
|
837
|
checker=self.checker)
|
|
845
|
checker=self.checker)
|
|
838
|
|
|
846
|
|
|
839
|
def configure(self, options, config):
|
|
847
|
def options(self, parser, env=os.environ):
|
|
|
|
|
848
|
Plugin.options(self, parser, env)
|
|
|
|
|
849
|
parser.add_option('--ipdoctest-tests', action='store_true',
|
|
|
|
|
850
|
dest='ipdoctest_tests',
|
|
|
|
|
851
|
default=env.get('NOSE_IPDOCTEST_TESTS',True),
|
|
|
|
|
852
|
help="Also look for doctests in test modules. "
|
|
|
|
|
853
|
"Note that classes, methods and functions should "
|
|
|
|
|
854
|
"have either doctests or non-doctest tests, "
|
|
|
|
|
855
|
"not both. [NOSE_IPDOCTEST_TESTS]")
|
|
|
|
|
856
|
parser.add_option('--ipdoctest-extension', action="append",
|
|
|
|
|
857
|
dest="ipdoctestExtension",
|
|
|
|
|
858
|
help="Also look for doctests in files with "
|
|
|
|
|
859
|
"this extension [NOSE_IPDOCTEST_EXTENSION]")
|
|
|
|
|
860
|
# Set the default as a list, if given in env; otherwise
|
|
|
|
|
861
|
# an additional value set on the command line will cause
|
|
|
|
|
862
|
# an error.
|
|
|
|
|
863
|
env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
|
|
|
|
|
864
|
if env_setting is not None:
|
|
|
|
|
865
|
parser.set_defaults(ipdoctestExtension=tolist(env_setting))
|
|
840
|
|
|
866
|
|
|
|
|
|
867
|
def configure(self, options, config):
|
|
841
|
Plugin.configure(self, options, config)
|
|
868
|
Plugin.configure(self, options, config)
|
|
842
|
self.doctest_tests = options.doctest_tests
|
|
869
|
self.doctest_tests = options.ipdoctest_tests
|
|
843
|
self.extension = tolist(options.doctestExtension)
|
|
870
|
self.extension = tolist(options.ipdoctestExtension)
|
|
844
|
|
|
871
|
|
|
845
|
self.parser = IPDocTestParser()
|
|
872
|
self.parser = IPDocTestParser()
|
|
846
|
self.finder = DocTestFinder(parser=self.parser)
|
|
873
|
self.finder = DocTestFinder(parser=self.parser)
|
|
847
|
self.checker = IPDoctestOutputChecker()
|
|
874
|
self.checker = IPDoctestOutputChecker()
|
|
848
|
self.globs = None
|
|
875
|
self.globs = None
|
|
849
|
self.extraglobs = None
|
|
876
|
self.extraglobs = None
|
|
850
|
|
|
877
|
|