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