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