##// END OF EJS Templates
Override makeTest to correctly support path/to/test.py:test_function syntax....
Fernando Perez -
Show More
@@ -1,820 +1,837 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 nodoc(object):
190 def __init__(self,obj):
190 def __init__(self,obj):
191 self.obj = obj
191 self.obj = obj
192
192
193 def __getattribute__(self,key):
193 def __getattribute__(self,key):
194 if key == '__doc__':
194 if key == '__doc__':
195 return None
195 return None
196 else:
196 else:
197 return getattr(object.__getattribute__(self,'obj'),key)
197 return getattr(object.__getattribute__(self,'obj'),key)
198
198
199 # Modified version of the one in the stdlib, that fixes a python bug (doctests
199 # 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)
200 # not found in extension modules, http://bugs.python.org/issue3158)
201 class DocTestFinder(doctest.DocTestFinder):
201 class DocTestFinder(doctest.DocTestFinder):
202
202
203 def _from_module(self, module, object):
203 def _from_module(self, module, object):
204 """
204 """
205 Return true if the given object is defined in the given
205 Return true if the given object is defined in the given
206 module.
206 module.
207 """
207 """
208 if module is None:
208 if module is None:
209 return True
209 return True
210 elif inspect.isfunction(object):
210 elif inspect.isfunction(object):
211 return module.__dict__ is object.func_globals
211 return module.__dict__ is object.func_globals
212 elif inspect.isbuiltin(object):
212 elif inspect.isbuiltin(object):
213 return module.__name__ == object.__module__
213 return module.__name__ == object.__module__
214 elif inspect.isclass(object):
214 elif inspect.isclass(object):
215 return module.__name__ == object.__module__
215 return module.__name__ == object.__module__
216 elif inspect.ismethod(object):
216 elif inspect.ismethod(object):
217 # This one may be a bug in cython that fails to correctly set the
217 # 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
218 # __module__ attribute of methods, but since the same error is easy
219 # to make by extension code writers, having this safety in place
219 # to make by extension code writers, having this safety in place
220 # isn't such a bad idea
220 # isn't such a bad idea
221 return module.__name__ == object.im_class.__module__
221 return module.__name__ == object.im_class.__module__
222 elif inspect.getmodule(object) is not None:
222 elif inspect.getmodule(object) is not None:
223 return module is inspect.getmodule(object)
223 return module is inspect.getmodule(object)
224 elif hasattr(object, '__module__'):
224 elif hasattr(object, '__module__'):
225 return module.__name__ == object.__module__
225 return module.__name__ == object.__module__
226 elif isinstance(object, property):
226 elif isinstance(object, property):
227 return True # [XX] no way not be sure.
227 return True # [XX] no way not be sure.
228 else:
228 else:
229 raise ValueError("object must be a class or function")
229 raise ValueError("object must be a class or function")
230
230
231 def _find(self, tests, obj, name, module, source_lines, globs, seen):
231 def _find(self, tests, obj, name, module, source_lines, globs, seen):
232 """
232 """
233 Find tests for the given object and any contained objects, and
233 Find tests for the given object and any contained objects, and
234 add them to `tests`.
234 add them to `tests`.
235 """
235 """
236
236
237 if hasattr(obj,"skip_doctest"):
237 if hasattr(obj,"skip_doctest"):
238 #print 'SKIPPING DOCTEST FOR:',obj # dbg
238 #print 'SKIPPING DOCTEST FOR:',obj # dbg
239 obj = nodoc(obj)
239 obj = nodoc(obj)
240
240
241 doctest.DocTestFinder._find(self,tests, obj, name, module,
241 doctest.DocTestFinder._find(self,tests, obj, name, module,
242 source_lines, globs, seen)
242 source_lines, globs, seen)
243
243
244 # Below we re-run pieces of the above method with manual modifications,
244 # Below we re-run pieces of the above method with manual modifications,
245 # because the original code is buggy and fails to correctly identify
245 # because the original code is buggy and fails to correctly identify
246 # doctests in extension modules.
246 # doctests in extension modules.
247
247
248 # Local shorthands
248 # Local shorthands
249 from inspect import isroutine, isclass, ismodule
249 from inspect import isroutine, isclass, ismodule
250
250
251 # Look for tests in a module's contained objects.
251 # Look for tests in a module's contained objects.
252 if inspect.ismodule(obj) and self._recurse:
252 if inspect.ismodule(obj) and self._recurse:
253 for valname, val in obj.__dict__.items():
253 for valname, val in obj.__dict__.items():
254 valname1 = '%s.%s' % (name, valname)
254 valname1 = '%s.%s' % (name, valname)
255 if ( (isroutine(val) or isclass(val))
255 if ( (isroutine(val) or isclass(val))
256 and self._from_module(module, val) ):
256 and self._from_module(module, val) ):
257
257
258 self._find(tests, val, valname1, module, source_lines,
258 self._find(tests, val, valname1, module, source_lines,
259 globs, seen)
259 globs, seen)
260
260
261 # Look for tests in a class's contained objects.
261 # Look for tests in a class's contained objects.
262 if inspect.isclass(obj) and self._recurse:
262 if inspect.isclass(obj) and self._recurse:
263 #print 'RECURSE into class:',obj # dbg
263 #print 'RECURSE into class:',obj # dbg
264 for valname, val in obj.__dict__.items():
264 for valname, val in obj.__dict__.items():
265 # Special handling for staticmethod/classmethod.
265 # Special handling for staticmethod/classmethod.
266 if isinstance(val, staticmethod):
266 if isinstance(val, staticmethod):
267 val = getattr(obj, valname)
267 val = getattr(obj, valname)
268 if isinstance(val, classmethod):
268 if isinstance(val, classmethod):
269 val = getattr(obj, valname).im_func
269 val = getattr(obj, valname).im_func
270
270
271 # Recurse to methods, properties, and nested classes.
271 # Recurse to methods, properties, and nested classes.
272 if ((inspect.isfunction(val) or inspect.isclass(val) or
272 if ((inspect.isfunction(val) or inspect.isclass(val) or
273 inspect.ismethod(val) or
273 inspect.ismethod(val) or
274 isinstance(val, property)) and
274 isinstance(val, property)) and
275 self._from_module(module, val)):
275 self._from_module(module, val)):
276 valname = '%s.%s' % (name, valname)
276 valname = '%s.%s' % (name, valname)
277 self._find(tests, val, valname, module, source_lines,
277 self._find(tests, val, valname, module, source_lines,
278 globs, seen)
278 globs, seen)
279
279
280
280
281 class IPDoctestOutputChecker(doctest.OutputChecker):
281 class IPDoctestOutputChecker(doctest.OutputChecker):
282 """Second-chance checker with support for random tests.
282 """Second-chance checker with support for random tests.
283
283
284 If the default comparison doesn't pass, this checker looks in the expected
284 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.
285 output string for flags that tell us to ignore the output.
286 """
286 """
287
287
288 random_re = re.compile(r'#\s*random\s+')
288 random_re = re.compile(r'#\s*random\s+')
289
289
290 def check_output(self, want, got, optionflags):
290 def check_output(self, want, got, optionflags):
291 """Check output, accepting special markers embedded in the output.
291 """Check output, accepting special markers embedded in the output.
292
292
293 If the output didn't pass the default validation but the special string
293 If the output didn't pass the default validation but the special string
294 '#random' is included, we accept it."""
294 '#random' is included, we accept it."""
295
295
296 # Let the original tester verify first, in case people have valid tests
296 # Let the original tester verify first, in case people have valid tests
297 # that happen to have a comment saying '#random' embedded in.
297 # that happen to have a comment saying '#random' embedded in.
298 ret = doctest.OutputChecker.check_output(self, want, got,
298 ret = doctest.OutputChecker.check_output(self, want, got,
299 optionflags)
299 optionflags)
300 if not ret and self.random_re.search(want):
300 if not ret and self.random_re.search(want):
301 #print >> sys.stderr, 'RANDOM OK:',want # dbg
301 #print >> sys.stderr, 'RANDOM OK:',want # dbg
302 return True
302 return True
303
303
304 return ret
304 return ret
305
305
306
306
307 class DocTestCase(doctests.DocTestCase):
307 class DocTestCase(doctests.DocTestCase):
308 """Proxy for DocTestCase: provides an address() method that
308 """Proxy for DocTestCase: provides an address() method that
309 returns the correct address for the doctest case. Otherwise
309 returns the correct address for the doctest case. Otherwise
310 acts as a proxy to the test case. To provide hints for address(),
310 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
311 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.
312 for purposes of determining the test address, if it is provided.
313 """
313 """
314
314
315 # Note: this method was taken from numpy's nosetester module.
315 # Note: this method was taken from numpy's nosetester module.
316
316
317 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
317 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
318 # its constructor that blocks non-default arguments from being passed
318 # its constructor that blocks non-default arguments from being passed
319 # down into doctest.DocTestCase
319 # down into doctest.DocTestCase
320
320
321 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
321 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
322 checker=None, obj=None, result_var='_'):
322 checker=None, obj=None, result_var='_'):
323 self._result_var = result_var
323 self._result_var = result_var
324 doctests.DocTestCase.__init__(self, test,
324 doctests.DocTestCase.__init__(self, test,
325 optionflags=optionflags,
325 optionflags=optionflags,
326 setUp=setUp, tearDown=tearDown,
326 setUp=setUp, tearDown=tearDown,
327 checker=checker)
327 checker=checker)
328 # Now we must actually copy the original constructor from the stdlib
328 # 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
329 # doctest class, because we can't call it directly and a bug in nose
330 # means it never gets passed the right arguments.
330 # means it never gets passed the right arguments.
331
331
332 self._dt_optionflags = optionflags
332 self._dt_optionflags = optionflags
333 self._dt_checker = checker
333 self._dt_checker = checker
334 self._dt_test = test
334 self._dt_test = test
335 self._dt_setUp = setUp
335 self._dt_setUp = setUp
336 self._dt_tearDown = tearDown
336 self._dt_tearDown = tearDown
337
337
338 # XXX - store this runner once in the object!
338 # XXX - store this runner once in the object!
339 runner = IPDocTestRunner(optionflags=optionflags,
339 runner = IPDocTestRunner(optionflags=optionflags,
340 checker=checker, verbose=False)
340 checker=checker, verbose=False)
341 self._dt_runner = runner
341 self._dt_runner = runner
342
342
343
343
344 # Each doctest should remember what directory it was loaded from...
344 # Each doctest should remember what directory it was loaded from...
345 self._ori_dir = os.getcwd()
345 self._ori_dir = os.getcwd()
346
346
347 # Modified runTest from the default stdlib
347 # Modified runTest from the default stdlib
348 def runTest(self):
348 def runTest(self):
349 test = self._dt_test
349 test = self._dt_test
350 runner = self._dt_runner
350 runner = self._dt_runner
351
351
352 old = sys.stdout
352 old = sys.stdout
353 new = StringIO()
353 new = StringIO()
354 optionflags = self._dt_optionflags
354 optionflags = self._dt_optionflags
355
355
356 if not (optionflags & REPORTING_FLAGS):
356 if not (optionflags & REPORTING_FLAGS):
357 # The option flags don't include any reporting flags,
357 # The option flags don't include any reporting flags,
358 # so add the default reporting flags
358 # so add the default reporting flags
359 optionflags |= _unittest_reportflags
359 optionflags |= _unittest_reportflags
360
360
361 try:
361 try:
362 # Save our current directory and switch out to the one where the
362 # Save our current directory and switch out to the one where the
363 # test was originally created, in case another doctest did a
363 # test was originally created, in case another doctest did a
364 # directory change. We'll restore this in the finally clause.
364 # directory change. We'll restore this in the finally clause.
365 curdir = os.getcwd()
365 curdir = os.getcwd()
366 os.chdir(self._ori_dir)
366 os.chdir(self._ori_dir)
367
367
368 runner.DIVIDER = "-"*70
368 runner.DIVIDER = "-"*70
369 failures, tries = runner.run(test,out=new.write,
369 failures, tries = runner.run(test,out=new.write,
370 clear_globs=False)
370 clear_globs=False)
371 finally:
371 finally:
372 sys.stdout = old
372 sys.stdout = old
373 os.chdir(curdir)
373 os.chdir(curdir)
374
374
375 if failures:
375 if failures:
376 raise self.failureException(self.format_failure(new.getvalue()))
376 raise self.failureException(self.format_failure(new.getvalue()))
377
377
378 def setUp(self):
378 def setUp(self):
379 """Modified test setup that syncs with ipython namespace"""
379 """Modified test setup that syncs with ipython namespace"""
380
380
381 if isinstance(self._dt_test.examples[0],IPExample):
381 if isinstance(self._dt_test.examples[0],IPExample):
382 # for IPython examples *only*, we swap the globals with the ipython
382 # for IPython examples *only*, we swap the globals with the ipython
383 # namespace, after updating it with the globals (which doctest
383 # namespace, after updating it with the globals (which doctest
384 # fills with the necessary info from the module being tested).
384 # fills with the necessary info from the module being tested).
385 _ip.IP.user_ns.update(self._dt_test.globs)
385 _ip.IP.user_ns.update(self._dt_test.globs)
386 self._dt_test.globs = _ip.IP.user_ns
386 self._dt_test.globs = _ip.IP.user_ns
387
387
388 doctests.DocTestCase.setUp(self)
388 doctests.DocTestCase.setUp(self)
389
389
390
390
391
391
392 # A simple subclassing of the original with a different class name, so we can
392 # 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.
393 # distinguish and treat differently IPython examples from pure python ones.
394 class IPExample(doctest.Example): pass
394 class IPExample(doctest.Example): pass
395
395
396
396
397 class IPExternalExample(doctest.Example):
397 class IPExternalExample(doctest.Example):
398 """Doctest examples to be run in an external process."""
398 """Doctest examples to be run in an external process."""
399
399
400 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
400 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
401 options=None):
401 options=None):
402 # Parent constructor
402 # Parent constructor
403 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
403 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
404
404
405 # An EXTRA newline is needed to prevent pexpect hangs
405 # An EXTRA newline is needed to prevent pexpect hangs
406 self.source += '\n'
406 self.source += '\n'
407
407
408
408
409 class IPDocTestParser(doctest.DocTestParser):
409 class IPDocTestParser(doctest.DocTestParser):
410 """
410 """
411 A class used to parse strings containing doctest examples.
411 A class used to parse strings containing doctest examples.
412
412
413 Note: This is a version modified to properly recognize IPython input and
413 Note: This is a version modified to properly recognize IPython input and
414 convert any IPython examples into valid Python ones.
414 convert any IPython examples into valid Python ones.
415 """
415 """
416 # This regular expression is used to find doctest examples in a
416 # This regular expression is used to find doctest examples in a
417 # string. It defines three groups: `source` is the source code
417 # string. It defines three groups: `source` is the source code
418 # (including leading indentation and prompts); `indent` is the
418 # (including leading indentation and prompts); `indent` is the
419 # indentation of the first (PS1) line of the source code; and
419 # indentation of the first (PS1) line of the source code; and
420 # `want` is the expected output (including leading indentation).
420 # `want` is the expected output (including leading indentation).
421
421
422 # Classic Python prompts or default IPython ones
422 # Classic Python prompts or default IPython ones
423 _PS1_PY = r'>>>'
423 _PS1_PY = r'>>>'
424 _PS2_PY = r'\.\.\.'
424 _PS2_PY = r'\.\.\.'
425
425
426 _PS1_IP = r'In\ \[\d+\]:'
426 _PS1_IP = r'In\ \[\d+\]:'
427 _PS2_IP = r'\ \ \ \.\.\.+:'
427 _PS2_IP = r'\ \ \ \.\.\.+:'
428
428
429 _RE_TPL = r'''
429 _RE_TPL = r'''
430 # Source consists of a PS1 line followed by zero or more PS2 lines.
430 # Source consists of a PS1 line followed by zero or more PS2 lines.
431 (?P<source>
431 (?P<source>
432 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
432 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
433 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
433 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
434 \n? # a newline
434 \n? # a newline
435 # Want consists of any non-blank lines that do not start with PS1.
435 # Want consists of any non-blank lines that do not start with PS1.
436 (?P<want> (?:(?![ ]*$) # Not a blank line
436 (?P<want> (?:(?![ ]*$) # Not a blank line
437 (?![ ]*%s) # Not a line starting with PS1
437 (?![ ]*%s) # Not a line starting with PS1
438 (?![ ]*%s) # Not a line starting with PS2
438 (?![ ]*%s) # Not a line starting with PS2
439 .*$\n? # But any other line
439 .*$\n? # But any other line
440 )*)
440 )*)
441 '''
441 '''
442
442
443 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
443 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
444 re.MULTILINE | re.VERBOSE)
444 re.MULTILINE | re.VERBOSE)
445
445
446 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
446 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
447 re.MULTILINE | re.VERBOSE)
447 re.MULTILINE | re.VERBOSE)
448
448
449 # Mark a test as being fully random. In this case, we simply append the
449 # 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
450 # random marker ('#random') to each individual example's output. This way
451 # we don't need to modify any other code.
451 # we don't need to modify any other code.
452 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
452 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
453
453
454 # Mark tests to be executed in an external process - currently unsupported.
454 # Mark tests to be executed in an external process - currently unsupported.
455 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
455 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
456
456
457 def ip2py(self,source):
457 def ip2py(self,source):
458 """Convert input IPython source into valid Python."""
458 """Convert input IPython source into valid Python."""
459 out = []
459 out = []
460 newline = out.append
460 newline = out.append
461 for lnum,line in enumerate(source.splitlines()):
461 for lnum,line in enumerate(source.splitlines()):
462 newline(_ip.IP.prefilter(line,lnum>0))
462 newline(_ip.IP.prefilter(line,lnum>0))
463 newline('') # ensure a closing newline, needed by doctest
463 newline('') # ensure a closing newline, needed by doctest
464 #print "PYSRC:", '\n'.join(out) # dbg
464 #print "PYSRC:", '\n'.join(out) # dbg
465 return '\n'.join(out)
465 return '\n'.join(out)
466
466
467 def parse(self, string, name='<string>'):
467 def parse(self, string, name='<string>'):
468 """
468 """
469 Divide the given string into examples and intervening text,
469 Divide the given string into examples and intervening text,
470 and return them as a list of alternating Examples and strings.
470 and return them as a list of alternating Examples and strings.
471 Line numbers for the Examples are 0-based. The optional
471 Line numbers for the Examples are 0-based. The optional
472 argument `name` is a name identifying this string, and is only
472 argument `name` is a name identifying this string, and is only
473 used for error messages.
473 used for error messages.
474 """
474 """
475
475
476 #print 'Parse string:\n',string # dbg
476 #print 'Parse string:\n',string # dbg
477
477
478 string = string.expandtabs()
478 string = string.expandtabs()
479 # If all lines begin with the same indentation, then strip it.
479 # If all lines begin with the same indentation, then strip it.
480 min_indent = self._min_indent(string)
480 min_indent = self._min_indent(string)
481 if min_indent > 0:
481 if min_indent > 0:
482 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
482 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
483
483
484 output = []
484 output = []
485 charno, lineno = 0, 0
485 charno, lineno = 0, 0
486
486
487 # We make 'all random' tests by adding the '# random' mark to every
487 # We make 'all random' tests by adding the '# random' mark to every
488 # block of output in the test.
488 # block of output in the test.
489 if self._RANDOM_TEST.search(string):
489 if self._RANDOM_TEST.search(string):
490 random_marker = '\n# random'
490 random_marker = '\n# random'
491 else:
491 else:
492 random_marker = ''
492 random_marker = ''
493
493
494 # Whether to convert the input from ipython to python syntax
494 # Whether to convert the input from ipython to python syntax
495 ip2py = False
495 ip2py = False
496 # Find all doctest examples in the string. First, try them as Python
496 # Find all doctest examples in the string. First, try them as Python
497 # examples, then as IPython ones
497 # examples, then as IPython ones
498 terms = list(self._EXAMPLE_RE_PY.finditer(string))
498 terms = list(self._EXAMPLE_RE_PY.finditer(string))
499 if terms:
499 if terms:
500 # Normal Python example
500 # Normal Python example
501 #print '-'*70 # dbg
501 #print '-'*70 # dbg
502 #print 'PyExample, Source:\n',string # dbg
502 #print 'PyExample, Source:\n',string # dbg
503 #print '-'*70 # dbg
503 #print '-'*70 # dbg
504 Example = doctest.Example
504 Example = doctest.Example
505 else:
505 else:
506 # It's an ipython example. Note that IPExamples are run
506 # It's an ipython example. Note that IPExamples are run
507 # in-process, so their syntax must be turned into valid python.
507 # in-process, so their syntax must be turned into valid python.
508 # IPExternalExamples are run out-of-process (via pexpect) so they
508 # IPExternalExamples are run out-of-process (via pexpect) so they
509 # don't need any filtering (a real ipython will be executing them).
509 # don't need any filtering (a real ipython will be executing them).
510 terms = list(self._EXAMPLE_RE_IP.finditer(string))
510 terms = list(self._EXAMPLE_RE_IP.finditer(string))
511 if self._EXTERNAL_IP.search(string):
511 if self._EXTERNAL_IP.search(string):
512 #print '-'*70 # dbg
512 #print '-'*70 # dbg
513 #print 'IPExternalExample, Source:\n',string # dbg
513 #print 'IPExternalExample, Source:\n',string # dbg
514 #print '-'*70 # dbg
514 #print '-'*70 # dbg
515 Example = IPExternalExample
515 Example = IPExternalExample
516 else:
516 else:
517 #print '-'*70 # dbg
517 #print '-'*70 # dbg
518 #print 'IPExample, Source:\n',string # dbg
518 #print 'IPExample, Source:\n',string # dbg
519 #print '-'*70 # dbg
519 #print '-'*70 # dbg
520 Example = IPExample
520 Example = IPExample
521 ip2py = True
521 ip2py = True
522
522
523 for m in terms:
523 for m in terms:
524 # Add the pre-example text to `output`.
524 # Add the pre-example text to `output`.
525 output.append(string[charno:m.start()])
525 output.append(string[charno:m.start()])
526 # Update lineno (lines before this example)
526 # Update lineno (lines before this example)
527 lineno += string.count('\n', charno, m.start())
527 lineno += string.count('\n', charno, m.start())
528 # Extract info from the regexp match.
528 # Extract info from the regexp match.
529 (source, options, want, exc_msg) = \
529 (source, options, want, exc_msg) = \
530 self._parse_example(m, name, lineno,ip2py)
530 self._parse_example(m, name, lineno,ip2py)
531
531
532 # Append the random-output marker (it defaults to empty in most
532 # Append the random-output marker (it defaults to empty in most
533 # cases, it's only non-empty for 'all-random' tests):
533 # cases, it's only non-empty for 'all-random' tests):
534 want += random_marker
534 want += random_marker
535
535
536 if Example is IPExternalExample:
536 if Example is IPExternalExample:
537 options[doctest.NORMALIZE_WHITESPACE] = True
537 options[doctest.NORMALIZE_WHITESPACE] = True
538 want += '\n'
538 want += '\n'
539
539
540 # Create an Example, and add it to the list.
540 # Create an Example, and add it to the list.
541 if not self._IS_BLANK_OR_COMMENT(source):
541 if not self._IS_BLANK_OR_COMMENT(source):
542 output.append(Example(source, want, exc_msg,
542 output.append(Example(source, want, exc_msg,
543 lineno=lineno,
543 lineno=lineno,
544 indent=min_indent+len(m.group('indent')),
544 indent=min_indent+len(m.group('indent')),
545 options=options))
545 options=options))
546 # Update lineno (lines inside this example)
546 # Update lineno (lines inside this example)
547 lineno += string.count('\n', m.start(), m.end())
547 lineno += string.count('\n', m.start(), m.end())
548 # Update charno.
548 # Update charno.
549 charno = m.end()
549 charno = m.end()
550 # Add any remaining post-example text to `output`.
550 # Add any remaining post-example text to `output`.
551 output.append(string[charno:])
551 output.append(string[charno:])
552 return output
552 return output
553
553
554 def _parse_example(self, m, name, lineno,ip2py=False):
554 def _parse_example(self, m, name, lineno,ip2py=False):
555 """
555 """
556 Given a regular expression match from `_EXAMPLE_RE` (`m`),
556 Given a regular expression match from `_EXAMPLE_RE` (`m`),
557 return a pair `(source, want)`, where `source` is the matched
557 return a pair `(source, want)`, where `source` is the matched
558 example's source code (with prompts and indentation stripped);
558 example's source code (with prompts and indentation stripped);
559 and `want` is the example's expected output (with indentation
559 and `want` is the example's expected output (with indentation
560 stripped).
560 stripped).
561
561
562 `name` is the string's name, and `lineno` is the line number
562 `name` is the string's name, and `lineno` is the line number
563 where the example starts; both are used for error messages.
563 where the example starts; both are used for error messages.
564
564
565 Optional:
565 Optional:
566 `ip2py`: if true, filter the input via IPython to convert the syntax
566 `ip2py`: if true, filter the input via IPython to convert the syntax
567 into valid python.
567 into valid python.
568 """
568 """
569
569
570 # Get the example's indentation level.
570 # Get the example's indentation level.
571 indent = len(m.group('indent'))
571 indent = len(m.group('indent'))
572
572
573 # Divide source into lines; check that they're properly
573 # Divide source into lines; check that they're properly
574 # indented; and then strip their indentation & prompts.
574 # indented; and then strip their indentation & prompts.
575 source_lines = m.group('source').split('\n')
575 source_lines = m.group('source').split('\n')
576
576
577 # We're using variable-length input prompts
577 # We're using variable-length input prompts
578 ps1 = m.group('ps1')
578 ps1 = m.group('ps1')
579 ps2 = m.group('ps2')
579 ps2 = m.group('ps2')
580 ps1_len = len(ps1)
580 ps1_len = len(ps1)
581
581
582 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
582 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
583 if ps2:
583 if ps2:
584 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
584 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
585
585
586 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
586 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
587
587
588 if ip2py:
588 if ip2py:
589 # Convert source input from IPython into valid Python syntax
589 # Convert source input from IPython into valid Python syntax
590 source = self.ip2py(source)
590 source = self.ip2py(source)
591
591
592 # Divide want into lines; check that it's properly indented; and
592 # Divide want into lines; check that it's properly indented; and
593 # then strip the indentation. Spaces before the last newline should
593 # then strip the indentation. Spaces before the last newline should
594 # be preserved, so plain rstrip() isn't good enough.
594 # be preserved, so plain rstrip() isn't good enough.
595 want = m.group('want')
595 want = m.group('want')
596 want_lines = want.split('\n')
596 want_lines = want.split('\n')
597 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
597 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
598 del want_lines[-1] # forget final newline & spaces after it
598 del want_lines[-1] # forget final newline & spaces after it
599 self._check_prefix(want_lines, ' '*indent, name,
599 self._check_prefix(want_lines, ' '*indent, name,
600 lineno + len(source_lines))
600 lineno + len(source_lines))
601
601
602 # Remove ipython output prompt that might be present in the first line
602 # 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])
603 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
604
604
605 want = '\n'.join([wl[indent:] for wl in want_lines])
605 want = '\n'.join([wl[indent:] for wl in want_lines])
606
606
607 # If `want` contains a traceback message, then extract it.
607 # If `want` contains a traceback message, then extract it.
608 m = self._EXCEPTION_RE.match(want)
608 m = self._EXCEPTION_RE.match(want)
609 if m:
609 if m:
610 exc_msg = m.group('msg')
610 exc_msg = m.group('msg')
611 else:
611 else:
612 exc_msg = None
612 exc_msg = None
613
613
614 # Extract options from the source.
614 # Extract options from the source.
615 options = self._find_options(source, name, lineno)
615 options = self._find_options(source, name, lineno)
616
616
617 return source, options, want, exc_msg
617 return source, options, want, exc_msg
618
618
619 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
619 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
620 """
620 """
621 Given the lines of a source string (including prompts and
621 Given the lines of a source string (including prompts and
622 leading indentation), check to make sure that every prompt is
622 leading indentation), check to make sure that every prompt is
623 followed by a space character. If any line is not followed by
623 followed by a space character. If any line is not followed by
624 a space character, then raise ValueError.
624 a space character, then raise ValueError.
625
625
626 Note: IPython-modified version which takes the input prompt length as a
626 Note: IPython-modified version which takes the input prompt length as a
627 parameter, so that prompts of variable length can be dealt with.
627 parameter, so that prompts of variable length can be dealt with.
628 """
628 """
629 space_idx = indent+ps1_len
629 space_idx = indent+ps1_len
630 min_len = space_idx+1
630 min_len = space_idx+1
631 for i, line in enumerate(lines):
631 for i, line in enumerate(lines):
632 if len(line) >= min_len and line[space_idx] != ' ':
632 if len(line) >= min_len and line[space_idx] != ' ':
633 raise ValueError('line %r of the docstring for %s '
633 raise ValueError('line %r of the docstring for %s '
634 'lacks blank after %s: %r' %
634 'lacks blank after %s: %r' %
635 (lineno+i+1, name,
635 (lineno+i+1, name,
636 line[indent:space_idx], line))
636 line[indent:space_idx], line))
637
637
638
638
639 SKIP = doctest.register_optionflag('SKIP')
639 SKIP = doctest.register_optionflag('SKIP')
640
640
641
641
642 class IPDocTestRunner(doctest.DocTestRunner,object):
642 class IPDocTestRunner(doctest.DocTestRunner,object):
643 """Test runner that synchronizes the IPython namespace with test globals.
643 """Test runner that synchronizes the IPython namespace with test globals.
644 """
644 """
645
645
646 def run(self, test, compileflags=None, out=None, clear_globs=True):
646 def run(self, test, compileflags=None, out=None, clear_globs=True):
647
647
648 # Hack: ipython needs access to the execution context of the example,
648 # Hack: ipython needs access to the execution context of the example,
649 # so that it can propagate user variables loaded by %run into
649 # 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
650 # 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
651 # attribute. Our new %run will then only make the namespace update
652 # when called (rather than unconconditionally updating test.globs here
652 # when called (rather than unconconditionally updating test.globs here
653 # for all examples, most of which won't be calling %run anyway).
653 # for all examples, most of which won't be calling %run anyway).
654 _run_ns_sync.test_globs = test.globs
654 _run_ns_sync.test_globs = test.globs
655 _run_ns_sync.test_filename = test.filename
655 _run_ns_sync.test_filename = test.filename
656
656
657 return super(IPDocTestRunner,self).run(test,
657 return super(IPDocTestRunner,self).run(test,
658 compileflags,out,clear_globs)
658 compileflags,out,clear_globs)
659
659
660
660
661 class DocFileCase(doctest.DocFileCase):
661 class DocFileCase(doctest.DocFileCase):
662 """Overrides to provide filename
662 """Overrides to provide filename
663 """
663 """
664 def address(self):
664 def address(self):
665 return (self._dt_test.filename, None, None)
665 return (self._dt_test.filename, None, None)
666
666
667
667
668 class ExtensionDoctest(doctests.Doctest):
668 class ExtensionDoctest(doctests.Doctest):
669 """Nose Plugin that supports doctests in extension modules.
669 """Nose Plugin that supports doctests in extension modules.
670 """
670 """
671 name = 'extdoctest' # call nosetests with --with-extdoctest
671 name = 'extdoctest' # call nosetests with --with-extdoctest
672 enabled = True
672 enabled = True
673
673
674 def options(self, parser, env=os.environ):
674 def options(self, parser, env=os.environ):
675 Plugin.options(self, parser, env)
675 Plugin.options(self, parser, env)
676 parser.add_option('--doctest-tests', action='store_true',
676 parser.add_option('--doctest-tests', action='store_true',
677 dest='doctest_tests',
677 dest='doctest_tests',
678 default=env.get('NOSE_DOCTEST_TESTS',True),
678 default=env.get('NOSE_DOCTEST_TESTS',True),
679 help="Also look for doctests in test modules. "
679 help="Also look for doctests in test modules. "
680 "Note that classes, methods and functions should "
680 "Note that classes, methods and functions should "
681 "have either doctests or non-doctest tests, "
681 "have either doctests or non-doctest tests, "
682 "not both. [NOSE_DOCTEST_TESTS]")
682 "not both. [NOSE_DOCTEST_TESTS]")
683 parser.add_option('--doctest-extension', action="append",
683 parser.add_option('--doctest-extension', action="append",
684 dest="doctestExtension",
684 dest="doctestExtension",
685 help="Also look for doctests in files with "
685 help="Also look for doctests in files with "
686 "this extension [NOSE_DOCTEST_EXTENSION]")
686 "this extension [NOSE_DOCTEST_EXTENSION]")
687 # Set the default as a list, if given in env; otherwise
687 # Set the default as a list, if given in env; otherwise
688 # an additional value set on the command line will cause
688 # an additional value set on the command line will cause
689 # an error.
689 # an error.
690 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
690 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
691 if env_setting is not None:
691 if env_setting is not None:
692 parser.set_defaults(doctestExtension=tolist(env_setting))
692 parser.set_defaults(doctestExtension=tolist(env_setting))
693
693
694
694
695 def configure(self, options, config):
695 def configure(self, options, config):
696 Plugin.configure(self, options, config)
696 Plugin.configure(self, options, config)
697 self.doctest_tests = options.doctest_tests
697 self.doctest_tests = options.doctest_tests
698 self.extension = tolist(options.doctestExtension)
698 self.extension = tolist(options.doctestExtension)
699
699
700 self.parser = doctest.DocTestParser()
700 self.parser = doctest.DocTestParser()
701 self.finder = DocTestFinder()
701 self.finder = DocTestFinder()
702 self.checker = IPDoctestOutputChecker()
702 self.checker = IPDoctestOutputChecker()
703 self.globs = None
703 self.globs = None
704 self.extraglobs = None
704 self.extraglobs = None
705
705
706
706
707 def loadTestsFromExtensionModule(self,filename):
707 def loadTestsFromExtensionModule(self,filename):
708 bpath,mod = os.path.split(filename)
708 bpath,mod = os.path.split(filename)
709 modname = os.path.splitext(mod)[0]
709 modname = os.path.splitext(mod)[0]
710 try:
710 try:
711 sys.path.append(bpath)
711 sys.path.append(bpath)
712 module = __import__(modname)
712 module = __import__(modname)
713 tests = list(self.loadTestsFromModule(module))
713 tests = list(self.loadTestsFromModule(module))
714 finally:
714 finally:
715 sys.path.pop()
715 sys.path.pop()
716 return tests
716 return tests
717
717
718 # NOTE: the method below is almost a copy of the original one in nose, with
718 # NOTE: the method below is almost a copy of the original one in nose, with
719 # a few modifications to control output checking.
719 # a few modifications to control output checking.
720
720
721 def loadTestsFromModule(self, module):
721 def loadTestsFromModule(self, module):
722 #print '*** ipdoctest - lTM',module # dbg
722 #print '*** ipdoctest - lTM',module # dbg
723
723
724 if not self.matches(module.__name__):
724 if not self.matches(module.__name__):
725 log.debug("Doctest doesn't want module %s", module)
725 log.debug("Doctest doesn't want module %s", module)
726 return
726 return
727
727
728 tests = self.finder.find(module,globs=self.globs,
728 tests = self.finder.find(module,globs=self.globs,
729 extraglobs=self.extraglobs)
729 extraglobs=self.extraglobs)
730 if not tests:
730 if not tests:
731 return
731 return
732
732
733 # always use whitespace and ellipsis options
733 # always use whitespace and ellipsis options
734 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
734 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
735
735
736 tests.sort()
736 tests.sort()
737 module_file = module.__file__
737 module_file = module.__file__
738 if module_file[-4:] in ('.pyc', '.pyo'):
738 if module_file[-4:] in ('.pyc', '.pyo'):
739 module_file = module_file[:-1]
739 module_file = module_file[:-1]
740 for test in tests:
740 for test in tests:
741 if not test.examples:
741 if not test.examples:
742 continue
742 continue
743 if not test.filename:
743 if not test.filename:
744 test.filename = module_file
744 test.filename = module_file
745
745
746 yield DocTestCase(test,
746 yield DocTestCase(test,
747 optionflags=optionflags,
747 optionflags=optionflags,
748 checker=self.checker)
748 checker=self.checker)
749
749
750
750
751 def loadTestsFromFile(self, filename):
751 def loadTestsFromFile(self, filename):
752 if is_extension_module(filename):
752 if is_extension_module(filename):
753 for t in self.loadTestsFromExtensionModule(filename):
753 for t in self.loadTestsFromExtensionModule(filename):
754 yield t
754 yield t
755 else:
755 else:
756 if self.extension and anyp(filename.endswith, self.extension):
756 if self.extension and anyp(filename.endswith, self.extension):
757 name = os.path.basename(filename)
757 name = os.path.basename(filename)
758 dh = open(filename)
758 dh = open(filename)
759 try:
759 try:
760 doc = dh.read()
760 doc = dh.read()
761 finally:
761 finally:
762 dh.close()
762 dh.close()
763 test = self.parser.get_doctest(
763 test = self.parser.get_doctest(
764 doc, globs={'__file__': filename}, name=name,
764 doc, globs={'__file__': filename}, name=name,
765 filename=filename, lineno=0)
765 filename=filename, lineno=0)
766 if test.examples:
766 if test.examples:
767 #print 'FileCase:',test.examples # dbg
767 #print 'FileCase:',test.examples # dbg
768 yield DocFileCase(test)
768 yield DocFileCase(test)
769 else:
769 else:
770 yield False # no tests to load
770 yield False # no tests to load
771
771
772 def wantFile(self,filename):
772 def wantFile(self,filename):
773 """Return whether the given filename should be scanned for tests.
773 """Return whether the given filename should be scanned for tests.
774
774
775 Modified version that accepts extension modules as valid containers for
775 Modified version that accepts extension modules as valid containers for
776 doctests.
776 doctests.
777 """
777 """
778 #print '*** ipdoctest- wantFile:',filename # dbg
778 #print '*** ipdoctest- wantFile:',filename # dbg
779
779
780 # XXX - temporarily hardcoded list, will move to driver later
780 # XXX - temporarily hardcoded list, will move to driver later
781 exclude = ['IPython/external/',
781 exclude = ['IPython/external/',
782 'IPython/platutils_win32',
782 'IPython/platutils_win32',
783 'IPython/frontend/cocoa',
783 'IPython/frontend/cocoa',
784 'IPython_doctest_plugin',
784 'IPython_doctest_plugin',
785 'IPython/Gnuplot',
785 'IPython/Gnuplot',
786 'IPython/Extensions/ipy_',
786 'IPython/Extensions/ipy_',
787 'IPython/Extensions/PhysicalQIn',
787 'IPython/Extensions/PhysicalQIn',
788 'IPython/Extensions/scitedirector',
788 'IPython/Extensions/scitedirector',
789 'IPython/testing/plugin',
789 'IPython/testing/plugin',
790 ]
790 ]
791
791
792 for fex in exclude:
792 for fex in exclude:
793 if fex in filename: # substring
793 if fex in filename: # substring
794 #print '###>>> SKIP:',filename # dbg
794 #print '###>>> SKIP:',filename # dbg
795 return False
795 return False
796
796
797 if is_extension_module(filename):
797 if is_extension_module(filename):
798 return True
798 return True
799 else:
799 else:
800 return doctests.Doctest.wantFile(self,filename)
800 return doctests.Doctest.wantFile(self,filename)
801
801
802
802
803 class IPythonDoctest(ExtensionDoctest):
803 class IPythonDoctest(ExtensionDoctest):
804 """Nose Plugin that supports doctests in extension modules.
804 """Nose Plugin that supports doctests in extension modules.
805 """
805 """
806 name = 'ipdoctest' # call nosetests with --with-ipdoctest
806 name = 'ipdoctest' # call nosetests with --with-ipdoctest
807 enabled = True
807 enabled = True
808
808
809 def makeTest(self, obj, parent):
810 """Look for doctests in the given object, which will be a
811 function, method or class.
812 """
813 # always use whitespace and ellipsis options
814 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
815
816 doctests = self.finder.find(obj, module=getmodule(parent))
817 if doctests:
818 for test in doctests:
819 if len(test.examples) == 0:
820 continue
821
822 yield DocTestCase(test, obj=obj,
823 optionflags=optionflags,
824 checker=self.checker)
825
809 def configure(self, options, config):
826 def configure(self, options, config):
810
827
811 Plugin.configure(self, options, config)
828 Plugin.configure(self, options, config)
812 self.doctest_tests = options.doctest_tests
829 self.doctest_tests = options.doctest_tests
813 self.extension = tolist(options.doctestExtension)
830 self.extension = tolist(options.doctestExtension)
814
831
815 self.parser = IPDocTestParser()
832 self.parser = IPDocTestParser()
816 self.finder = DocTestFinder(parser=self.parser)
833 self.finder = DocTestFinder(parser=self.parser)
817 self.checker = IPDoctestOutputChecker()
834 self.checker = IPDoctestOutputChecker()
818 self.globs = None
835 self.globs = None
819 self.extraglobs = None
836 self.extraglobs = None
820
837
General Comments 0
You need to be logged in to leave comments. Login now