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