##// END OF EJS Templates
BF - allow nose with-doctest setting in environment...
Matthew Brett -
Show More
@@ -1,443 +1,437 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) recursively. This
8 calling this script (with different arguments) 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 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009 The IPython Development Team
18 # Copyright (C) 2009 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 # Stdlib
28 # Stdlib
29 import os
29 import os
30 import os.path as path
30 import os.path as path
31 import signal
31 import signal
32 import sys
32 import sys
33 import subprocess
33 import subprocess
34 import tempfile
34 import tempfile
35 import time
35 import time
36 import warnings
36 import warnings
37
37
38 # Note: monkeypatch!
38 # Note: monkeypatch!
39 # We need to monkeypatch a small problem in nose itself first, before importing
39 # We need to monkeypatch a small problem in nose itself first, before importing
40 # it for actual use. This should get into nose upstream, but its release cycle
40 # it for actual use. This should get into nose upstream, but its release cycle
41 # is slow and we need it for our parametric tests to work correctly.
41 # is slow and we need it for our parametric tests to work correctly.
42 from IPython.testing import nosepatch
42 from IPython.testing import nosepatch
43 # Now, proceed to import nose itself
43 # Now, proceed to import nose itself
44 import nose.plugins.builtin
44 import nose.plugins.builtin
45 from nose.core import TestProgram
45 from nose.core import TestProgram
46
46
47 # Our own imports
47 # Our own imports
48 from IPython.utils.path import get_ipython_module_path
48 from IPython.utils.path import get_ipython_module_path
49 from IPython.utils.process import find_cmd, pycmd2argv
49 from IPython.utils.process import find_cmd, pycmd2argv
50 from IPython.utils.sysinfo import sys_info
50 from IPython.utils.sysinfo import sys_info
51
51
52 from IPython.testing import globalipapp
52 from IPython.testing import globalipapp
53 from IPython.testing.plugin.ipdoctest import IPythonDoctest
53 from IPython.testing.plugin.ipdoctest import IPythonDoctest
54 from IPython.external.decorators import KnownFailure
54 from IPython.external.decorators import KnownFailure
55
55
56 pjoin = path.join
56 pjoin = path.join
57
57
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Warnings control
65 # Warnings control
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 # Twisted generates annoying warnings with Python 2.6, as will do other code
68 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 # that imports 'sets' as of today
69 # that imports 'sets' as of today
70 warnings.filterwarnings('ignore', 'the sets module is deprecated',
70 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 DeprecationWarning )
71 DeprecationWarning )
72
72
73 # This one also comes from Twisted
73 # This one also comes from Twisted
74 warnings.filterwarnings('ignore', 'the sha module is deprecated',
74 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 DeprecationWarning)
75 DeprecationWarning)
76
76
77 # Wx on Fedora11 spits these out
77 # Wx on Fedora11 spits these out
78 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
78 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 UserWarning)
79 UserWarning)
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Logic for skipping doctests
82 # Logic for skipping doctests
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 def test_for(mod, min_version=None):
85 def test_for(mod, min_version=None):
86 """Test to see if mod is importable."""
86 """Test to see if mod is importable."""
87 try:
87 try:
88 __import__(mod)
88 __import__(mod)
89 except (ImportError, RuntimeError):
89 except (ImportError, RuntimeError):
90 # GTK reports Runtime error if it can't be initialized even if it's
90 # GTK reports Runtime error if it can't be initialized even if it's
91 # importable.
91 # importable.
92 return False
92 return False
93 else:
93 else:
94 if min_version:
94 if min_version:
95 return sys.modules[mod].__version__ >= min_version
95 return sys.modules[mod].__version__ >= min_version
96 else:
96 else:
97 return True
97 return True
98
98
99 # Global dict where we can store information on what we have and what we don't
99 # Global dict where we can store information on what we have and what we don't
100 # have available at test run time
100 # have available at test run time
101 have = {}
101 have = {}
102
102
103 have['curses'] = test_for('_curses')
103 have['curses'] = test_for('_curses')
104 have['matplotlib'] = test_for('matplotlib')
104 have['matplotlib'] = test_for('matplotlib')
105 have['pexpect'] = test_for('pexpect')
105 have['pexpect'] = test_for('pexpect')
106 have['pymongo'] = test_for('pymongo')
106 have['pymongo'] = test_for('pymongo')
107 have['wx'] = test_for('wx')
107 have['wx'] = test_for('wx')
108 have['wx.aui'] = test_for('wx.aui')
108 have['wx.aui'] = test_for('wx.aui')
109 if os.name == 'nt':
109 if os.name == 'nt':
110 have['zmq'] = test_for('zmq', '2.1.7')
110 have['zmq'] = test_for('zmq', '2.1.7')
111 else:
111 else:
112 have['zmq'] = test_for('zmq', '2.1.4')
112 have['zmq'] = test_for('zmq', '2.1.4')
113 have['qt'] = test_for('IPython.external.qt')
113 have['qt'] = test_for('IPython.external.qt')
114
114
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 # Functions and classes
116 # Functions and classes
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118
118
119 def report():
119 def report():
120 """Return a string with a summary report of test-related variables."""
120 """Return a string with a summary report of test-related variables."""
121
121
122 out = [ sys_info(), '\n']
122 out = [ sys_info(), '\n']
123
123
124 avail = []
124 avail = []
125 not_avail = []
125 not_avail = []
126
126
127 for k, is_avail in have.items():
127 for k, is_avail in have.items():
128 if is_avail:
128 if is_avail:
129 avail.append(k)
129 avail.append(k)
130 else:
130 else:
131 not_avail.append(k)
131 not_avail.append(k)
132
132
133 if avail:
133 if avail:
134 out.append('\nTools and libraries available at test time:\n')
134 out.append('\nTools and libraries available at test time:\n')
135 avail.sort()
135 avail.sort()
136 out.append(' ' + ' '.join(avail)+'\n')
136 out.append(' ' + ' '.join(avail)+'\n')
137
137
138 if not_avail:
138 if not_avail:
139 out.append('\nTools and libraries NOT available at test time:\n')
139 out.append('\nTools and libraries NOT available at test time:\n')
140 not_avail.sort()
140 not_avail.sort()
141 out.append(' ' + ' '.join(not_avail)+'\n')
141 out.append(' ' + ' '.join(not_avail)+'\n')
142
142
143 return ''.join(out)
143 return ''.join(out)
144
144
145
145
146 def make_exclude():
146 def make_exclude():
147 """Make patterns of modules and packages to exclude from testing.
147 """Make patterns of modules and packages to exclude from testing.
148
148
149 For the IPythonDoctest plugin, we need to exclude certain patterns that
149 For the IPythonDoctest plugin, we need to exclude certain patterns that
150 cause testing problems. We should strive to minimize the number of
150 cause testing problems. We should strive to minimize the number of
151 skipped modules, since this means untested code.
151 skipped modules, since this means untested code.
152
152
153 These modules and packages will NOT get scanned by nose at all for tests.
153 These modules and packages will NOT get scanned by nose at all for tests.
154 """
154 """
155 # Simple utility to make IPython paths more readably, we need a lot of
155 # Simple utility to make IPython paths more readably, we need a lot of
156 # these below
156 # these below
157 ipjoin = lambda *paths: pjoin('IPython', *paths)
157 ipjoin = lambda *paths: pjoin('IPython', *paths)
158
158
159 exclusions = [ipjoin('external'),
159 exclusions = [ipjoin('external'),
160 pjoin('IPython_doctest_plugin'),
160 pjoin('IPython_doctest_plugin'),
161 ipjoin('quarantine'),
161 ipjoin('quarantine'),
162 ipjoin('deathrow'),
162 ipjoin('deathrow'),
163 ipjoin('testing', 'attic'),
163 ipjoin('testing', 'attic'),
164 # This guy is probably attic material
164 # This guy is probably attic material
165 ipjoin('testing', 'mkdoctests'),
165 ipjoin('testing', 'mkdoctests'),
166 # Testing inputhook will need a lot of thought, to figure out
166 # Testing inputhook will need a lot of thought, to figure out
167 # how to have tests that don't lock up with the gui event
167 # how to have tests that don't lock up with the gui event
168 # loops in the picture
168 # loops in the picture
169 ipjoin('lib', 'inputhook'),
169 ipjoin('lib', 'inputhook'),
170 # Config files aren't really importable stand-alone
170 # Config files aren't really importable stand-alone
171 ipjoin('config', 'default'),
171 ipjoin('config', 'default'),
172 ipjoin('config', 'profile'),
172 ipjoin('config', 'profile'),
173 ]
173 ]
174
174
175 if not have['wx']:
175 if not have['wx']:
176 exclusions.append(ipjoin('lib', 'inputhookwx'))
176 exclusions.append(ipjoin('lib', 'inputhookwx'))
177
177
178 # We do this unconditionally, so that the test suite doesn't import
178 # We do this unconditionally, so that the test suite doesn't import
179 # gtk, changing the default encoding and masking some unicode bugs.
179 # gtk, changing the default encoding and masking some unicode bugs.
180 exclusions.append(ipjoin('lib', 'inputhookgtk'))
180 exclusions.append(ipjoin('lib', 'inputhookgtk'))
181
181
182 # These have to be skipped on win32 because the use echo, rm, cd, etc.
182 # These have to be skipped on win32 because the use echo, rm, cd, etc.
183 # See ticket https://github.com/ipython/ipython/issues/87
183 # See ticket https://github.com/ipython/ipython/issues/87
184 if sys.platform == 'win32':
184 if sys.platform == 'win32':
185 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
185 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
186 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
186 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
187
187
188 if not have['pexpect']:
188 if not have['pexpect']:
189 exclusions.extend([ipjoin('scripts', 'irunner'),
189 exclusions.extend([ipjoin('scripts', 'irunner'),
190 ipjoin('lib', 'irunner'),
190 ipjoin('lib', 'irunner'),
191 ipjoin('lib', 'tests', 'test_irunner')])
191 ipjoin('lib', 'tests', 'test_irunner')])
192
192
193 if not have['zmq']:
193 if not have['zmq']:
194 exclusions.append(ipjoin('zmq'))
194 exclusions.append(ipjoin('zmq'))
195 exclusions.append(ipjoin('frontend', 'qt'))
195 exclusions.append(ipjoin('frontend', 'qt'))
196 exclusions.append(ipjoin('parallel'))
196 exclusions.append(ipjoin('parallel'))
197 elif not have['qt']:
197 elif not have['qt']:
198 exclusions.append(ipjoin('frontend', 'qt'))
198 exclusions.append(ipjoin('frontend', 'qt'))
199
199
200 if not have['pymongo']:
200 if not have['pymongo']:
201 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
201 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
202 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
202 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
203
203
204 if not have['matplotlib']:
204 if not have['matplotlib']:
205 exclusions.extend([ipjoin('lib', 'pylabtools'),
205 exclusions.extend([ipjoin('lib', 'pylabtools'),
206 ipjoin('lib', 'tests', 'test_pylabtools')])
206 ipjoin('lib', 'tests', 'test_pylabtools')])
207
207
208 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
208 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
209 if sys.platform == 'win32':
209 if sys.platform == 'win32':
210 exclusions = [s.replace('\\','\\\\') for s in exclusions]
210 exclusions = [s.replace('\\','\\\\') for s in exclusions]
211
211
212 return exclusions
212 return exclusions
213
213
214
214
215 class IPTester(object):
215 class IPTester(object):
216 """Call that calls iptest or trial in a subprocess.
216 """Call that calls iptest or trial in a subprocess.
217 """
217 """
218 #: string, name of test runner that will be called
218 #: string, name of test runner that will be called
219 runner = None
219 runner = None
220 #: list, parameters for test runner
220 #: list, parameters for test runner
221 params = None
221 params = None
222 #: list, arguments of system call to be made to call test runner
222 #: list, arguments of system call to be made to call test runner
223 call_args = None
223 call_args = None
224 #: list, process ids of subprocesses we start (for cleanup)
224 #: list, process ids of subprocesses we start (for cleanup)
225 pids = None
225 pids = None
226
226
227 def __init__(self, runner='iptest', params=None):
227 def __init__(self, runner='iptest', params=None):
228 """Create new test runner."""
228 """Create new test runner."""
229 p = os.path
229 p = os.path
230 if runner == 'iptest':
230 if runner == 'iptest':
231 iptest_app = get_ipython_module_path('IPython.testing.iptest')
231 iptest_app = get_ipython_module_path('IPython.testing.iptest')
232 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
232 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
233 else:
233 else:
234 raise Exception('Not a valid test runner: %s' % repr(runner))
234 raise Exception('Not a valid test runner: %s' % repr(runner))
235 if params is None:
235 if params is None:
236 params = []
236 params = []
237 if isinstance(params, str):
237 if isinstance(params, str):
238 params = [params]
238 params = [params]
239 self.params = params
239 self.params = params
240
240
241 # Assemble call
241 # Assemble call
242 self.call_args = self.runner+self.params
242 self.call_args = self.runner+self.params
243
243
244 # Store pids of anything we start to clean up on deletion, if possible
244 # Store pids of anything we start to clean up on deletion, if possible
245 # (on posix only, since win32 has no os.kill)
245 # (on posix only, since win32 has no os.kill)
246 self.pids = []
246 self.pids = []
247
247
248 if sys.platform == 'win32':
248 if sys.platform == 'win32':
249 def _run_cmd(self):
249 def _run_cmd(self):
250 # On Windows, use os.system instead of subprocess.call, because I
250 # On Windows, use os.system instead of subprocess.call, because I
251 # was having problems with subprocess and I just don't know enough
251 # was having problems with subprocess and I just don't know enough
252 # about win32 to debug this reliably. Os.system may be the 'old
252 # about win32 to debug this reliably. Os.system may be the 'old
253 # fashioned' way to do it, but it works just fine. If someone
253 # fashioned' way to do it, but it works just fine. If someone
254 # later can clean this up that's fine, as long as the tests run
254 # later can clean this up that's fine, as long as the tests run
255 # reliably in win32.
255 # reliably in win32.
256 # What types of problems are you having. They may be related to
256 # What types of problems are you having. They may be related to
257 # running Python in unboffered mode. BG.
257 # running Python in unboffered mode. BG.
258 return os.system(' '.join(self.call_args))
258 return os.system(' '.join(self.call_args))
259 else:
259 else:
260 def _run_cmd(self):
260 def _run_cmd(self):
261 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
261 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
262 subp = subprocess.Popen(self.call_args)
262 subp = subprocess.Popen(self.call_args)
263 self.pids.append(subp.pid)
263 self.pids.append(subp.pid)
264 # If this fails, the pid will be left in self.pids and cleaned up
264 # If this fails, the pid will be left in self.pids and cleaned up
265 # later, but if the wait call succeeds, then we can clear the
265 # later, but if the wait call succeeds, then we can clear the
266 # stored pid.
266 # stored pid.
267 retcode = subp.wait()
267 retcode = subp.wait()
268 self.pids.pop()
268 self.pids.pop()
269 return retcode
269 return retcode
270
270
271 def run(self):
271 def run(self):
272 """Run the stored commands"""
272 """Run the stored commands"""
273 try:
273 try:
274 return self._run_cmd()
274 return self._run_cmd()
275 except:
275 except:
276 import traceback
276 import traceback
277 traceback.print_exc()
277 traceback.print_exc()
278 return 1 # signal failure
278 return 1 # signal failure
279
279
280 def __del__(self):
280 def __del__(self):
281 """Cleanup on exit by killing any leftover processes."""
281 """Cleanup on exit by killing any leftover processes."""
282
282
283 if not hasattr(os, 'kill'):
283 if not hasattr(os, 'kill'):
284 return
284 return
285
285
286 for pid in self.pids:
286 for pid in self.pids:
287 try:
287 try:
288 print 'Cleaning stale PID:', pid
288 print 'Cleaning stale PID:', pid
289 os.kill(pid, signal.SIGKILL)
289 os.kill(pid, signal.SIGKILL)
290 except OSError:
290 except OSError:
291 # This is just a best effort, if we fail or the process was
291 # This is just a best effort, if we fail or the process was
292 # really gone, ignore it.
292 # really gone, ignore it.
293 pass
293 pass
294
294
295
295
296 def make_runners():
296 def make_runners():
297 """Define the top-level packages that need to be tested.
297 """Define the top-level packages that need to be tested.
298 """
298 """
299
299
300 # Packages to be tested via nose, that only depend on the stdlib
300 # Packages to be tested via nose, that only depend on the stdlib
301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
302 'scripts', 'testing', 'utils' ]
302 'scripts', 'testing', 'utils' ]
303
303
304 if have['zmq']:
304 if have['zmq']:
305 nose_pkg_names.append('parallel')
305 nose_pkg_names.append('parallel')
306
306
307 # For debugging this code, only load quick stuff
307 # For debugging this code, only load quick stuff
308 #nose_pkg_names = ['core', 'extensions'] # dbg
308 #nose_pkg_names = ['core', 'extensions'] # dbg
309
309
310 # Make fully qualified package names prepending 'IPython.' to our name lists
310 # Make fully qualified package names prepending 'IPython.' to our name lists
311 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
311 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
312
312
313 # Make runners
313 # Make runners
314 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
314 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
315
315
316 return runners
316 return runners
317
317
318
318
319 def run_iptest():
319 def run_iptest():
320 """Run the IPython test suite using nose.
320 """Run the IPython test suite using nose.
321
321
322 This function is called when this script is **not** called with the form
322 This function is called when this script is **not** called with the form
323 `iptest all`. It simply calls nose with appropriate command line flags
323 `iptest all`. It simply calls nose with appropriate command line flags
324 and accepts all of the standard nose arguments.
324 and accepts all of the standard nose arguments.
325 """
325 """
326
326
327 warnings.filterwarnings('ignore',
327 warnings.filterwarnings('ignore',
328 'This will be removed soon. Use IPython.testing.util instead')
328 'This will be removed soon. Use IPython.testing.util instead')
329
329
330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
331
331
332 # Loading ipdoctest causes problems with Twisted, but
332 # Loading ipdoctest causes problems with Twisted, but
333 # our test suite runner now separates things and runs
333 # our test suite runner now separates things and runs
334 # all Twisted tests with trial.
334 # all Twisted tests with trial.
335 '--with-ipdoctest',
335 '--with-ipdoctest',
336 '--ipdoctest-tests','--ipdoctest-extension=txt',
336 '--ipdoctest-tests','--ipdoctest-extension=txt',
337
337
338 # We add --exe because of setuptools' imbecility (it
338 # We add --exe because of setuptools' imbecility (it
339 # blindly does chmod +x on ALL files). Nose does the
339 # blindly does chmod +x on ALL files). Nose does the
340 # right thing and it tries to avoid executables,
340 # right thing and it tries to avoid executables,
341 # setuptools unfortunately forces our hand here. This
341 # setuptools unfortunately forces our hand here. This
342 # has been discussed on the distutils list and the
342 # has been discussed on the distutils list and the
343 # setuptools devs refuse to fix this problem!
343 # setuptools devs refuse to fix this problem!
344 '--exe',
344 '--exe',
345 ]
345 ]
346
346
347 if nose.__version__ >= '0.11':
347 if nose.__version__ >= '0.11':
348 # I don't fully understand why we need this one, but depending on what
348 # I don't fully understand why we need this one, but depending on what
349 # directory the test suite is run from, if we don't give it, 0 tests
349 # directory the test suite is run from, if we don't give it, 0 tests
350 # get run. Specifically, if the test suite is run from the source dir
350 # get run. Specifically, if the test suite is run from the source dir
351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
352 # even if the same call done in this directory works fine). It appears
352 # even if the same call done in this directory works fine). It appears
353 # that if the requested package is in the current dir, nose bails early
353 # that if the requested package is in the current dir, nose bails early
354 # by default. Since it's otherwise harmless, leave it in by default
354 # by default. Since it's otherwise harmless, leave it in by default
355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
356 argv.append('--traverse-namespace')
356 argv.append('--traverse-namespace')
357
357
358 # Construct list of plugins, omitting the existing doctest plugin, which
358 # use our plugin for doctesting. It will remove the standard doctest plugin
359 # ours replaces (and extends).
359 # if it finds it enabled
360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
361 for p in nose.plugins.builtin.plugins:
362 plug = p()
363 if plug.name == 'doctest':
364 continue
365 plugins.append(plug)
366
367 # We need a global ipython running in this process
361 # We need a global ipython running in this process
368 globalipapp.start_ipython()
362 globalipapp.start_ipython()
369 # Now nose can run
363 # Now nose can run
370 TestProgram(argv=argv, plugins=plugins)
364 TestProgram(argv=argv, addplugins=plugins)
371
365
372
366
373 def run_iptestall():
367 def run_iptestall():
374 """Run the entire IPython test suite by calling nose and trial.
368 """Run the entire IPython test suite by calling nose and trial.
375
369
376 This function constructs :class:`IPTester` instances for all IPython
370 This function constructs :class:`IPTester` instances for all IPython
377 modules and package and then runs each of them. This causes the modules
371 modules and package and then runs each of them. This causes the modules
378 and packages of IPython to be tested each in their own subprocess using
372 and packages of IPython to be tested each in their own subprocess using
379 nose or twisted.trial appropriately.
373 nose or twisted.trial appropriately.
380 """
374 """
381
375
382 runners = make_runners()
376 runners = make_runners()
383
377
384 # Run the test runners in a temporary dir so we can nuke it when finished
378 # Run the test runners in a temporary dir so we can nuke it when finished
385 # to clean up any junk files left over by accident. This also makes it
379 # to clean up any junk files left over by accident. This also makes it
386 # robust against being run in non-writeable directories by mistake, as the
380 # robust against being run in non-writeable directories by mistake, as the
387 # temp dir will always be user-writeable.
381 # temp dir will always be user-writeable.
388 curdir = os.getcwdu()
382 curdir = os.getcwdu()
389 testdir = tempfile.gettempdir()
383 testdir = tempfile.gettempdir()
390 os.chdir(testdir)
384 os.chdir(testdir)
391
385
392 # Run all test runners, tracking execution time
386 # Run all test runners, tracking execution time
393 failed = []
387 failed = []
394 t_start = time.time()
388 t_start = time.time()
395 try:
389 try:
396 for (name, runner) in runners:
390 for (name, runner) in runners:
397 print '*'*70
391 print '*'*70
398 print 'IPython test group:',name
392 print 'IPython test group:',name
399 res = runner.run()
393 res = runner.run()
400 if res:
394 if res:
401 failed.append( (name, runner) )
395 failed.append( (name, runner) )
402 finally:
396 finally:
403 os.chdir(curdir)
397 os.chdir(curdir)
404 t_end = time.time()
398 t_end = time.time()
405 t_tests = t_end - t_start
399 t_tests = t_end - t_start
406 nrunners = len(runners)
400 nrunners = len(runners)
407 nfail = len(failed)
401 nfail = len(failed)
408 # summarize results
402 # summarize results
409 print
403 print
410 print '*'*70
404 print '*'*70
411 print 'Test suite completed for system with the following information:'
405 print 'Test suite completed for system with the following information:'
412 print report()
406 print report()
413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
407 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
414 print
408 print
415 print 'Status:'
409 print 'Status:'
416 if not failed:
410 if not failed:
417 print 'OK'
411 print 'OK'
418 else:
412 else:
419 # If anything went wrong, point out what command to rerun manually to
413 # If anything went wrong, point out what command to rerun manually to
420 # see the actual errors and individual summary
414 # see the actual errors and individual summary
421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
415 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
422 for name, failed_runner in failed:
416 for name, failed_runner in failed:
423 print '-'*40
417 print '-'*40
424 print 'Runner failed:',name
418 print 'Runner failed:',name
425 print 'You may wish to rerun this one individually, with:'
419 print 'You may wish to rerun this one individually, with:'
426 print ' '.join(failed_runner.call_args)
420 print ' '.join(failed_runner.call_args)
427 print
421 print
428 # Ensure that our exit code indicates failure
422 # Ensure that our exit code indicates failure
429 sys.exit(1)
423 sys.exit(1)
430
424
431
425
432 def main():
426 def main():
433 for arg in sys.argv[1:]:
427 for arg in sys.argv[1:]:
434 if arg.startswith('IPython'):
428 if arg.startswith('IPython'):
435 # This is in-process
429 # This is in-process
436 run_iptest()
430 run_iptest()
437 else:
431 else:
438 # This starts subprocesses
432 # This starts subprocesses
439 run_iptestall()
433 run_iptestall()
440
434
441
435
442 if __name__ == '__main__':
436 if __name__ == '__main__':
443 main()
437 main()
@@ -1,793 +1,799 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
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Classes and functions
58 # Classes and functions
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 def is_extension_module(filename):
61 def is_extension_module(filename):
62 """Return whether the given filename is an extension module.
62 """Return whether the given filename is an extension module.
63
63
64 This simply checks that the extension is either .so or .pyd.
64 This simply checks that the extension is either .so or .pyd.
65 """
65 """
66 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
66 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
67
67
68
68
69 class DocTestSkip(object):
69 class DocTestSkip(object):
70 """Object wrapper for doctests to be skipped."""
70 """Object wrapper for doctests to be skipped."""
71
71
72 ds_skip = """Doctest to skip.
72 ds_skip = """Doctest to skip.
73 >>> 1 #doctest: +SKIP
73 >>> 1 #doctest: +SKIP
74 """
74 """
75
75
76 def __init__(self,obj):
76 def __init__(self,obj):
77 self.obj = obj
77 self.obj = obj
78
78
79 def __getattribute__(self,key):
79 def __getattribute__(self,key):
80 if key == '__doc__':
80 if key == '__doc__':
81 return DocTestSkip.ds_skip
81 return DocTestSkip.ds_skip
82 else:
82 else:
83 return getattr(object.__getattribute__(self,'obj'),key)
83 return getattr(object.__getattribute__(self,'obj'),key)
84
84
85 # Modified version of the one in the stdlib, that fixes a python bug (doctests
85 # Modified version of the one in the stdlib, that fixes a python bug (doctests
86 # not found in extension modules, http://bugs.python.org/issue3158)
86 # not found in extension modules, http://bugs.python.org/issue3158)
87 class DocTestFinder(doctest.DocTestFinder):
87 class DocTestFinder(doctest.DocTestFinder):
88
88
89 def _from_module(self, module, object):
89 def _from_module(self, module, object):
90 """
90 """
91 Return true if the given object is defined in the given
91 Return true if the given object is defined in the given
92 module.
92 module.
93 """
93 """
94 if module is None:
94 if module is None:
95 return True
95 return True
96 elif inspect.isfunction(object):
96 elif inspect.isfunction(object):
97 return module.__dict__ is object.func_globals
97 return module.__dict__ is object.func_globals
98 elif inspect.isbuiltin(object):
98 elif inspect.isbuiltin(object):
99 return module.__name__ == object.__module__
99 return module.__name__ == object.__module__
100 elif inspect.isclass(object):
100 elif inspect.isclass(object):
101 return module.__name__ == object.__module__
101 return module.__name__ == object.__module__
102 elif inspect.ismethod(object):
102 elif inspect.ismethod(object):
103 # This one may be a bug in cython that fails to correctly set the
103 # This one may be a bug in cython that fails to correctly set the
104 # __module__ attribute of methods, but since the same error is easy
104 # __module__ attribute of methods, but since the same error is easy
105 # to make by extension code writers, having this safety in place
105 # to make by extension code writers, having this safety in place
106 # isn't such a bad idea
106 # isn't such a bad idea
107 return module.__name__ == object.im_class.__module__
107 return module.__name__ == object.im_class.__module__
108 elif inspect.getmodule(object) is not None:
108 elif inspect.getmodule(object) is not None:
109 return module is inspect.getmodule(object)
109 return module is inspect.getmodule(object)
110 elif hasattr(object, '__module__'):
110 elif hasattr(object, '__module__'):
111 return module.__name__ == object.__module__
111 return module.__name__ == object.__module__
112 elif isinstance(object, property):
112 elif isinstance(object, property):
113 return True # [XX] no way not be sure.
113 return True # [XX] no way not be sure.
114 else:
114 else:
115 raise ValueError("object must be a class or function")
115 raise ValueError("object must be a class or function")
116
116
117 def _find(self, tests, obj, name, module, source_lines, globs, seen):
117 def _find(self, tests, obj, name, module, source_lines, globs, seen):
118 """
118 """
119 Find tests for the given object and any contained objects, and
119 Find tests for the given object and any contained objects, and
120 add them to `tests`.
120 add them to `tests`.
121 """
121 """
122 #print '_find for:', obj, name, module # dbg
122 #print '_find for:', obj, name, module # dbg
123 if hasattr(obj,"skip_doctest"):
123 if hasattr(obj,"skip_doctest"):
124 #print 'SKIPPING DOCTEST FOR:',obj # dbg
124 #print 'SKIPPING DOCTEST FOR:',obj # dbg
125 obj = DocTestSkip(obj)
125 obj = DocTestSkip(obj)
126
126
127 doctest.DocTestFinder._find(self,tests, obj, name, module,
127 doctest.DocTestFinder._find(self,tests, obj, name, module,
128 source_lines, globs, seen)
128 source_lines, globs, seen)
129
129
130 # Below we re-run pieces of the above method with manual modifications,
130 # Below we re-run pieces of the above method with manual modifications,
131 # because the original code is buggy and fails to correctly identify
131 # because the original code is buggy and fails to correctly identify
132 # doctests in extension modules.
132 # doctests in extension modules.
133
133
134 # Local shorthands
134 # Local shorthands
135 from inspect import isroutine, isclass, ismodule
135 from inspect import isroutine, isclass, ismodule
136
136
137 # Look for tests in a module's contained objects.
137 # Look for tests in a module's contained objects.
138 if inspect.ismodule(obj) and self._recurse:
138 if inspect.ismodule(obj) and self._recurse:
139 for valname, val in obj.__dict__.items():
139 for valname, val in obj.__dict__.items():
140 valname1 = '%s.%s' % (name, valname)
140 valname1 = '%s.%s' % (name, valname)
141 if ( (isroutine(val) or isclass(val))
141 if ( (isroutine(val) or isclass(val))
142 and self._from_module(module, val) ):
142 and self._from_module(module, val) ):
143
143
144 self._find(tests, val, valname1, module, source_lines,
144 self._find(tests, val, valname1, module, source_lines,
145 globs, seen)
145 globs, seen)
146
146
147 # Look for tests in a class's contained objects.
147 # Look for tests in a class's contained objects.
148 if inspect.isclass(obj) and self._recurse:
148 if inspect.isclass(obj) and self._recurse:
149 #print 'RECURSE into class:',obj # dbg
149 #print 'RECURSE into class:',obj # dbg
150 for valname, val in obj.__dict__.items():
150 for valname, val in obj.__dict__.items():
151 # Special handling for staticmethod/classmethod.
151 # Special handling for staticmethod/classmethod.
152 if isinstance(val, staticmethod):
152 if isinstance(val, staticmethod):
153 val = getattr(obj, valname)
153 val = getattr(obj, valname)
154 if isinstance(val, classmethod):
154 if isinstance(val, classmethod):
155 val = getattr(obj, valname).im_func
155 val = getattr(obj, valname).im_func
156
156
157 # Recurse to methods, properties, and nested classes.
157 # Recurse to methods, properties, and nested classes.
158 if ((inspect.isfunction(val) or inspect.isclass(val) or
158 if ((inspect.isfunction(val) or inspect.isclass(val) or
159 inspect.ismethod(val) or
159 inspect.ismethod(val) or
160 isinstance(val, property)) and
160 isinstance(val, property)) and
161 self._from_module(module, val)):
161 self._from_module(module, val)):
162 valname = '%s.%s' % (name, valname)
162 valname = '%s.%s' % (name, valname)
163 self._find(tests, val, valname, module, source_lines,
163 self._find(tests, val, valname, module, source_lines,
164 globs, seen)
164 globs, seen)
165
165
166
166
167 class IPDoctestOutputChecker(doctest.OutputChecker):
167 class IPDoctestOutputChecker(doctest.OutputChecker):
168 """Second-chance checker with support for random tests.
168 """Second-chance checker with support for random tests.
169
169
170 If the default comparison doesn't pass, this checker looks in the expected
170 If the default comparison doesn't pass, this checker looks in the expected
171 output string for flags that tell us to ignore the output.
171 output string for flags that tell us to ignore the output.
172 """
172 """
173
173
174 random_re = re.compile(r'#\s*random\s+')
174 random_re = re.compile(r'#\s*random\s+')
175
175
176 def check_output(self, want, got, optionflags):
176 def check_output(self, want, got, optionflags):
177 """Check output, accepting special markers embedded in the output.
177 """Check output, accepting special markers embedded in the output.
178
178
179 If the output didn't pass the default validation but the special string
179 If the output didn't pass the default validation but the special string
180 '#random' is included, we accept it."""
180 '#random' is included, we accept it."""
181
181
182 # Let the original tester verify first, in case people have valid tests
182 # Let the original tester verify first, in case people have valid tests
183 # that happen to have a comment saying '#random' embedded in.
183 # that happen to have a comment saying '#random' embedded in.
184 ret = doctest.OutputChecker.check_output(self, want, got,
184 ret = doctest.OutputChecker.check_output(self, want, got,
185 optionflags)
185 optionflags)
186 if not ret and self.random_re.search(want):
186 if not ret and self.random_re.search(want):
187 #print >> sys.stderr, 'RANDOM OK:',want # dbg
187 #print >> sys.stderr, 'RANDOM OK:',want # dbg
188 return True
188 return True
189
189
190 return ret
190 return ret
191
191
192
192
193 class DocTestCase(doctests.DocTestCase):
193 class DocTestCase(doctests.DocTestCase):
194 """Proxy for DocTestCase: provides an address() method that
194 """Proxy for DocTestCase: provides an address() method that
195 returns the correct address for the doctest case. Otherwise
195 returns the correct address for the doctest case. Otherwise
196 acts as a proxy to the test case. To provide hints for address(),
196 acts as a proxy to the test case. To provide hints for address(),
197 an obj may also be passed -- this will be used as the test object
197 an obj may also be passed -- this will be used as the test object
198 for purposes of determining the test address, if it is provided.
198 for purposes of determining the test address, if it is provided.
199 """
199 """
200
200
201 # Note: this method was taken from numpy's nosetester module.
201 # Note: this method was taken from numpy's nosetester module.
202
202
203 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
203 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
204 # its constructor that blocks non-default arguments from being passed
204 # its constructor that blocks non-default arguments from being passed
205 # down into doctest.DocTestCase
205 # down into doctest.DocTestCase
206
206
207 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
207 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
208 checker=None, obj=None, result_var='_'):
208 checker=None, obj=None, result_var='_'):
209 self._result_var = result_var
209 self._result_var = result_var
210 doctests.DocTestCase.__init__(self, test,
210 doctests.DocTestCase.__init__(self, test,
211 optionflags=optionflags,
211 optionflags=optionflags,
212 setUp=setUp, tearDown=tearDown,
212 setUp=setUp, tearDown=tearDown,
213 checker=checker)
213 checker=checker)
214 # Now we must actually copy the original constructor from the stdlib
214 # Now we must actually copy the original constructor from the stdlib
215 # doctest class, because we can't call it directly and a bug in nose
215 # doctest class, because we can't call it directly and a bug in nose
216 # means it never gets passed the right arguments.
216 # means it never gets passed the right arguments.
217
217
218 self._dt_optionflags = optionflags
218 self._dt_optionflags = optionflags
219 self._dt_checker = checker
219 self._dt_checker = checker
220 self._dt_test = test
220 self._dt_test = test
221 self._dt_test_globs_ori = test.globs
221 self._dt_test_globs_ori = test.globs
222 self._dt_setUp = setUp
222 self._dt_setUp = setUp
223 self._dt_tearDown = tearDown
223 self._dt_tearDown = tearDown
224
224
225 # XXX - store this runner once in the object!
225 # XXX - store this runner once in the object!
226 runner = IPDocTestRunner(optionflags=optionflags,
226 runner = IPDocTestRunner(optionflags=optionflags,
227 checker=checker, verbose=False)
227 checker=checker, verbose=False)
228 self._dt_runner = runner
228 self._dt_runner = runner
229
229
230
230
231 # Each doctest should remember the directory it was loaded from, so
231 # Each doctest should remember the directory it was loaded from, so
232 # things like %run work without too many contortions
232 # things like %run work without too many contortions
233 self._ori_dir = os.path.dirname(test.filename)
233 self._ori_dir = os.path.dirname(test.filename)
234
234
235 # Modified runTest from the default stdlib
235 # Modified runTest from the default stdlib
236 def runTest(self):
236 def runTest(self):
237 test = self._dt_test
237 test = self._dt_test
238 runner = self._dt_runner
238 runner = self._dt_runner
239
239
240 old = sys.stdout
240 old = sys.stdout
241 new = StringIO()
241 new = StringIO()
242 optionflags = self._dt_optionflags
242 optionflags = self._dt_optionflags
243
243
244 if not (optionflags & REPORTING_FLAGS):
244 if not (optionflags & REPORTING_FLAGS):
245 # The option flags don't include any reporting flags,
245 # The option flags don't include any reporting flags,
246 # so add the default reporting flags
246 # so add the default reporting flags
247 optionflags |= _unittest_reportflags
247 optionflags |= _unittest_reportflags
248
248
249 try:
249 try:
250 # Save our current directory and switch out to the one where the
250 # Save our current directory and switch out to the one where the
251 # test was originally created, in case another doctest did a
251 # test was originally created, in case another doctest did a
252 # directory change. We'll restore this in the finally clause.
252 # directory change. We'll restore this in the finally clause.
253 curdir = os.getcwdu()
253 curdir = os.getcwdu()
254 #print 'runTest in dir:', self._ori_dir # dbg
254 #print 'runTest in dir:', self._ori_dir # dbg
255 os.chdir(self._ori_dir)
255 os.chdir(self._ori_dir)
256
256
257 runner.DIVIDER = "-"*70
257 runner.DIVIDER = "-"*70
258 failures, tries = runner.run(test,out=new.write,
258 failures, tries = runner.run(test,out=new.write,
259 clear_globs=False)
259 clear_globs=False)
260 finally:
260 finally:
261 sys.stdout = old
261 sys.stdout = old
262 os.chdir(curdir)
262 os.chdir(curdir)
263
263
264 if failures:
264 if failures:
265 raise self.failureException(self.format_failure(new.getvalue()))
265 raise self.failureException(self.format_failure(new.getvalue()))
266
266
267 def setUp(self):
267 def setUp(self):
268 """Modified test setup that syncs with ipython namespace"""
268 """Modified test setup that syncs with ipython namespace"""
269 #print "setUp test", self._dt_test.examples # dbg
269 #print "setUp test", self._dt_test.examples # dbg
270 if isinstance(self._dt_test.examples[0],IPExample):
270 if isinstance(self._dt_test.examples[0],IPExample):
271 # for IPython examples *only*, we swap the globals with the ipython
271 # for IPython examples *only*, we swap the globals with the ipython
272 # namespace, after updating it with the globals (which doctest
272 # namespace, after updating it with the globals (which doctest
273 # fills with the necessary info from the module being tested).
273 # fills with the necessary info from the module being tested).
274 _ip.user_ns.update(self._dt_test.globs)
274 _ip.user_ns.update(self._dt_test.globs)
275 self._dt_test.globs = _ip.user_ns
275 self._dt_test.globs = _ip.user_ns
276 # IPython must protect the _ key in the namespace (it can't exist)
276 # IPython must protect the _ key in the namespace (it can't exist)
277 # so that Python's doctest code sets it naturally, so we enable
277 # so that Python's doctest code sets it naturally, so we enable
278 # this feature of our testing namespace.
278 # this feature of our testing namespace.
279 _ip.user_ns.protect_underscore = True
279 _ip.user_ns.protect_underscore = True
280
280
281 super(DocTestCase, self).setUp()
281 super(DocTestCase, self).setUp()
282
282
283 def tearDown(self):
283 def tearDown(self):
284
284
285 # Undo the test.globs reassignment we made, so that the parent class
285 # Undo the test.globs reassignment we made, so that the parent class
286 # teardown doesn't destroy the ipython namespace
286 # teardown doesn't destroy the ipython namespace
287 if isinstance(self._dt_test.examples[0],IPExample):
287 if isinstance(self._dt_test.examples[0],IPExample):
288 self._dt_test.globs = self._dt_test_globs_ori
288 self._dt_test.globs = self._dt_test_globs_ori
289 # Restore the behavior of the '_' key in the user namespace to
289 # Restore the behavior of the '_' key in the user namespace to
290 # normal after each doctest, so that unittests behave normally
290 # normal after each doctest, so that unittests behave normally
291 _ip.user_ns.protect_underscore = False
291 _ip.user_ns.protect_underscore = False
292
292
293 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
293 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
294 # it does look like one to me: its tearDown method tries to run
294 # it does look like one to me: its tearDown method tries to run
295 #
295 #
296 # delattr(__builtin__, self._result_var)
296 # delattr(__builtin__, self._result_var)
297 #
297 #
298 # without checking that the attribute really is there; it implicitly
298 # without checking that the attribute really is there; it implicitly
299 # assumes it should have been set via displayhook. But if the
299 # assumes it should have been set via displayhook. But if the
300 # displayhook was never called, this doesn't necessarily happen. I
300 # displayhook was never called, this doesn't necessarily happen. I
301 # haven't been able to find a little self-contained example outside of
301 # haven't been able to find a little self-contained example outside of
302 # ipython that would show the problem so I can report it to the nose
302 # ipython that would show the problem so I can report it to the nose
303 # team, but it does happen a lot in our code.
303 # team, but it does happen a lot in our code.
304 #
304 #
305 # So here, we just protect as narrowly as possible by trapping an
305 # So here, we just protect as narrowly as possible by trapping an
306 # attribute error whose message would be the name of self._result_var,
306 # attribute error whose message would be the name of self._result_var,
307 # and letting any other error propagate.
307 # and letting any other error propagate.
308 try:
308 try:
309 super(DocTestCase, self).tearDown()
309 super(DocTestCase, self).tearDown()
310 except AttributeError, exc:
310 except AttributeError, exc:
311 if exc.args[0] != self._result_var:
311 if exc.args[0] != self._result_var:
312 raise
312 raise
313
313
314
314
315 # A simple subclassing of the original with a different class name, so we can
315 # A simple subclassing of the original with a different class name, so we can
316 # distinguish and treat differently IPython examples from pure python ones.
316 # distinguish and treat differently IPython examples from pure python ones.
317 class IPExample(doctest.Example): pass
317 class IPExample(doctest.Example): pass
318
318
319
319
320 class IPExternalExample(doctest.Example):
320 class IPExternalExample(doctest.Example):
321 """Doctest examples to be run in an external process."""
321 """Doctest examples to be run in an external process."""
322
322
323 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
323 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
324 options=None):
324 options=None):
325 # Parent constructor
325 # Parent constructor
326 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
326 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
327
327
328 # An EXTRA newline is needed to prevent pexpect hangs
328 # An EXTRA newline is needed to prevent pexpect hangs
329 self.source += '\n'
329 self.source += '\n'
330
330
331
331
332 class IPDocTestParser(doctest.DocTestParser):
332 class IPDocTestParser(doctest.DocTestParser):
333 """
333 """
334 A class used to parse strings containing doctest examples.
334 A class used to parse strings containing doctest examples.
335
335
336 Note: This is a version modified to properly recognize IPython input and
336 Note: This is a version modified to properly recognize IPython input and
337 convert any IPython examples into valid Python ones.
337 convert any IPython examples into valid Python ones.
338 """
338 """
339 # This regular expression is used to find doctest examples in a
339 # This regular expression is used to find doctest examples in a
340 # string. It defines three groups: `source` is the source code
340 # string. It defines three groups: `source` is the source code
341 # (including leading indentation and prompts); `indent` is the
341 # (including leading indentation and prompts); `indent` is the
342 # indentation of the first (PS1) line of the source code; and
342 # indentation of the first (PS1) line of the source code; and
343 # `want` is the expected output (including leading indentation).
343 # `want` is the expected output (including leading indentation).
344
344
345 # Classic Python prompts or default IPython ones
345 # Classic Python prompts or default IPython ones
346 _PS1_PY = r'>>>'
346 _PS1_PY = r'>>>'
347 _PS2_PY = r'\.\.\.'
347 _PS2_PY = r'\.\.\.'
348
348
349 _PS1_IP = r'In\ \[\d+\]:'
349 _PS1_IP = r'In\ \[\d+\]:'
350 _PS2_IP = r'\ \ \ \.\.\.+:'
350 _PS2_IP = r'\ \ \ \.\.\.+:'
351
351
352 _RE_TPL = r'''
352 _RE_TPL = r'''
353 # Source consists of a PS1 line followed by zero or more PS2 lines.
353 # Source consists of a PS1 line followed by zero or more PS2 lines.
354 (?P<source>
354 (?P<source>
355 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
355 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
356 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
356 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
357 \n? # a newline
357 \n? # a newline
358 # Want consists of any non-blank lines that do not start with PS1.
358 # Want consists of any non-blank lines that do not start with PS1.
359 (?P<want> (?:(?![ ]*$) # Not a blank line
359 (?P<want> (?:(?![ ]*$) # Not a blank line
360 (?![ ]*%s) # Not a line starting with PS1
360 (?![ ]*%s) # Not a line starting with PS1
361 (?![ ]*%s) # Not a line starting with PS2
361 (?![ ]*%s) # Not a line starting with PS2
362 .*$\n? # But any other line
362 .*$\n? # But any other line
363 )*)
363 )*)
364 '''
364 '''
365
365
366 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
366 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
367 re.MULTILINE | re.VERBOSE)
367 re.MULTILINE | re.VERBOSE)
368
368
369 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
369 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
370 re.MULTILINE | re.VERBOSE)
370 re.MULTILINE | re.VERBOSE)
371
371
372 # Mark a test as being fully random. In this case, we simply append the
372 # Mark a test as being fully random. In this case, we simply append the
373 # random marker ('#random') to each individual example's output. This way
373 # random marker ('#random') to each individual example's output. This way
374 # we don't need to modify any other code.
374 # we don't need to modify any other code.
375 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
375 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
376
376
377 # Mark tests to be executed in an external process - currently unsupported.
377 # Mark tests to be executed in an external process - currently unsupported.
378 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
378 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
379
379
380 def ip2py(self,source):
380 def ip2py(self,source):
381 """Convert input IPython source into valid Python."""
381 """Convert input IPython source into valid Python."""
382 out = []
382 out = []
383 newline = out.append
383 newline = out.append
384 #print 'IPSRC:\n',source,'\n###' # dbg
384 #print 'IPSRC:\n',source,'\n###' # dbg
385 # The input source must be first stripped of all bracketing whitespace
385 # The input source must be first stripped of all bracketing whitespace
386 # and turned into lines, so it looks to the parser like regular user
386 # and turned into lines, so it looks to the parser like regular user
387 # input
387 # input
388 for lnum,line in enumerate(source.strip().splitlines()):
388 for lnum,line in enumerate(source.strip().splitlines()):
389 newline(_ip.prefilter(line,lnum>0))
389 newline(_ip.prefilter(line,lnum>0))
390 newline('') # ensure a closing newline, needed by doctest
390 newline('') # ensure a closing newline, needed by doctest
391 #print "PYSRC:", '\n'.join(out) # dbg
391 #print "PYSRC:", '\n'.join(out) # dbg
392 return '\n'.join(out)
392 return '\n'.join(out)
393
393
394 def parse(self, string, name='<string>'):
394 def parse(self, string, name='<string>'):
395 """
395 """
396 Divide the given string into examples and intervening text,
396 Divide the given string into examples and intervening text,
397 and return them as a list of alternating Examples and strings.
397 and return them as a list of alternating Examples and strings.
398 Line numbers for the Examples are 0-based. The optional
398 Line numbers for the Examples are 0-based. The optional
399 argument `name` is a name identifying this string, and is only
399 argument `name` is a name identifying this string, and is only
400 used for error messages.
400 used for error messages.
401 """
401 """
402
402
403 #print 'Parse string:\n',string # dbg
403 #print 'Parse string:\n',string # dbg
404
404
405 string = string.expandtabs()
405 string = string.expandtabs()
406 # If all lines begin with the same indentation, then strip it.
406 # If all lines begin with the same indentation, then strip it.
407 min_indent = self._min_indent(string)
407 min_indent = self._min_indent(string)
408 if min_indent > 0:
408 if min_indent > 0:
409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
410
410
411 output = []
411 output = []
412 charno, lineno = 0, 0
412 charno, lineno = 0, 0
413
413
414 # We make 'all random' tests by adding the '# random' mark to every
414 # We make 'all random' tests by adding the '# random' mark to every
415 # block of output in the test.
415 # block of output in the test.
416 if self._RANDOM_TEST.search(string):
416 if self._RANDOM_TEST.search(string):
417 random_marker = '\n# random'
417 random_marker = '\n# random'
418 else:
418 else:
419 random_marker = ''
419 random_marker = ''
420
420
421 # Whether to convert the input from ipython to python syntax
421 # Whether to convert the input from ipython to python syntax
422 ip2py = False
422 ip2py = False
423 # Find all doctest examples in the string. First, try them as Python
423 # Find all doctest examples in the string. First, try them as Python
424 # examples, then as IPython ones
424 # examples, then as IPython ones
425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
426 if terms:
426 if terms:
427 # Normal Python example
427 # Normal Python example
428 #print '-'*70 # dbg
428 #print '-'*70 # dbg
429 #print 'PyExample, Source:\n',string # dbg
429 #print 'PyExample, Source:\n',string # dbg
430 #print '-'*70 # dbg
430 #print '-'*70 # dbg
431 Example = doctest.Example
431 Example = doctest.Example
432 else:
432 else:
433 # It's an ipython example. Note that IPExamples are run
433 # It's an ipython example. Note that IPExamples are run
434 # in-process, so their syntax must be turned into valid python.
434 # in-process, so their syntax must be turned into valid python.
435 # IPExternalExamples are run out-of-process (via pexpect) so they
435 # IPExternalExamples are run out-of-process (via pexpect) so they
436 # don't need any filtering (a real ipython will be executing them).
436 # don't need any filtering (a real ipython will be executing them).
437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
438 if self._EXTERNAL_IP.search(string):
438 if self._EXTERNAL_IP.search(string):
439 #print '-'*70 # dbg
439 #print '-'*70 # dbg
440 #print 'IPExternalExample, Source:\n',string # dbg
440 #print 'IPExternalExample, Source:\n',string # dbg
441 #print '-'*70 # dbg
441 #print '-'*70 # dbg
442 Example = IPExternalExample
442 Example = IPExternalExample
443 else:
443 else:
444 #print '-'*70 # dbg
444 #print '-'*70 # dbg
445 #print 'IPExample, Source:\n',string # dbg
445 #print 'IPExample, Source:\n',string # dbg
446 #print '-'*70 # dbg
446 #print '-'*70 # dbg
447 Example = IPExample
447 Example = IPExample
448 ip2py = True
448 ip2py = True
449
449
450 for m in terms:
450 for m in terms:
451 # Add the pre-example text to `output`.
451 # Add the pre-example text to `output`.
452 output.append(string[charno:m.start()])
452 output.append(string[charno:m.start()])
453 # Update lineno (lines before this example)
453 # Update lineno (lines before this example)
454 lineno += string.count('\n', charno, m.start())
454 lineno += string.count('\n', charno, m.start())
455 # Extract info from the regexp match.
455 # Extract info from the regexp match.
456 (source, options, want, exc_msg) = \
456 (source, options, want, exc_msg) = \
457 self._parse_example(m, name, lineno,ip2py)
457 self._parse_example(m, name, lineno,ip2py)
458
458
459 # Append the random-output marker (it defaults to empty in most
459 # Append the random-output marker (it defaults to empty in most
460 # cases, it's only non-empty for 'all-random' tests):
460 # cases, it's only non-empty for 'all-random' tests):
461 want += random_marker
461 want += random_marker
462
462
463 if Example is IPExternalExample:
463 if Example is IPExternalExample:
464 options[doctest.NORMALIZE_WHITESPACE] = True
464 options[doctest.NORMALIZE_WHITESPACE] = True
465 want += '\n'
465 want += '\n'
466
466
467 # Create an Example, and add it to the list.
467 # Create an Example, and add it to the list.
468 if not self._IS_BLANK_OR_COMMENT(source):
468 if not self._IS_BLANK_OR_COMMENT(source):
469 output.append(Example(source, want, exc_msg,
469 output.append(Example(source, want, exc_msg,
470 lineno=lineno,
470 lineno=lineno,
471 indent=min_indent+len(m.group('indent')),
471 indent=min_indent+len(m.group('indent')),
472 options=options))
472 options=options))
473 # Update lineno (lines inside this example)
473 # Update lineno (lines inside this example)
474 lineno += string.count('\n', m.start(), m.end())
474 lineno += string.count('\n', m.start(), m.end())
475 # Update charno.
475 # Update charno.
476 charno = m.end()
476 charno = m.end()
477 # Add any remaining post-example text to `output`.
477 # Add any remaining post-example text to `output`.
478 output.append(string[charno:])
478 output.append(string[charno:])
479 return output
479 return output
480
480
481 def _parse_example(self, m, name, lineno,ip2py=False):
481 def _parse_example(self, m, name, lineno,ip2py=False):
482 """
482 """
483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
484 return a pair `(source, want)`, where `source` is the matched
484 return a pair `(source, want)`, where `source` is the matched
485 example's source code (with prompts and indentation stripped);
485 example's source code (with prompts and indentation stripped);
486 and `want` is the example's expected output (with indentation
486 and `want` is the example's expected output (with indentation
487 stripped).
487 stripped).
488
488
489 `name` is the string's name, and `lineno` is the line number
489 `name` is the string's name, and `lineno` is the line number
490 where the example starts; both are used for error messages.
490 where the example starts; both are used for error messages.
491
491
492 Optional:
492 Optional:
493 `ip2py`: if true, filter the input via IPython to convert the syntax
493 `ip2py`: if true, filter the input via IPython to convert the syntax
494 into valid python.
494 into valid python.
495 """
495 """
496
496
497 # Get the example's indentation level.
497 # Get the example's indentation level.
498 indent = len(m.group('indent'))
498 indent = len(m.group('indent'))
499
499
500 # Divide source into lines; check that they're properly
500 # Divide source into lines; check that they're properly
501 # indented; and then strip their indentation & prompts.
501 # indented; and then strip their indentation & prompts.
502 source_lines = m.group('source').split('\n')
502 source_lines = m.group('source').split('\n')
503
503
504 # We're using variable-length input prompts
504 # We're using variable-length input prompts
505 ps1 = m.group('ps1')
505 ps1 = m.group('ps1')
506 ps2 = m.group('ps2')
506 ps2 = m.group('ps2')
507 ps1_len = len(ps1)
507 ps1_len = len(ps1)
508
508
509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
510 if ps2:
510 if ps2:
511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
512
512
513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
514
514
515 if ip2py:
515 if ip2py:
516 # Convert source input from IPython into valid Python syntax
516 # Convert source input from IPython into valid Python syntax
517 source = self.ip2py(source)
517 source = self.ip2py(source)
518
518
519 # Divide want into lines; check that it's properly indented; and
519 # Divide want into lines; check that it's properly indented; and
520 # then strip the indentation. Spaces before the last newline should
520 # then strip the indentation. Spaces before the last newline should
521 # be preserved, so plain rstrip() isn't good enough.
521 # be preserved, so plain rstrip() isn't good enough.
522 want = m.group('want')
522 want = m.group('want')
523 want_lines = want.split('\n')
523 want_lines = want.split('\n')
524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
525 del want_lines[-1] # forget final newline & spaces after it
525 del want_lines[-1] # forget final newline & spaces after it
526 self._check_prefix(want_lines, ' '*indent, name,
526 self._check_prefix(want_lines, ' '*indent, name,
527 lineno + len(source_lines))
527 lineno + len(source_lines))
528
528
529 # Remove ipython output prompt that might be present in the first line
529 # Remove ipython output prompt that might be present in the first line
530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
531
531
532 want = '\n'.join([wl[indent:] for wl in want_lines])
532 want = '\n'.join([wl[indent:] for wl in want_lines])
533
533
534 # If `want` contains a traceback message, then extract it.
534 # If `want` contains a traceback message, then extract it.
535 m = self._EXCEPTION_RE.match(want)
535 m = self._EXCEPTION_RE.match(want)
536 if m:
536 if m:
537 exc_msg = m.group('msg')
537 exc_msg = m.group('msg')
538 else:
538 else:
539 exc_msg = None
539 exc_msg = None
540
540
541 # Extract options from the source.
541 # Extract options from the source.
542 options = self._find_options(source, name, lineno)
542 options = self._find_options(source, name, lineno)
543
543
544 return source, options, want, exc_msg
544 return source, options, want, exc_msg
545
545
546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
547 """
547 """
548 Given the lines of a source string (including prompts and
548 Given the lines of a source string (including prompts and
549 leading indentation), check to make sure that every prompt is
549 leading indentation), check to make sure that every prompt is
550 followed by a space character. If any line is not followed by
550 followed by a space character. If any line is not followed by
551 a space character, then raise ValueError.
551 a space character, then raise ValueError.
552
552
553 Note: IPython-modified version which takes the input prompt length as a
553 Note: IPython-modified version which takes the input prompt length as a
554 parameter, so that prompts of variable length can be dealt with.
554 parameter, so that prompts of variable length can be dealt with.
555 """
555 """
556 space_idx = indent+ps1_len
556 space_idx = indent+ps1_len
557 min_len = space_idx+1
557 min_len = space_idx+1
558 for i, line in enumerate(lines):
558 for i, line in enumerate(lines):
559 if len(line) >= min_len and line[space_idx] != ' ':
559 if len(line) >= min_len and line[space_idx] != ' ':
560 raise ValueError('line %r of the docstring for %s '
560 raise ValueError('line %r of the docstring for %s '
561 'lacks blank after %s: %r' %
561 'lacks blank after %s: %r' %
562 (lineno+i+1, name,
562 (lineno+i+1, name,
563 line[indent:space_idx], line))
563 line[indent:space_idx], line))
564
564
565
565
566 SKIP = doctest.register_optionflag('SKIP')
566 SKIP = doctest.register_optionflag('SKIP')
567
567
568
568
569 class IPDocTestRunner(doctest.DocTestRunner,object):
569 class IPDocTestRunner(doctest.DocTestRunner,object):
570 """Test runner that synchronizes the IPython namespace with test globals.
570 """Test runner that synchronizes the IPython namespace with test globals.
571 """
571 """
572
572
573 def run(self, test, compileflags=None, out=None, clear_globs=True):
573 def run(self, test, compileflags=None, out=None, clear_globs=True):
574
574
575 # Hack: ipython needs access to the execution context of the example,
575 # Hack: ipython needs access to the execution context of the example,
576 # so that it can propagate user variables loaded by %run into
576 # so that it can propagate user variables loaded by %run into
577 # test.globs. We put them here into our modified %run as a function
577 # test.globs. We put them here into our modified %run as a function
578 # attribute. Our new %run will then only make the namespace update
578 # attribute. Our new %run will then only make the namespace update
579 # when called (rather than unconconditionally updating test.globs here
579 # when called (rather than unconconditionally updating test.globs here
580 # for all examples, most of which won't be calling %run anyway).
580 # for all examples, most of which won't be calling %run anyway).
581 #_ip._ipdoctest_test_globs = test.globs
581 #_ip._ipdoctest_test_globs = test.globs
582 #_ip._ipdoctest_test_filename = test.filename
582 #_ip._ipdoctest_test_filename = test.filename
583
583
584 test.globs.update(_ip.user_ns)
584 test.globs.update(_ip.user_ns)
585
585
586 return super(IPDocTestRunner,self).run(test,
586 return super(IPDocTestRunner,self).run(test,
587 compileflags,out,clear_globs)
587 compileflags,out,clear_globs)
588
588
589
589
590 class DocFileCase(doctest.DocFileCase):
590 class DocFileCase(doctest.DocFileCase):
591 """Overrides to provide filename
591 """Overrides to provide filename
592 """
592 """
593 def address(self):
593 def address(self):
594 return (self._dt_test.filename, None, None)
594 return (self._dt_test.filename, None, None)
595
595
596
596
597 class ExtensionDoctest(doctests.Doctest):
597 class ExtensionDoctest(doctests.Doctest):
598 """Nose Plugin that supports doctests in extension modules.
598 """Nose Plugin that supports doctests in extension modules.
599 """
599 """
600 name = 'extdoctest' # call nosetests with --with-extdoctest
600 name = 'extdoctest' # call nosetests with --with-extdoctest
601 enabled = True
601 enabled = True
602
602
603 def __init__(self,exclude_patterns=None):
603 def __init__(self,exclude_patterns=None):
604 """Create a new ExtensionDoctest plugin.
604 """Create a new ExtensionDoctest plugin.
605
605
606 Parameters
606 Parameters
607 ----------
607 ----------
608
608
609 exclude_patterns : sequence of strings, optional
609 exclude_patterns : sequence of strings, optional
610 These patterns are compiled as regular expressions, subsequently used
610 These patterns are compiled as regular expressions, subsequently used
611 to exclude any filename which matches them from inclusion in the test
611 to exclude any filename which matches them from inclusion in the test
612 suite (using pattern.search(), NOT pattern.match() ).
612 suite (using pattern.search(), NOT pattern.match() ).
613 """
613 """
614
614
615 if exclude_patterns is None:
615 if exclude_patterns is None:
616 exclude_patterns = []
616 exclude_patterns = []
617 self.exclude_patterns = map(re.compile,exclude_patterns)
617 self.exclude_patterns = map(re.compile,exclude_patterns)
618 doctests.Doctest.__init__(self)
618 doctests.Doctest.__init__(self)
619
619
620 def options(self, parser, env=os.environ):
620 def options(self, parser, env=os.environ):
621 Plugin.options(self, parser, env)
621 Plugin.options(self, parser, env)
622 parser.add_option('--doctest-tests', action='store_true',
622 parser.add_option('--doctest-tests', action='store_true',
623 dest='doctest_tests',
623 dest='doctest_tests',
624 default=env.get('NOSE_DOCTEST_TESTS',True),
624 default=env.get('NOSE_DOCTEST_TESTS',True),
625 help="Also look for doctests in test modules. "
625 help="Also look for doctests in test modules. "
626 "Note that classes, methods and functions should "
626 "Note that classes, methods and functions should "
627 "have either doctests or non-doctest tests, "
627 "have either doctests or non-doctest tests, "
628 "not both. [NOSE_DOCTEST_TESTS]")
628 "not both. [NOSE_DOCTEST_TESTS]")
629 parser.add_option('--doctest-extension', action="append",
629 parser.add_option('--doctest-extension', action="append",
630 dest="doctestExtension",
630 dest="doctestExtension",
631 help="Also look for doctests in files with "
631 help="Also look for doctests in files with "
632 "this extension [NOSE_DOCTEST_EXTENSION]")
632 "this extension [NOSE_DOCTEST_EXTENSION]")
633 # Set the default as a list, if given in env; otherwise
633 # Set the default as a list, if given in env; otherwise
634 # an additional value set on the command line will cause
634 # an additional value set on the command line will cause
635 # an error.
635 # an error.
636 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
636 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
637 if env_setting is not None:
637 if env_setting is not None:
638 parser.set_defaults(doctestExtension=tolist(env_setting))
638 parser.set_defaults(doctestExtension=tolist(env_setting))
639
639
640
640
641 def configure(self, options, config):
641 def configure(self, options, config):
642 Plugin.configure(self, options, config)
642 Plugin.configure(self, options, config)
643 # Pull standard doctest plugin out of config; we will do doctesting
644 config.plugins.plugins = [p for p in config.plugins.plugins
645 if p.name != 'doctest']
643 self.doctest_tests = options.doctest_tests
646 self.doctest_tests = options.doctest_tests
644 self.extension = tolist(options.doctestExtension)
647 self.extension = tolist(options.doctestExtension)
645
648
646 self.parser = doctest.DocTestParser()
649 self.parser = doctest.DocTestParser()
647 self.finder = DocTestFinder()
650 self.finder = DocTestFinder()
648 self.checker = IPDoctestOutputChecker()
651 self.checker = IPDoctestOutputChecker()
649 self.globs = None
652 self.globs = None
650 self.extraglobs = None
653 self.extraglobs = None
651
654
652
655
653 def loadTestsFromExtensionModule(self,filename):
656 def loadTestsFromExtensionModule(self,filename):
654 bpath,mod = os.path.split(filename)
657 bpath,mod = os.path.split(filename)
655 modname = os.path.splitext(mod)[0]
658 modname = os.path.splitext(mod)[0]
656 try:
659 try:
657 sys.path.append(bpath)
660 sys.path.append(bpath)
658 module = __import__(modname)
661 module = __import__(modname)
659 tests = list(self.loadTestsFromModule(module))
662 tests = list(self.loadTestsFromModule(module))
660 finally:
663 finally:
661 sys.path.pop()
664 sys.path.pop()
662 return tests
665 return tests
663
666
664 # NOTE: the method below is almost a copy of the original one in nose, with
667 # NOTE: the method below is almost a copy of the original one in nose, with
665 # a few modifications to control output checking.
668 # a few modifications to control output checking.
666
669
667 def loadTestsFromModule(self, module):
670 def loadTestsFromModule(self, module):
668 #print '*** ipdoctest - lTM',module # dbg
671 #print '*** ipdoctest - lTM',module # dbg
669
672
670 if not self.matches(module.__name__):
673 if not self.matches(module.__name__):
671 log.debug("Doctest doesn't want module %s", module)
674 log.debug("Doctest doesn't want module %s", module)
672 return
675 return
673
676
674 tests = self.finder.find(module,globs=self.globs,
677 tests = self.finder.find(module,globs=self.globs,
675 extraglobs=self.extraglobs)
678 extraglobs=self.extraglobs)
676 if not tests:
679 if not tests:
677 return
680 return
678
681
679 # always use whitespace and ellipsis options
682 # always use whitespace and ellipsis options
680 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
683 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
681
684
682 tests.sort()
685 tests.sort()
683 module_file = module.__file__
686 module_file = module.__file__
684 if module_file[-4:] in ('.pyc', '.pyo'):
687 if module_file[-4:] in ('.pyc', '.pyo'):
685 module_file = module_file[:-1]
688 module_file = module_file[:-1]
686 for test in tests:
689 for test in tests:
687 if not test.examples:
690 if not test.examples:
688 continue
691 continue
689 if not test.filename:
692 if not test.filename:
690 test.filename = module_file
693 test.filename = module_file
691
694
692 yield DocTestCase(test,
695 yield DocTestCase(test,
693 optionflags=optionflags,
696 optionflags=optionflags,
694 checker=self.checker)
697 checker=self.checker)
695
698
696
699
697 def loadTestsFromFile(self, filename):
700 def loadTestsFromFile(self, filename):
698 #print "ipdoctest - from file", filename # dbg
701 #print "ipdoctest - from file", filename # dbg
699 if is_extension_module(filename):
702 if is_extension_module(filename):
700 for t in self.loadTestsFromExtensionModule(filename):
703 for t in self.loadTestsFromExtensionModule(filename):
701 yield t
704 yield t
702 else:
705 else:
703 if self.extension and anyp(filename.endswith, self.extension):
706 if self.extension and anyp(filename.endswith, self.extension):
704 name = os.path.basename(filename)
707 name = os.path.basename(filename)
705 dh = open(filename)
708 dh = open(filename)
706 try:
709 try:
707 doc = dh.read()
710 doc = dh.read()
708 finally:
711 finally:
709 dh.close()
712 dh.close()
710 test = self.parser.get_doctest(
713 test = self.parser.get_doctest(
711 doc, globs={'__file__': filename}, name=name,
714 doc, globs={'__file__': filename}, name=name,
712 filename=filename, lineno=0)
715 filename=filename, lineno=0)
713 if test.examples:
716 if test.examples:
714 #print 'FileCase:',test.examples # dbg
717 #print 'FileCase:',test.examples # dbg
715 yield DocFileCase(test)
718 yield DocFileCase(test)
716 else:
719 else:
717 yield False # no tests to load
720 yield False # no tests to load
718
721
719 def wantFile(self,filename):
722 def wantFile(self,filename):
720 """Return whether the given filename should be scanned for tests.
723 """Return whether the given filename should be scanned for tests.
721
724
722 Modified version that accepts extension modules as valid containers for
725 Modified version that accepts extension modules as valid containers for
723 doctests.
726 doctests.
724 """
727 """
725 #print '*** ipdoctest- wantFile:',filename # dbg
728 #print '*** ipdoctest- wantFile:',filename # dbg
726
729
727 for pat in self.exclude_patterns:
730 for pat in self.exclude_patterns:
728 if pat.search(filename):
731 if pat.search(filename):
729 # print '###>>> SKIP:',filename # dbg
732 # print '###>>> SKIP:',filename # dbg
730 return False
733 return False
731
734
732 if is_extension_module(filename):
735 if is_extension_module(filename):
733 return True
736 return True
734 else:
737 else:
735 return doctests.Doctest.wantFile(self,filename)
738 return doctests.Doctest.wantFile(self,filename)
736
739
737
740
738 class IPythonDoctest(ExtensionDoctest):
741 class IPythonDoctest(ExtensionDoctest):
739 """Nose Plugin that supports doctests in extension modules.
742 """Nose Plugin that supports doctests in extension modules.
740 """
743 """
741 name = 'ipdoctest' # call nosetests with --with-ipdoctest
744 name = 'ipdoctest' # call nosetests with --with-ipdoctest
742 enabled = True
745 enabled = True
743
746
744 def makeTest(self, obj, parent):
747 def makeTest(self, obj, parent):
745 """Look for doctests in the given object, which will be a
748 """Look for doctests in the given object, which will be a
746 function, method or class.
749 function, method or class.
747 """
750 """
748 #print 'Plugin analyzing:', obj, parent # dbg
751 #print 'Plugin analyzing:', obj, parent # dbg
749 # always use whitespace and ellipsis options
752 # always use whitespace and ellipsis options
750 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
753 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
751
754
752 doctests = self.finder.find(obj, module=getmodule(parent))
755 doctests = self.finder.find(obj, module=getmodule(parent))
753 if doctests:
756 if doctests:
754 for test in doctests:
757 for test in doctests:
755 if len(test.examples) == 0:
758 if len(test.examples) == 0:
756 continue
759 continue
757
760
758 yield DocTestCase(test, obj=obj,
761 yield DocTestCase(test, obj=obj,
759 optionflags=optionflags,
762 optionflags=optionflags,
760 checker=self.checker)
763 checker=self.checker)
761
764
762 def options(self, parser, env=os.environ):
765 def options(self, parser, env=os.environ):
763 #print "Options for nose plugin:", self.name # dbg
766 #print "Options for nose plugin:", self.name # dbg
764 Plugin.options(self, parser, env)
767 Plugin.options(self, parser, env)
765 parser.add_option('--ipdoctest-tests', action='store_true',
768 parser.add_option('--ipdoctest-tests', action='store_true',
766 dest='ipdoctest_tests',
769 dest='ipdoctest_tests',
767 default=env.get('NOSE_IPDOCTEST_TESTS',True),
770 default=env.get('NOSE_IPDOCTEST_TESTS',True),
768 help="Also look for doctests in test modules. "
771 help="Also look for doctests in test modules. "
769 "Note that classes, methods and functions should "
772 "Note that classes, methods and functions should "
770 "have either doctests or non-doctest tests, "
773 "have either doctests or non-doctest tests, "
771 "not both. [NOSE_IPDOCTEST_TESTS]")
774 "not both. [NOSE_IPDOCTEST_TESTS]")
772 parser.add_option('--ipdoctest-extension', action="append",
775 parser.add_option('--ipdoctest-extension', action="append",
773 dest="ipdoctest_extension",
776 dest="ipdoctest_extension",
774 help="Also look for doctests in files with "
777 help="Also look for doctests in files with "
775 "this extension [NOSE_IPDOCTEST_EXTENSION]")
778 "this extension [NOSE_IPDOCTEST_EXTENSION]")
776 # Set the default as a list, if given in env; otherwise
779 # Set the default as a list, if given in env; otherwise
777 # an additional value set on the command line will cause
780 # an additional value set on the command line will cause
778 # an error.
781 # an error.
779 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
782 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
780 if env_setting is not None:
783 if env_setting is not None:
781 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
784 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
782
785
783 def configure(self, options, config):
786 def configure(self, options, config):
784 #print "Configuring nose plugin:", self.name # dbg
787 #print "Configuring nose plugin:", self.name # dbg
785 Plugin.configure(self, options, config)
788 Plugin.configure(self, options, config)
789 # Pull standard doctest plugin out of config; we will do doctesting
790 config.plugins.plugins = [p for p in config.plugins.plugins
791 if p.name != 'doctest']
786 self.doctest_tests = options.ipdoctest_tests
792 self.doctest_tests = options.ipdoctest_tests
787 self.extension = tolist(options.ipdoctest_extension)
793 self.extension = tolist(options.ipdoctest_extension)
788
794
789 self.parser = IPDocTestParser()
795 self.parser = IPDocTestParser()
790 self.finder = DocTestFinder(parser=self.parser)
796 self.finder = DocTestFinder(parser=self.parser)
791 self.checker = IPDoctestOutputChecker()
797 self.checker = IPDoctestOutputChecker()
792 self.globs = None
798 self.globs = None
793 self.extraglobs = None
799 self.extraglobs = None
General Comments 0
You need to be logged in to leave comments. Login now