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