##// END OF EJS Templates
Merge pull request #469 from takluyver/exit-code...
Thomas -
r3897:465180e8 merge
parent child Browse files
Show More
@@ -1,441 +1,443
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://bugs.launchpad.net/bugs/366982
183 # See ticket https://bugs.launchpad.net/bugs/366982
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 # Construct list of plugins, omitting the existing doctest plugin, which
359 # ours replaces (and extends).
359 # ours replaces (and extends).
360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
361 for p in nose.plugins.builtin.plugins:
361 for p in nose.plugins.builtin.plugins:
362 plug = p()
362 plug = p()
363 if plug.name == 'doctest':
363 if plug.name == 'doctest':
364 continue
364 continue
365 plugins.append(plug)
365 plugins.append(plug)
366
366
367 # We need a global ipython running in this process
367 # We need a global ipython running in this process
368 globalipapp.start_ipython()
368 globalipapp.start_ipython()
369 # Now nose can run
369 # Now nose can run
370 TestProgram(argv=argv, plugins=plugins)
370 TestProgram(argv=argv, plugins=plugins)
371
371
372
372
373 def run_iptestall():
373 def run_iptestall():
374 """Run the entire IPython test suite by calling nose and trial.
374 """Run the entire IPython test suite by calling nose and trial.
375
375
376 This function constructs :class:`IPTester` instances for all IPython
376 This function constructs :class:`IPTester` instances for all IPython
377 modules and package and then runs each of them. This causes the modules
377 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
378 and packages of IPython to be tested each in their own subprocess using
379 nose or twisted.trial appropriately.
379 nose or twisted.trial appropriately.
380 """
380 """
381
381
382 runners = make_runners()
382 runners = make_runners()
383
383
384 # Run the test runners in a temporary dir so we can nuke it when finished
384 # 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
385 # 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
386 # robust against being run in non-writeable directories by mistake, as the
387 # temp dir will always be user-writeable.
387 # temp dir will always be user-writeable.
388 curdir = os.getcwd()
388 curdir = os.getcwd()
389 testdir = tempfile.gettempdir()
389 testdir = tempfile.gettempdir()
390 os.chdir(testdir)
390 os.chdir(testdir)
391
391
392 # Run all test runners, tracking execution time
392 # Run all test runners, tracking execution time
393 failed = []
393 failed = []
394 t_start = time.time()
394 t_start = time.time()
395 try:
395 try:
396 for (name, runner) in runners:
396 for (name, runner) in runners:
397 print '*'*70
397 print '*'*70
398 print 'IPython test group:',name
398 print 'IPython test group:',name
399 res = runner.run()
399 res = runner.run()
400 if res:
400 if res:
401 failed.append( (name, runner) )
401 failed.append( (name, runner) )
402 finally:
402 finally:
403 os.chdir(curdir)
403 os.chdir(curdir)
404 t_end = time.time()
404 t_end = time.time()
405 t_tests = t_end - t_start
405 t_tests = t_end - t_start
406 nrunners = len(runners)
406 nrunners = len(runners)
407 nfail = len(failed)
407 nfail = len(failed)
408 # summarize results
408 # summarize results
409 print
409 print
410 print '*'*70
410 print '*'*70
411 print 'Test suite completed for system with the following information:'
411 print 'Test suite completed for system with the following information:'
412 print report()
412 print report()
413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
414 print
414 print
415 print 'Status:'
415 print 'Status:'
416 if not failed:
416 if not failed:
417 print 'OK'
417 print 'OK'
418 else:
418 else:
419 # If anything went wrong, point out what command to rerun manually to
419 # If anything went wrong, point out what command to rerun manually to
420 # see the actual errors and individual summary
420 # see the actual errors and individual summary
421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
422 for name, failed_runner in failed:
422 for name, failed_runner in failed:
423 print '-'*40
423 print '-'*40
424 print 'Runner failed:',name
424 print 'Runner failed:',name
425 print 'You may wish to rerun this one individually, with:'
425 print 'You may wish to rerun this one individually, with:'
426 print ' '.join(failed_runner.call_args)
426 print ' '.join(failed_runner.call_args)
427 print
427 print
428 # Ensure that our exit code indicates failure
429 sys.exit(1)
428
430
429
431
430 def main():
432 def main():
431 for arg in sys.argv[1:]:
433 for arg in sys.argv[1:]:
432 if arg.startswith('IPython'):
434 if arg.startswith('IPython'):
433 # This is in-process
435 # This is in-process
434 run_iptest()
436 run_iptest()
435 else:
437 else:
436 # This starts subprocesses
438 # This starts subprocesses
437 run_iptestall()
439 run_iptestall()
438
440
439
441
440 if __name__ == '__main__':
442 if __name__ == '__main__':
441 main()
443 main()
General Comments 0
You need to be logged in to leave comments. Login now