##// END OF EJS Templates
use pathlib in testing/plugin/ipdoctest.py
rushabh-v -
Show More
@@ -1,761 +1,762 b''
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 setting the
6 pretty-printing OFF. This can be done either by setting the
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 by interactively disabling it with %Pprint. This is required so that IPython
8 by 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 # Module imports
19 # Module imports
20
20
21 # From the standard library
21 # From the standard library
22 import builtins as builtin_mod
22 import builtins as builtin_mod
23 import doctest
23 import doctest
24 import inspect
24 import inspect
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29 from importlib import import_module
29 from importlib import import_module
30 from io import StringIO
30 from io import StringIO
31
31
32 from testpath import modified_env
32 from testpath import modified_env
33
33
34 from inspect import getmodule
34 from inspect import getmodule
35
35
36 from pathlib import Path, PurePath
37
36 # We are overriding the default doctest runner, so we need to import a few
38 # We are overriding the default doctest runner, so we need to import a few
37 # things from doctest directly
39 # things from doctest directly
38 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
40 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
39 _unittest_reportflags, DocTestRunner,
41 _unittest_reportflags, DocTestRunner,
40 _extract_future_flags, pdb, _OutputRedirectingPdb,
42 _extract_future_flags, pdb, _OutputRedirectingPdb,
41 _exception_traceback,
43 _exception_traceback,
42 linecache)
44 linecache)
43
45
44 # Third-party modules
46 # Third-party modules
45
47
46 from nose.plugins import doctests, Plugin
48 from nose.plugins import doctests, Plugin
47 from nose.util import anyp, tolist
49 from nose.util import anyp, tolist
48
50
49 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
50 # Module globals and other constants
52 # Module globals and other constants
51 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
52
54
53 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
54
56
55
57
56 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
57 # Classes and functions
59 # Classes and functions
58 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
59
61
60 def is_extension_module(filename):
62 def is_extension_module(filename):
61 """Return whether the given filename is an extension module.
63 """Return whether the given filename is an extension module.
62
64
63 This simply checks that the extension is either .so or .pyd.
65 This simply checks that the extension is either .so or .pyd.
64 """
66 """
65 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
67 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
66
68
67
69
68 class DocTestSkip(object):
70 class DocTestSkip(object):
69 """Object wrapper for doctests to be skipped."""
71 """Object wrapper for doctests to be skipped."""
70
72
71 ds_skip = """Doctest to skip.
73 ds_skip = """Doctest to skip.
72 >>> 1 #doctest: +SKIP
74 >>> 1 #doctest: +SKIP
73 """
75 """
74
76
75 def __init__(self,obj):
77 def __init__(self,obj):
76 self.obj = obj
78 self.obj = obj
77
79
78 def __getattribute__(self,key):
80 def __getattribute__(self,key):
79 if key == '__doc__':
81 if key == '__doc__':
80 return DocTestSkip.ds_skip
82 return DocTestSkip.ds_skip
81 else:
83 else:
82 return getattr(object.__getattribute__(self,'obj'),key)
84 return getattr(object.__getattribute__(self,'obj'),key)
83
85
84 # Modified version of the one in the stdlib, that fixes a python bug (doctests
86 # Modified version of the one in the stdlib, that fixes a python bug (doctests
85 # not found in extension modules, http://bugs.python.org/issue3158)
87 # not found in extension modules, http://bugs.python.org/issue3158)
86 class DocTestFinder(doctest.DocTestFinder):
88 class DocTestFinder(doctest.DocTestFinder):
87
89
88 def _from_module(self, module, object):
90 def _from_module(self, module, object):
89 """
91 """
90 Return true if the given object is defined in the given
92 Return true if the given object is defined in the given
91 module.
93 module.
92 """
94 """
93 if module is None:
95 if module is None:
94 return True
96 return True
95 elif inspect.isfunction(object):
97 elif inspect.isfunction(object):
96 return module.__dict__ is object.__globals__
98 return module.__dict__ is object.__globals__
97 elif inspect.isbuiltin(object):
99 elif inspect.isbuiltin(object):
98 return module.__name__ == object.__module__
100 return module.__name__ == object.__module__
99 elif inspect.isclass(object):
101 elif inspect.isclass(object):
100 return module.__name__ == object.__module__
102 return module.__name__ == object.__module__
101 elif inspect.ismethod(object):
103 elif inspect.ismethod(object):
102 # This one may be a bug in cython that fails to correctly set the
104 # This one may be a bug in cython that fails to correctly set the
103 # __module__ attribute of methods, but since the same error is easy
105 # __module__ attribute of methods, but since the same error is easy
104 # to make by extension code writers, having this safety in place
106 # to make by extension code writers, having this safety in place
105 # isn't such a bad idea
107 # isn't such a bad idea
106 return module.__name__ == object.__self__.__class__.__module__
108 return module.__name__ == object.__self__.__class__.__module__
107 elif inspect.getmodule(object) is not None:
109 elif inspect.getmodule(object) is not None:
108 return module is inspect.getmodule(object)
110 return module is inspect.getmodule(object)
109 elif hasattr(object, '__module__'):
111 elif hasattr(object, '__module__'):
110 return module.__name__ == object.__module__
112 return module.__name__ == object.__module__
111 elif isinstance(object, property):
113 elif isinstance(object, property):
112 return True # [XX] no way not be sure.
114 return True # [XX] no way not be sure.
113 elif inspect.ismethoddescriptor(object):
115 elif inspect.ismethoddescriptor(object):
114 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
116 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
115 # to avoid throwing an error. See also http://bugs.python.org/issue3158
117 # to avoid throwing an error. See also http://bugs.python.org/issue3158
116 return False
118 return False
117 else:
119 else:
118 raise ValueError("object must be a class or function, got %r" % object)
120 raise ValueError("object must be a class or function, got %r" % object)
119
121
120 def _find(self, tests, obj, name, module, source_lines, globs, seen):
122 def _find(self, tests, obj, name, module, source_lines, globs, seen):
121 """
123 """
122 Find tests for the given object and any contained objects, and
124 Find tests for the given object and any contained objects, and
123 add them to `tests`.
125 add them to `tests`.
124 """
126 """
125 print('_find for:', obj, name, module) # dbg
127 print('_find for:', obj, name, module) # dbg
126 if hasattr(obj,"skip_doctest"):
128 if hasattr(obj,"skip_doctest"):
127 #print 'SKIPPING DOCTEST FOR:',obj # dbg
129 #print 'SKIPPING DOCTEST FOR:',obj # dbg
128 obj = DocTestSkip(obj)
130 obj = DocTestSkip(obj)
129
131
130 doctest.DocTestFinder._find(self,tests, obj, name, module,
132 doctest.DocTestFinder._find(self,tests, obj, name, module,
131 source_lines, globs, seen)
133 source_lines, globs, seen)
132
134
133 # Below we re-run pieces of the above method with manual modifications,
135 # Below we re-run pieces of the above method with manual modifications,
134 # because the original code is buggy and fails to correctly identify
136 # because the original code is buggy and fails to correctly identify
135 # doctests in extension modules.
137 # doctests in extension modules.
136
138
137 # Local shorthands
139 # Local shorthands
138 from inspect import isroutine, isclass
140 from inspect import isroutine, isclass
139
141
140 # Look for tests in a module's contained objects.
142 # Look for tests in a module's contained objects.
141 if inspect.ismodule(obj) and self._recurse:
143 if inspect.ismodule(obj) and self._recurse:
142 for valname, val in obj.__dict__.items():
144 for valname, val in obj.__dict__.items():
143 valname1 = '%s.%s' % (name, valname)
145 valname1 = '%s.%s' % (name, valname)
144 if ( (isroutine(val) or isclass(val))
146 if ( (isroutine(val) or isclass(val))
145 and self._from_module(module, val) ):
147 and self._from_module(module, val) ):
146
148
147 self._find(tests, val, valname1, module, source_lines,
149 self._find(tests, val, valname1, module, source_lines,
148 globs, seen)
150 globs, seen)
149
151
150 # Look for tests in a class's contained objects.
152 # Look for tests in a class's contained objects.
151 if inspect.isclass(obj) and self._recurse:
153 if inspect.isclass(obj) and self._recurse:
152 #print 'RECURSE into class:',obj # dbg
154 #print 'RECURSE into class:',obj # dbg
153 for valname, val in obj.__dict__.items():
155 for valname, val in obj.__dict__.items():
154 # Special handling for staticmethod/classmethod.
156 # Special handling for staticmethod/classmethod.
155 if isinstance(val, staticmethod):
157 if isinstance(val, staticmethod):
156 val = getattr(obj, valname)
158 val = getattr(obj, valname)
157 if isinstance(val, classmethod):
159 if isinstance(val, classmethod):
158 val = getattr(obj, valname).__func__
160 val = getattr(obj, valname).__func__
159
161
160 # Recurse to methods, properties, and nested classes.
162 # Recurse to methods, properties, and nested classes.
161 if ((inspect.isfunction(val) or inspect.isclass(val) or
163 if ((inspect.isfunction(val) or inspect.isclass(val) or
162 inspect.ismethod(val) or
164 inspect.ismethod(val) or
163 isinstance(val, property)) and
165 isinstance(val, property)) and
164 self._from_module(module, val)):
166 self._from_module(module, val)):
165 valname = '%s.%s' % (name, valname)
167 valname = '%s.%s' % (name, valname)
166 self._find(tests, val, valname, module, source_lines,
168 self._find(tests, val, valname, module, source_lines,
167 globs, seen)
169 globs, seen)
168
170
169
171
170 class IPDoctestOutputChecker(doctest.OutputChecker):
172 class IPDoctestOutputChecker(doctest.OutputChecker):
171 """Second-chance checker with support for random tests.
173 """Second-chance checker with support for random tests.
172
174
173 If the default comparison doesn't pass, this checker looks in the expected
175 If the default comparison doesn't pass, this checker looks in the expected
174 output string for flags that tell us to ignore the output.
176 output string for flags that tell us to ignore the output.
175 """
177 """
176
178
177 random_re = re.compile(r'#\s*random\s+')
179 random_re = re.compile(r'#\s*random\s+')
178
180
179 def check_output(self, want, got, optionflags):
181 def check_output(self, want, got, optionflags):
180 """Check output, accepting special markers embedded in the output.
182 """Check output, accepting special markers embedded in the output.
181
183
182 If the output didn't pass the default validation but the special string
184 If the output didn't pass the default validation but the special string
183 '#random' is included, we accept it."""
185 '#random' is included, we accept it."""
184
186
185 # Let the original tester verify first, in case people have valid tests
187 # Let the original tester verify first, in case people have valid tests
186 # that happen to have a comment saying '#random' embedded in.
188 # that happen to have a comment saying '#random' embedded in.
187 ret = doctest.OutputChecker.check_output(self, want, got,
189 ret = doctest.OutputChecker.check_output(self, want, got,
188 optionflags)
190 optionflags)
189 if not ret and self.random_re.search(want):
191 if not ret and self.random_re.search(want):
190 #print >> sys.stderr, 'RANDOM OK:',want # dbg
192 #print >> sys.stderr, 'RANDOM OK:',want # dbg
191 return True
193 return True
192
194
193 return ret
195 return ret
194
196
195
197
196 class DocTestCase(doctests.DocTestCase):
198 class DocTestCase(doctests.DocTestCase):
197 """Proxy for DocTestCase: provides an address() method that
199 """Proxy for DocTestCase: provides an address() method that
198 returns the correct address for the doctest case. Otherwise
200 returns the correct address for the doctest case. Otherwise
199 acts as a proxy to the test case. To provide hints for address(),
201 acts as a proxy to the test case. To provide hints for address(),
200 an obj may also be passed -- this will be used as the test object
202 an obj may also be passed -- this will be used as the test object
201 for purposes of determining the test address, if it is provided.
203 for purposes of determining the test address, if it is provided.
202 """
204 """
203
205
204 # Note: this method was taken from numpy's nosetester module.
206 # Note: this method was taken from numpy's nosetester module.
205
207
206 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
208 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
207 # its constructor that blocks non-default arguments from being passed
209 # its constructor that blocks non-default arguments from being passed
208 # down into doctest.DocTestCase
210 # down into doctest.DocTestCase
209
211
210 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
212 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
211 checker=None, obj=None, result_var='_'):
213 checker=None, obj=None, result_var='_'):
212 self._result_var = result_var
214 self._result_var = result_var
213 doctests.DocTestCase.__init__(self, test,
215 doctests.DocTestCase.__init__(self, test,
214 optionflags=optionflags,
216 optionflags=optionflags,
215 setUp=setUp, tearDown=tearDown,
217 setUp=setUp, tearDown=tearDown,
216 checker=checker)
218 checker=checker)
217 # Now we must actually copy the original constructor from the stdlib
219 # Now we must actually copy the original constructor from the stdlib
218 # doctest class, because we can't call it directly and a bug in nose
220 # doctest class, because we can't call it directly and a bug in nose
219 # means it never gets passed the right arguments.
221 # means it never gets passed the right arguments.
220
222
221 self._dt_optionflags = optionflags
223 self._dt_optionflags = optionflags
222 self._dt_checker = checker
224 self._dt_checker = checker
223 self._dt_test = test
225 self._dt_test = test
224 self._dt_test_globs_ori = test.globs
226 self._dt_test_globs_ori = test.globs
225 self._dt_setUp = setUp
227 self._dt_setUp = setUp
226 self._dt_tearDown = tearDown
228 self._dt_tearDown = tearDown
227
229
228 # XXX - store this runner once in the object!
230 # XXX - store this runner once in the object!
229 runner = IPDocTestRunner(optionflags=optionflags,
231 runner = IPDocTestRunner(optionflags=optionflags,
230 checker=checker, verbose=False)
232 checker=checker, verbose=False)
231 self._dt_runner = runner
233 self._dt_runner = runner
232
234
233
235
234 # Each doctest should remember the directory it was loaded from, so
236 # Each doctest should remember the directory it was loaded from, so
235 # things like %run work without too many contortions
237 # things like %run work without too many contortions
236 self._ori_dir = os.path.dirname(test.filename)
238 self._ori_dir = os.path.dirname(test.filename)
237
239
238 # Modified runTest from the default stdlib
240 # Modified runTest from the default stdlib
239 def runTest(self):
241 def runTest(self):
240 test = self._dt_test
242 test = self._dt_test
241 runner = self._dt_runner
243 runner = self._dt_runner
242
244
243 old = sys.stdout
245 old = sys.stdout
244 new = StringIO()
246 new = StringIO()
245 optionflags = self._dt_optionflags
247 optionflags = self._dt_optionflags
246
248
247 if not (optionflags & REPORTING_FLAGS):
249 if not (optionflags & REPORTING_FLAGS):
248 # The option flags don't include any reporting flags,
250 # The option flags don't include any reporting flags,
249 # so add the default reporting flags
251 # so add the default reporting flags
250 optionflags |= _unittest_reportflags
252 optionflags |= _unittest_reportflags
251
253
252 try:
254 try:
253 # Save our current directory and switch out to the one where the
255 # Save our current directory and switch out to the one where the
254 # test was originally created, in case another doctest did a
256 # test was originally created, in case another doctest did a
255 # directory change. We'll restore this in the finally clause.
257 # directory change. We'll restore this in the finally clause.
256 curdir = os.getcwd()
258 curdir = os.getcwd()
257 #print 'runTest in dir:', self._ori_dir # dbg
259 #print 'runTest in dir:', self._ori_dir # dbg
258 os.chdir(self._ori_dir)
260 os.chdir(self._ori_dir)
259
261
260 runner.DIVIDER = "-"*70
262 runner.DIVIDER = "-"*70
261 failures, tries = runner.run(test,out=new.write,
263 failures, tries = runner.run(test,out=new.write,
262 clear_globs=False)
264 clear_globs=False)
263 finally:
265 finally:
264 sys.stdout = old
266 sys.stdout = old
265 os.chdir(curdir)
267 os.chdir(curdir)
266
268
267 if failures:
269 if failures:
268 raise self.failureException(self.format_failure(new.getvalue()))
270 raise self.failureException(self.format_failure(new.getvalue()))
269
271
270 def setUp(self):
272 def setUp(self):
271 """Modified test setup that syncs with ipython namespace"""
273 """Modified test setup that syncs with ipython namespace"""
272 #print "setUp test", self._dt_test.examples # dbg
274 #print "setUp test", self._dt_test.examples # dbg
273 if isinstance(self._dt_test.examples[0], IPExample):
275 if isinstance(self._dt_test.examples[0], IPExample):
274 # for IPython examples *only*, we swap the globals with the ipython
276 # for IPython examples *only*, we swap the globals with the ipython
275 # namespace, after updating it with the globals (which doctest
277 # namespace, after updating it with the globals (which doctest
276 # fills with the necessary info from the module being tested).
278 # fills with the necessary info from the module being tested).
277 self.user_ns_orig = {}
279 self.user_ns_orig = {}
278 self.user_ns_orig.update(_ip.user_ns)
280 self.user_ns_orig.update(_ip.user_ns)
279 _ip.user_ns.update(self._dt_test.globs)
281 _ip.user_ns.update(self._dt_test.globs)
280 # We must remove the _ key in the namespace, so that Python's
282 # We must remove the _ key in the namespace, so that Python's
281 # doctest code sets it naturally
283 # doctest code sets it naturally
282 _ip.user_ns.pop('_', None)
284 _ip.user_ns.pop('_', None)
283 _ip.user_ns['__builtins__'] = builtin_mod
285 _ip.user_ns['__builtins__'] = builtin_mod
284 self._dt_test.globs = _ip.user_ns
286 self._dt_test.globs = _ip.user_ns
285
287
286 super(DocTestCase, self).setUp()
288 super(DocTestCase, self).setUp()
287
289
288 def tearDown(self):
290 def tearDown(self):
289
291
290 # Undo the test.globs reassignment we made, so that the parent class
292 # Undo the test.globs reassignment we made, so that the parent class
291 # teardown doesn't destroy the ipython namespace
293 # teardown doesn't destroy the ipython namespace
292 if isinstance(self._dt_test.examples[0], IPExample):
294 if isinstance(self._dt_test.examples[0], IPExample):
293 self._dt_test.globs = self._dt_test_globs_ori
295 self._dt_test.globs = self._dt_test_globs_ori
294 _ip.user_ns.clear()
296 _ip.user_ns.clear()
295 _ip.user_ns.update(self.user_ns_orig)
297 _ip.user_ns.update(self.user_ns_orig)
296
298
297 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
299 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
298 # it does look like one to me: its tearDown method tries to run
300 # it does look like one to me: its tearDown method tries to run
299 #
301 #
300 # delattr(builtin_mod, self._result_var)
302 # delattr(builtin_mod, self._result_var)
301 #
303 #
302 # without checking that the attribute really is there; it implicitly
304 # without checking that the attribute really is there; it implicitly
303 # assumes it should have been set via displayhook. But if the
305 # assumes it should have been set via displayhook. But if the
304 # displayhook was never called, this doesn't necessarily happen. I
306 # displayhook was never called, this doesn't necessarily happen. I
305 # haven't been able to find a little self-contained example outside of
307 # haven't been able to find a little self-contained example outside of
306 # ipython that would show the problem so I can report it to the nose
308 # ipython that would show the problem so I can report it to the nose
307 # team, but it does happen a lot in our code.
309 # team, but it does happen a lot in our code.
308 #
310 #
309 # So here, we just protect as narrowly as possible by trapping an
311 # So here, we just protect as narrowly as possible by trapping an
310 # attribute error whose message would be the name of self._result_var,
312 # attribute error whose message would be the name of self._result_var,
311 # and letting any other error propagate.
313 # and letting any other error propagate.
312 try:
314 try:
313 super(DocTestCase, self).tearDown()
315 super(DocTestCase, self).tearDown()
314 except AttributeError as exc:
316 except AttributeError as exc:
315 if exc.args[0] != self._result_var:
317 if exc.args[0] != self._result_var:
316 raise
318 raise
317
319
318
320
319 # A simple subclassing of the original with a different class name, so we can
321 # A simple subclassing of the original with a different class name, so we can
320 # distinguish and treat differently IPython examples from pure python ones.
322 # distinguish and treat differently IPython examples from pure python ones.
321 class IPExample(doctest.Example): pass
323 class IPExample(doctest.Example): pass
322
324
323
325
324 class IPExternalExample(doctest.Example):
326 class IPExternalExample(doctest.Example):
325 """Doctest examples to be run in an external process."""
327 """Doctest examples to be run in an external process."""
326
328
327 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
329 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
328 options=None):
330 options=None):
329 # Parent constructor
331 # Parent constructor
330 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
332 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
331
333
332 # An EXTRA newline is needed to prevent pexpect hangs
334 # An EXTRA newline is needed to prevent pexpect hangs
333 self.source += '\n'
335 self.source += '\n'
334
336
335
337
336 class IPDocTestParser(doctest.DocTestParser):
338 class IPDocTestParser(doctest.DocTestParser):
337 """
339 """
338 A class used to parse strings containing doctest examples.
340 A class used to parse strings containing doctest examples.
339
341
340 Note: This is a version modified to properly recognize IPython input and
342 Note: This is a version modified to properly recognize IPython input and
341 convert any IPython examples into valid Python ones.
343 convert any IPython examples into valid Python ones.
342 """
344 """
343 # This regular expression is used to find doctest examples in a
345 # This regular expression is used to find doctest examples in a
344 # string. It defines three groups: `source` is the source code
346 # string. It defines three groups: `source` is the source code
345 # (including leading indentation and prompts); `indent` is the
347 # (including leading indentation and prompts); `indent` is the
346 # indentation of the first (PS1) line of the source code; and
348 # indentation of the first (PS1) line of the source code; and
347 # `want` is the expected output (including leading indentation).
349 # `want` is the expected output (including leading indentation).
348
350
349 # Classic Python prompts or default IPython ones
351 # Classic Python prompts or default IPython ones
350 _PS1_PY = r'>>>'
352 _PS1_PY = r'>>>'
351 _PS2_PY = r'\.\.\.'
353 _PS2_PY = r'\.\.\.'
352
354
353 _PS1_IP = r'In\ \[\d+\]:'
355 _PS1_IP = r'In\ \[\d+\]:'
354 _PS2_IP = r'\ \ \ \.\.\.+:'
356 _PS2_IP = r'\ \ \ \.\.\.+:'
355
357
356 _RE_TPL = r'''
358 _RE_TPL = r'''
357 # Source consists of a PS1 line followed by zero or more PS2 lines.
359 # Source consists of a PS1 line followed by zero or more PS2 lines.
358 (?P<source>
360 (?P<source>
359 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
361 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
360 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
362 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
361 \n? # a newline
363 \n? # a newline
362 # Want consists of any non-blank lines that do not start with PS1.
364 # Want consists of any non-blank lines that do not start with PS1.
363 (?P<want> (?:(?![ ]*$) # Not a blank line
365 (?P<want> (?:(?![ ]*$) # Not a blank line
364 (?![ ]*%s) # Not a line starting with PS1
366 (?![ ]*%s) # Not a line starting with PS1
365 (?![ ]*%s) # Not a line starting with PS2
367 (?![ ]*%s) # Not a line starting with PS2
366 .*$\n? # But any other line
368 .*$\n? # But any other line
367 )*)
369 )*)
368 '''
370 '''
369
371
370 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
372 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
371 re.MULTILINE | re.VERBOSE)
373 re.MULTILINE | re.VERBOSE)
372
374
373 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
375 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
374 re.MULTILINE | re.VERBOSE)
376 re.MULTILINE | re.VERBOSE)
375
377
376 # Mark a test as being fully random. In this case, we simply append the
378 # Mark a test as being fully random. In this case, we simply append the
377 # random marker ('#random') to each individual example's output. This way
379 # random marker ('#random') to each individual example's output. This way
378 # we don't need to modify any other code.
380 # we don't need to modify any other code.
379 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
381 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
380
382
381 # Mark tests to be executed in an external process - currently unsupported.
383 # Mark tests to be executed in an external process - currently unsupported.
382 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
384 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
383
385
384 def ip2py(self,source):
386 def ip2py(self,source):
385 """Convert input IPython source into valid Python."""
387 """Convert input IPython source into valid Python."""
386 block = _ip.input_transformer_manager.transform_cell(source)
388 block = _ip.input_transformer_manager.transform_cell(source)
387 if len(block.splitlines()) == 1:
389 if len(block.splitlines()) == 1:
388 return _ip.prefilter(block)
390 return _ip.prefilter(block)
389 else:
391 else:
390 return block
392 return block
391
393
392 def parse(self, string, name='<string>'):
394 def parse(self, string, name='<string>'):
393 """
395 """
394 Divide the given string into examples and intervening text,
396 Divide the given string into examples and intervening text,
395 and return them as a list of alternating Examples and strings.
397 and return them as a list of alternating Examples and strings.
396 Line numbers for the Examples are 0-based. The optional
398 Line numbers for the Examples are 0-based. The optional
397 argument `name` is a name identifying this string, and is only
399 argument `name` is a name identifying this string, and is only
398 used for error messages.
400 used for error messages.
399 """
401 """
400
402
401 #print 'Parse string:\n',string # dbg
403 #print 'Parse string:\n',string # dbg
402
404
403 string = string.expandtabs()
405 string = string.expandtabs()
404 # If all lines begin with the same indentation, then strip it.
406 # If all lines begin with the same indentation, then strip it.
405 min_indent = self._min_indent(string)
407 min_indent = self._min_indent(string)
406 if min_indent > 0:
408 if min_indent > 0:
407 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
408
410
409 output = []
411 output = []
410 charno, lineno = 0, 0
412 charno, lineno = 0, 0
411
413
412 # We make 'all random' tests by adding the '# random' mark to every
414 # We make 'all random' tests by adding the '# random' mark to every
413 # block of output in the test.
415 # block of output in the test.
414 if self._RANDOM_TEST.search(string):
416 if self._RANDOM_TEST.search(string):
415 random_marker = '\n# random'
417 random_marker = '\n# random'
416 else:
418 else:
417 random_marker = ''
419 random_marker = ''
418
420
419 # Whether to convert the input from ipython to python syntax
421 # Whether to convert the input from ipython to python syntax
420 ip2py = False
422 ip2py = False
421 # Find all doctest examples in the string. First, try them as Python
423 # Find all doctest examples in the string. First, try them as Python
422 # examples, then as IPython ones
424 # examples, then as IPython ones
423 terms = list(self._EXAMPLE_RE_PY.finditer(string))
425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
424 if terms:
426 if terms:
425 # Normal Python example
427 # Normal Python example
426 #print '-'*70 # dbg
428 #print '-'*70 # dbg
427 #print 'PyExample, Source:\n',string # dbg
429 #print 'PyExample, Source:\n',string # dbg
428 #print '-'*70 # dbg
430 #print '-'*70 # dbg
429 Example = doctest.Example
431 Example = doctest.Example
430 else:
432 else:
431 # It's an ipython example. Note that IPExamples are run
433 # It's an ipython example. Note that IPExamples are run
432 # in-process, so their syntax must be turned into valid python.
434 # in-process, so their syntax must be turned into valid python.
433 # IPExternalExamples are run out-of-process (via pexpect) so they
435 # IPExternalExamples are run out-of-process (via pexpect) so they
434 # don't need any filtering (a real ipython will be executing them).
436 # don't need any filtering (a real ipython will be executing them).
435 terms = list(self._EXAMPLE_RE_IP.finditer(string))
437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
436 if self._EXTERNAL_IP.search(string):
438 if self._EXTERNAL_IP.search(string):
437 #print '-'*70 # dbg
439 #print '-'*70 # dbg
438 #print 'IPExternalExample, Source:\n',string # dbg
440 #print 'IPExternalExample, Source:\n',string # dbg
439 #print '-'*70 # dbg
441 #print '-'*70 # dbg
440 Example = IPExternalExample
442 Example = IPExternalExample
441 else:
443 else:
442 #print '-'*70 # dbg
444 #print '-'*70 # dbg
443 #print 'IPExample, Source:\n',string # dbg
445 #print 'IPExample, Source:\n',string # dbg
444 #print '-'*70 # dbg
446 #print '-'*70 # dbg
445 Example = IPExample
447 Example = IPExample
446 ip2py = True
448 ip2py = True
447
449
448 for m in terms:
450 for m in terms:
449 # Add the pre-example text to `output`.
451 # Add the pre-example text to `output`.
450 output.append(string[charno:m.start()])
452 output.append(string[charno:m.start()])
451 # Update lineno (lines before this example)
453 # Update lineno (lines before this example)
452 lineno += string.count('\n', charno, m.start())
454 lineno += string.count('\n', charno, m.start())
453 # Extract info from the regexp match.
455 # Extract info from the regexp match.
454 (source, options, want, exc_msg) = \
456 (source, options, want, exc_msg) = \
455 self._parse_example(m, name, lineno,ip2py)
457 self._parse_example(m, name, lineno,ip2py)
456
458
457 # Append the random-output marker (it defaults to empty in most
459 # Append the random-output marker (it defaults to empty in most
458 # cases, it's only non-empty for 'all-random' tests):
460 # cases, it's only non-empty for 'all-random' tests):
459 want += random_marker
461 want += random_marker
460
462
461 if Example is IPExternalExample:
463 if Example is IPExternalExample:
462 options[doctest.NORMALIZE_WHITESPACE] = True
464 options[doctest.NORMALIZE_WHITESPACE] = True
463 want += '\n'
465 want += '\n'
464
466
465 # Create an Example, and add it to the list.
467 # Create an Example, and add it to the list.
466 if not self._IS_BLANK_OR_COMMENT(source):
468 if not self._IS_BLANK_OR_COMMENT(source):
467 output.append(Example(source, want, exc_msg,
469 output.append(Example(source, want, exc_msg,
468 lineno=lineno,
470 lineno=lineno,
469 indent=min_indent+len(m.group('indent')),
471 indent=min_indent+len(m.group('indent')),
470 options=options))
472 options=options))
471 # Update lineno (lines inside this example)
473 # Update lineno (lines inside this example)
472 lineno += string.count('\n', m.start(), m.end())
474 lineno += string.count('\n', m.start(), m.end())
473 # Update charno.
475 # Update charno.
474 charno = m.end()
476 charno = m.end()
475 # Add any remaining post-example text to `output`.
477 # Add any remaining post-example text to `output`.
476 output.append(string[charno:])
478 output.append(string[charno:])
477 return output
479 return output
478
480
479 def _parse_example(self, m, name, lineno,ip2py=False):
481 def _parse_example(self, m, name, lineno,ip2py=False):
480 """
482 """
481 Given a regular expression match from `_EXAMPLE_RE` (`m`),
483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
482 return a pair `(source, want)`, where `source` is the matched
484 return a pair `(source, want)`, where `source` is the matched
483 example's source code (with prompts and indentation stripped);
485 example's source code (with prompts and indentation stripped);
484 and `want` is the example's expected output (with indentation
486 and `want` is the example's expected output (with indentation
485 stripped).
487 stripped).
486
488
487 `name` is the string's name, and `lineno` is the line number
489 `name` is the string's name, and `lineno` is the line number
488 where the example starts; both are used for error messages.
490 where the example starts; both are used for error messages.
489
491
490 Optional:
492 Optional:
491 `ip2py`: if true, filter the input via IPython to convert the syntax
493 `ip2py`: if true, filter the input via IPython to convert the syntax
492 into valid python.
494 into valid python.
493 """
495 """
494
496
495 # Get the example's indentation level.
497 # Get the example's indentation level.
496 indent = len(m.group('indent'))
498 indent = len(m.group('indent'))
497
499
498 # Divide source into lines; check that they're properly
500 # Divide source into lines; check that they're properly
499 # indented; and then strip their indentation & prompts.
501 # indented; and then strip their indentation & prompts.
500 source_lines = m.group('source').split('\n')
502 source_lines = m.group('source').split('\n')
501
503
502 # We're using variable-length input prompts
504 # We're using variable-length input prompts
503 ps1 = m.group('ps1')
505 ps1 = m.group('ps1')
504 ps2 = m.group('ps2')
506 ps2 = m.group('ps2')
505 ps1_len = len(ps1)
507 ps1_len = len(ps1)
506
508
507 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
508 if ps2:
510 if ps2:
509 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
510
512
511 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
512
514
513 if ip2py:
515 if ip2py:
514 # Convert source input from IPython into valid Python syntax
516 # Convert source input from IPython into valid Python syntax
515 source = self.ip2py(source)
517 source = self.ip2py(source)
516
518
517 # Divide want into lines; check that it's properly indented; and
519 # Divide want into lines; check that it's properly indented; and
518 # then strip the indentation. Spaces before the last newline should
520 # then strip the indentation. Spaces before the last newline should
519 # be preserved, so plain rstrip() isn't good enough.
521 # be preserved, so plain rstrip() isn't good enough.
520 want = m.group('want')
522 want = m.group('want')
521 want_lines = want.split('\n')
523 want_lines = want.split('\n')
522 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
523 del want_lines[-1] # forget final newline & spaces after it
525 del want_lines[-1] # forget final newline & spaces after it
524 self._check_prefix(want_lines, ' '*indent, name,
526 self._check_prefix(want_lines, ' '*indent, name,
525 lineno + len(source_lines))
527 lineno + len(source_lines))
526
528
527 # Remove ipython output prompt that might be present in the first line
529 # Remove ipython output prompt that might be present in the first line
528 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
529
531
530 want = '\n'.join([wl[indent:] for wl in want_lines])
532 want = '\n'.join([wl[indent:] for wl in want_lines])
531
533
532 # If `want` contains a traceback message, then extract it.
534 # If `want` contains a traceback message, then extract it.
533 m = self._EXCEPTION_RE.match(want)
535 m = self._EXCEPTION_RE.match(want)
534 if m:
536 if m:
535 exc_msg = m.group('msg')
537 exc_msg = m.group('msg')
536 else:
538 else:
537 exc_msg = None
539 exc_msg = None
538
540
539 # Extract options from the source.
541 # Extract options from the source.
540 options = self._find_options(source, name, lineno)
542 options = self._find_options(source, name, lineno)
541
543
542 return source, options, want, exc_msg
544 return source, options, want, exc_msg
543
545
544 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
545 """
547 """
546 Given the lines of a source string (including prompts and
548 Given the lines of a source string (including prompts and
547 leading indentation), check to make sure that every prompt is
549 leading indentation), check to make sure that every prompt is
548 followed by a space character. If any line is not followed by
550 followed by a space character. If any line is not followed by
549 a space character, then raise ValueError.
551 a space character, then raise ValueError.
550
552
551 Note: IPython-modified version which takes the input prompt length as a
553 Note: IPython-modified version which takes the input prompt length as a
552 parameter, so that prompts of variable length can be dealt with.
554 parameter, so that prompts of variable length can be dealt with.
553 """
555 """
554 space_idx = indent+ps1_len
556 space_idx = indent+ps1_len
555 min_len = space_idx+1
557 min_len = space_idx+1
556 for i, line in enumerate(lines):
558 for i, line in enumerate(lines):
557 if len(line) >= min_len and line[space_idx] != ' ':
559 if len(line) >= min_len and line[space_idx] != ' ':
558 raise ValueError('line %r of the docstring for %s '
560 raise ValueError('line %r of the docstring for %s '
559 'lacks blank after %s: %r' %
561 'lacks blank after %s: %r' %
560 (lineno+i+1, name,
562 (lineno+i+1, name,
561 line[indent:space_idx], line))
563 line[indent:space_idx], line))
562
564
563
565
564 SKIP = doctest.register_optionflag('SKIP')
566 SKIP = doctest.register_optionflag('SKIP')
565
567
566
568
567 class IPDocTestRunner(doctest.DocTestRunner,object):
569 class IPDocTestRunner(doctest.DocTestRunner,object):
568 """Test runner that synchronizes the IPython namespace with test globals.
570 """Test runner that synchronizes the IPython namespace with test globals.
569 """
571 """
570
572
571 def run(self, test, compileflags=None, out=None, clear_globs=True):
573 def run(self, test, compileflags=None, out=None, clear_globs=True):
572
574
573 # Hack: ipython needs access to the execution context of the example,
575 # Hack: ipython needs access to the execution context of the example,
574 # so that it can propagate user variables loaded by %run into
576 # so that it can propagate user variables loaded by %run into
575 # test.globs. We put them here into our modified %run as a function
577 # test.globs. We put them here into our modified %run as a function
576 # attribute. Our new %run will then only make the namespace update
578 # attribute. Our new %run will then only make the namespace update
577 # when called (rather than unconditionally updating test.globs here
579 # when called (rather than unconditionally updating test.globs here
578 # for all examples, most of which won't be calling %run anyway).
580 # for all examples, most of which won't be calling %run anyway).
579 #_ip._ipdoctest_test_globs = test.globs
581 #_ip._ipdoctest_test_globs = test.globs
580 #_ip._ipdoctest_test_filename = test.filename
582 #_ip._ipdoctest_test_filename = test.filename
581
583
582 test.globs.update(_ip.user_ns)
584 test.globs.update(_ip.user_ns)
583
585
584 # Override terminal size to standardise traceback format
586 # Override terminal size to standardise traceback format
585 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
587 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
586 return super(IPDocTestRunner,self).run(test,
588 return super(IPDocTestRunner,self).run(test,
587 compileflags,out,clear_globs)
589 compileflags,out,clear_globs)
588
590
589
591
590 class DocFileCase(doctest.DocFileCase):
592 class DocFileCase(doctest.DocFileCase):
591 """Overrides to provide filename
593 """Overrides to provide filename
592 """
594 """
593 def address(self):
595 def address(self):
594 return (self._dt_test.filename, None, None)
596 return (self._dt_test.filename, None, None)
595
597
596
598
597 class ExtensionDoctest(doctests.Doctest):
599 class ExtensionDoctest(doctests.Doctest):
598 """Nose Plugin that supports doctests in extension modules.
600 """Nose Plugin that supports doctests in extension modules.
599 """
601 """
600 name = 'extdoctest' # call nosetests with --with-extdoctest
602 name = 'extdoctest' # call nosetests with --with-extdoctest
601 enabled = True
603 enabled = True
602
604
603 def options(self, parser, env=os.environ):
605 def options(self, parser, env=os.environ):
604 Plugin.options(self, parser, env)
606 Plugin.options(self, parser, env)
605 parser.add_option('--doctest-tests', action='store_true',
607 parser.add_option('--doctest-tests', action='store_true',
606 dest='doctest_tests',
608 dest='doctest_tests',
607 default=env.get('NOSE_DOCTEST_TESTS',True),
609 default=env.get('NOSE_DOCTEST_TESTS',True),
608 help="Also look for doctests in test modules. "
610 help="Also look for doctests in test modules. "
609 "Note that classes, methods and functions should "
611 "Note that classes, methods and functions should "
610 "have either doctests or non-doctest tests, "
612 "have either doctests or non-doctest tests, "
611 "not both. [NOSE_DOCTEST_TESTS]")
613 "not both. [NOSE_DOCTEST_TESTS]")
612 parser.add_option('--doctest-extension', action="append",
614 parser.add_option('--doctest-extension', action="append",
613 dest="doctestExtension",
615 dest="doctestExtension",
614 help="Also look for doctests in files with "
616 help="Also look for doctests in files with "
615 "this extension [NOSE_DOCTEST_EXTENSION]")
617 "this extension [NOSE_DOCTEST_EXTENSION]")
616 # Set the default as a list, if given in env; otherwise
618 # Set the default as a list, if given in env; otherwise
617 # an additional value set on the command line will cause
619 # an additional value set on the command line will cause
618 # an error.
620 # an error.
619 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
621 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
620 if env_setting is not None:
622 if env_setting is not None:
621 parser.set_defaults(doctestExtension=tolist(env_setting))
623 parser.set_defaults(doctestExtension=tolist(env_setting))
622
624
623
625
624 def configure(self, options, config):
626 def configure(self, options, config):
625 Plugin.configure(self, options, config)
627 Plugin.configure(self, options, config)
626 # Pull standard doctest plugin out of config; we will do doctesting
628 # Pull standard doctest plugin out of config; we will do doctesting
627 config.plugins.plugins = [p for p in config.plugins.plugins
629 config.plugins.plugins = [p for p in config.plugins.plugins
628 if p.name != 'doctest']
630 if p.name != 'doctest']
629 self.doctest_tests = options.doctest_tests
631 self.doctest_tests = options.doctest_tests
630 self.extension = tolist(options.doctestExtension)
632 self.extension = tolist(options.doctestExtension)
631
633
632 self.parser = doctest.DocTestParser()
634 self.parser = doctest.DocTestParser()
633 self.finder = DocTestFinder()
635 self.finder = DocTestFinder()
634 self.checker = IPDoctestOutputChecker()
636 self.checker = IPDoctestOutputChecker()
635 self.globs = None
637 self.globs = None
636 self.extraglobs = None
638 self.extraglobs = None
637
639
638
640
639 def loadTestsFromExtensionModule(self,filename):
641 def loadTestsFromExtensionModule(self,filename):
640 bpath,mod = os.path.split(filename)
642 bpath,mod = os.path.split(filename)
641 modname = os.path.splitext(mod)[0]
643 modname = os.path.splitext(mod)[0]
642 try:
644 try:
643 sys.path.append(bpath)
645 sys.path.append(bpath)
644 module = import_module(modname)
646 module = import_module(modname)
645 tests = list(self.loadTestsFromModule(module))
647 tests = list(self.loadTestsFromModule(module))
646 finally:
648 finally:
647 sys.path.pop()
649 sys.path.pop()
648 return tests
650 return tests
649
651
650 # NOTE: the method below is almost a copy of the original one in nose, with
652 # NOTE: the method below is almost a copy of the original one in nose, with
651 # a few modifications to control output checking.
653 # a few modifications to control output checking.
652
654
653 def loadTestsFromModule(self, module):
655 def loadTestsFromModule(self, module):
654 #print '*** ipdoctest - lTM',module # dbg
656 #print '*** ipdoctest - lTM',module # dbg
655
657
656 if not self.matches(module.__name__):
658 if not self.matches(module.__name__):
657 log.debug("Doctest doesn't want module %s", module)
659 log.debug("Doctest doesn't want module %s", module)
658 return
660 return
659
661
660 tests = self.finder.find(module,globs=self.globs,
662 tests = self.finder.find(module,globs=self.globs,
661 extraglobs=self.extraglobs)
663 extraglobs=self.extraglobs)
662 if not tests:
664 if not tests:
663 return
665 return
664
666
665 # always use whitespace and ellipsis options
667 # always use whitespace and ellipsis options
666 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
668 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
667
669
668 tests.sort()
670 tests.sort()
669 module_file = module.__file__
671 module_file = module.__file__
670 if module_file[-4:] in ('.pyc', '.pyo'):
672 if module_file[-4:] in ('.pyc', '.pyo'):
671 module_file = module_file[:-1]
673 module_file = module_file[:-1]
672 for test in tests:
674 for test in tests:
673 if not test.examples:
675 if not test.examples:
674 continue
676 continue
675 if not test.filename:
677 if not test.filename:
676 test.filename = module_file
678 test.filename = module_file
677
679
678 yield DocTestCase(test,
680 yield DocTestCase(test,
679 optionflags=optionflags,
681 optionflags=optionflags,
680 checker=self.checker)
682 checker=self.checker)
681
683
682
684
683 def loadTestsFromFile(self, filename):
685 def loadTestsFromFile(self, filename):
684 #print "ipdoctest - from file", filename # dbg
686 #print "ipdoctest - from file", filename # dbg
685 if is_extension_module(filename):
687 if is_extension_module(filename):
686 for t in self.loadTestsFromExtensionModule(filename):
688 for t in self.loadTestsFromExtensionModule(filename):
687 yield t
689 yield t
688 else:
690 else:
689 if self.extension and anyp(filename.endswith, self.extension):
691 if self.extension and anyp(filename.endswith, self.extension):
690 name = os.path.basename(filename)
692 name = PurePath(filename).name
691 with open(filename) as dh:
693 doc = Path(filename).read_text()
692 doc = dh.read()
693 test = self.parser.get_doctest(
694 test = self.parser.get_doctest(
694 doc, globs={'__file__': filename}, name=name,
695 doc, globs={'__file__': filename}, name=name,
695 filename=filename, lineno=0)
696 filename=filename, lineno=0)
696 if test.examples:
697 if test.examples:
697 #print 'FileCase:',test.examples # dbg
698 #print 'FileCase:',test.examples # dbg
698 yield DocFileCase(test)
699 yield DocFileCase(test)
699 else:
700 else:
700 yield False # no tests to load
701 yield False # no tests to load
701
702
702
703
703 class IPythonDoctest(ExtensionDoctest):
704 class IPythonDoctest(ExtensionDoctest):
704 """Nose Plugin that supports doctests in extension modules.
705 """Nose Plugin that supports doctests in extension modules.
705 """
706 """
706 name = 'ipdoctest' # call nosetests with --with-ipdoctest
707 name = 'ipdoctest' # call nosetests with --with-ipdoctest
707 enabled = True
708 enabled = True
708
709
709 def makeTest(self, obj, parent):
710 def makeTest(self, obj, parent):
710 """Look for doctests in the given object, which will be a
711 """Look for doctests in the given object, which will be a
711 function, method or class.
712 function, method or class.
712 """
713 """
713 #print 'Plugin analyzing:', obj, parent # dbg
714 #print 'Plugin analyzing:', obj, parent # dbg
714 # always use whitespace and ellipsis options
715 # always use whitespace and ellipsis options
715 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
716 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
716
717
717 doctests = self.finder.find(obj, module=getmodule(parent))
718 doctests = self.finder.find(obj, module=getmodule(parent))
718 if doctests:
719 if doctests:
719 for test in doctests:
720 for test in doctests:
720 if len(test.examples) == 0:
721 if len(test.examples) == 0:
721 continue
722 continue
722
723
723 yield DocTestCase(test, obj=obj,
724 yield DocTestCase(test, obj=obj,
724 optionflags=optionflags,
725 optionflags=optionflags,
725 checker=self.checker)
726 checker=self.checker)
726
727
727 def options(self, parser, env=os.environ):
728 def options(self, parser, env=os.environ):
728 #print "Options for nose plugin:", self.name # dbg
729 #print "Options for nose plugin:", self.name # dbg
729 Plugin.options(self, parser, env)
730 Plugin.options(self, parser, env)
730 parser.add_option('--ipdoctest-tests', action='store_true',
731 parser.add_option('--ipdoctest-tests', action='store_true',
731 dest='ipdoctest_tests',
732 dest='ipdoctest_tests',
732 default=env.get('NOSE_IPDOCTEST_TESTS',True),
733 default=env.get('NOSE_IPDOCTEST_TESTS',True),
733 help="Also look for doctests in test modules. "
734 help="Also look for doctests in test modules. "
734 "Note that classes, methods and functions should "
735 "Note that classes, methods and functions should "
735 "have either doctests or non-doctest tests, "
736 "have either doctests or non-doctest tests, "
736 "not both. [NOSE_IPDOCTEST_TESTS]")
737 "not both. [NOSE_IPDOCTEST_TESTS]")
737 parser.add_option('--ipdoctest-extension', action="append",
738 parser.add_option('--ipdoctest-extension', action="append",
738 dest="ipdoctest_extension",
739 dest="ipdoctest_extension",
739 help="Also look for doctests in files with "
740 help="Also look for doctests in files with "
740 "this extension [NOSE_IPDOCTEST_EXTENSION]")
741 "this extension [NOSE_IPDOCTEST_EXTENSION]")
741 # Set the default as a list, if given in env; otherwise
742 # Set the default as a list, if given in env; otherwise
742 # an additional value set on the command line will cause
743 # an additional value set on the command line will cause
743 # an error.
744 # an error.
744 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
745 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
745 if env_setting is not None:
746 if env_setting is not None:
746 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
747 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
747
748
748 def configure(self, options, config):
749 def configure(self, options, config):
749 #print "Configuring nose plugin:", self.name # dbg
750 #print "Configuring nose plugin:", self.name # dbg
750 Plugin.configure(self, options, config)
751 Plugin.configure(self, options, config)
751 # Pull standard doctest plugin out of config; we will do doctesting
752 # Pull standard doctest plugin out of config; we will do doctesting
752 config.plugins.plugins = [p for p in config.plugins.plugins
753 config.plugins.plugins = [p for p in config.plugins.plugins
753 if p.name != 'doctest']
754 if p.name != 'doctest']
754 self.doctest_tests = options.ipdoctest_tests
755 self.doctest_tests = options.ipdoctest_tests
755 self.extension = tolist(options.ipdoctest_extension)
756 self.extension = tolist(options.ipdoctest_extension)
756
757
757 self.parser = IPDocTestParser()
758 self.parser = IPDocTestParser()
758 self.finder = DocTestFinder(parser=self.parser)
759 self.finder = DocTestFinder(parser=self.parser)
759 self.checker = IPDoctestOutputChecker()
760 self.checker = IPDoctestOutputChecker()
760 self.globs = None
761 self.globs = None
761 self.extraglobs = None
762 self.extraglobs = None
General Comments 0
You need to be logged in to leave comments. Login now