##// END OF EJS Templates
Remove check for GTK in tests, so that the default encoding remains ascii, and Unicode failures can be resolved.
Robert Kern -
Show More
@@ -1,442 +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) or trial recursively. This
8 calling this script (with different arguments) or trial recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2009 The IPython Development Team
20 # Copyright (C) 2009 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # Stdlib
30 # Stdlib
31 import os
31 import os
32 import os.path as path
32 import os.path as path
33 import signal
33 import signal
34 import sys
34 import sys
35 import subprocess
35 import subprocess
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import warnings
38 import warnings
39
39
40 # Note: monkeypatch!
40 # Note: monkeypatch!
41 # We need to monkeypatch a small problem in nose itself first, before importing
41 # We need to monkeypatch a small problem in nose itself first, before importing
42 # it for actual use. This should get into nose upstream, but its release cycle
42 # it for actual use. This should get into nose upstream, but its release cycle
43 # is slow and we need it for our parametric tests to work correctly.
43 # is slow and we need it for our parametric tests to work correctly.
44 from IPython.testing import nosepatch
44 from IPython.testing import nosepatch
45 # Now, proceed to import nose itself
45 # Now, proceed to import nose itself
46 import nose.plugins.builtin
46 import nose.plugins.builtin
47 from nose.core import TestProgram
47 from nose.core import TestProgram
48
48
49 # Our own imports
49 # Our own imports
50 from IPython.utils.path import get_ipython_module_path
50 from IPython.utils.path import get_ipython_module_path
51 from IPython.utils.process import find_cmd, pycmd2argv
51 from IPython.utils.process import find_cmd, pycmd2argv
52 from IPython.utils.sysinfo import sys_info
52 from IPython.utils.sysinfo import sys_info
53
53
54 from IPython.testing import globalipapp
54 from IPython.testing import globalipapp
55 from IPython.testing.plugin.ipdoctest import IPythonDoctest
55 from IPython.testing.plugin.ipdoctest import IPythonDoctest
56
56
57 pjoin = path.join
57 pjoin = path.join
58
58
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Globals
61 # Globals
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Warnings control
66 # Warnings control
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 # Twisted generates annoying warnings with Python 2.6, as will do other code
70 # that imports 'sets' as of today
70 # that imports 'sets' as of today
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
72 DeprecationWarning )
72 DeprecationWarning )
73
73
74 # This one also comes from Twisted
74 # This one also comes from Twisted
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
76 DeprecationWarning)
76 DeprecationWarning)
77
77
78 # Wx on Fedora11 spits these out
78 # Wx on Fedora11 spits these out
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
80 UserWarning)
80 UserWarning)
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Logic for skipping doctests
83 # Logic for skipping doctests
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86 def test_for(mod):
86 def test_for(mod):
87 """Test to see if mod is importable."""
87 """Test to see if mod is importable."""
88 try:
88 try:
89 __import__(mod)
89 __import__(mod)
90 except (ImportError, RuntimeError):
90 except (ImportError, RuntimeError):
91 # GTK reports Runtime error if it can't be initialized even if it's
91 # GTK reports Runtime error if it can't be initialized even if it's
92 # importable.
92 # importable.
93 return False
93 return False
94 else:
94 else:
95 return True
95 return True
96
96
97 # Global dict where we can store information on what we have and what we don't
97 # Global dict where we can store information on what we have and what we don't
98 # have available at test run time
98 # have available at test run time
99 have = {}
99 have = {}
100
100
101 have['curses'] = test_for('_curses')
101 have['curses'] = test_for('_curses')
102 have['wx'] = test_for('wx')
102 have['wx'] = test_for('wx')
103 have['wx.aui'] = test_for('wx.aui')
103 have['wx.aui'] = test_for('wx.aui')
104 have['zope.interface'] = test_for('zope.interface')
104 have['zope.interface'] = test_for('zope.interface')
105 have['twisted'] = test_for('twisted')
105 have['twisted'] = test_for('twisted')
106 have['foolscap'] = test_for('foolscap')
106 have['foolscap'] = test_for('foolscap')
107 have['pexpect'] = test_for('pexpect')
107 have['pexpect'] = test_for('pexpect')
108 have['gtk'] = test_for('gtk')
109 have['gobject'] = test_for('gobject')
110
108
111 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
112 # Functions and classes
110 # Functions and classes
113 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
114
112
115 def report():
113 def report():
116 """Return a string with a summary report of test-related variables."""
114 """Return a string with a summary report of test-related variables."""
117
115
118 out = [ sys_info(), '\n']
116 out = [ sys_info(), '\n']
119
117
120 avail = []
118 avail = []
121 not_avail = []
119 not_avail = []
122
120
123 for k, is_avail in have.items():
121 for k, is_avail in have.items():
124 if is_avail:
122 if is_avail:
125 avail.append(k)
123 avail.append(k)
126 else:
124 else:
127 not_avail.append(k)
125 not_avail.append(k)
128
126
129 if avail:
127 if avail:
130 out.append('\nTools and libraries available at test time:\n')
128 out.append('\nTools and libraries available at test time:\n')
131 avail.sort()
129 avail.sort()
132 out.append(' ' + ' '.join(avail)+'\n')
130 out.append(' ' + ' '.join(avail)+'\n')
133
131
134 if not_avail:
132 if not_avail:
135 out.append('\nTools and libraries NOT available at test time:\n')
133 out.append('\nTools and libraries NOT available at test time:\n')
136 not_avail.sort()
134 not_avail.sort()
137 out.append(' ' + ' '.join(not_avail)+'\n')
135 out.append(' ' + ' '.join(not_avail)+'\n')
138
136
139 return ''.join(out)
137 return ''.join(out)
140
138
141
139
142 def make_exclude():
140 def make_exclude():
143 """Make patterns of modules and packages to exclude from testing.
141 """Make patterns of modules and packages to exclude from testing.
144
142
145 For the IPythonDoctest plugin, we need to exclude certain patterns that
143 For the IPythonDoctest plugin, we need to exclude certain patterns that
146 cause testing problems. We should strive to minimize the number of
144 cause testing problems. We should strive to minimize the number of
147 skipped modules, since this means untested code.
145 skipped modules, since this means untested code.
148
146
149 These modules and packages will NOT get scanned by nose at all for tests.
147 These modules and packages will NOT get scanned by nose at all for tests.
150 """
148 """
151 # Simple utility to make IPython paths more readably, we need a lot of
149 # Simple utility to make IPython paths more readably, we need a lot of
152 # these below
150 # these below
153 ipjoin = lambda *paths: pjoin('IPython', *paths)
151 ipjoin = lambda *paths: pjoin('IPython', *paths)
154
152
155 exclusions = [ipjoin('external'),
153 exclusions = [ipjoin('external'),
156 pjoin('IPython_doctest_plugin'),
154 pjoin('IPython_doctest_plugin'),
157 ipjoin('quarantine'),
155 ipjoin('quarantine'),
158 ipjoin('deathrow'),
156 ipjoin('deathrow'),
159 ipjoin('testing', 'attic'),
157 ipjoin('testing', 'attic'),
160 # This guy is probably attic material
158 # This guy is probably attic material
161 ipjoin('testing', 'mkdoctests'),
159 ipjoin('testing', 'mkdoctests'),
162 # Testing inputhook will need a lot of thought, to figure out
160 # Testing inputhook will need a lot of thought, to figure out
163 # how to have tests that don't lock up with the gui event
161 # how to have tests that don't lock up with the gui event
164 # loops in the picture
162 # loops in the picture
165 ipjoin('lib', 'inputhook'),
163 ipjoin('lib', 'inputhook'),
166 # Config files aren't really importable stand-alone
164 # Config files aren't really importable stand-alone
167 ipjoin('config', 'default'),
165 ipjoin('config', 'default'),
168 ipjoin('config', 'profile'),
166 ipjoin('config', 'profile'),
169 ]
167 ]
170
168
171 if not have['wx']:
169 if not have['wx']:
172 exclusions.append(ipjoin('lib', 'inputhookwx'))
170 exclusions.append(ipjoin('lib', 'inputhookwx'))
173
171
174 if not have['gtk'] or not have['gobject']:
175 exclusions.append(ipjoin('lib', 'inputhookgtk'))
176
177 # These have to be skipped on win32 because the use echo, rm, cd, etc.
172 # These have to be skipped on win32 because the use echo, rm, cd, etc.
178 # See ticket https://bugs.launchpad.net/bugs/366982
173 # See ticket https://bugs.launchpad.net/bugs/366982
179 if sys.platform == 'win32':
174 if sys.platform == 'win32':
180 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
175 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
181 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
176 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
182
177
183 if not have['pexpect']:
178 if not have['pexpect']:
184 exclusions.extend([ipjoin('scripts', 'irunner'),
179 exclusions.extend([ipjoin('scripts', 'irunner'),
185 ipjoin('lib', 'irunner')])
180 ipjoin('lib', 'irunner')])
186
181
187 # This is scary. We still have things in frontend and testing that
182 # This is scary. We still have things in frontend and testing that
188 # are being tested by nose that use twisted. We need to rethink
183 # are being tested by nose that use twisted. We need to rethink
189 # how we are isolating dependencies in testing.
184 # how we are isolating dependencies in testing.
190 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
185 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
191 exclusions.extend(
186 exclusions.extend(
192 [ipjoin('testing', 'parametric'),
187 [ipjoin('testing', 'parametric'),
193 ipjoin('testing', 'util'),
188 ipjoin('testing', 'util'),
194 ipjoin('testing', 'tests', 'test_decorators_trial'),
189 ipjoin('testing', 'tests', 'test_decorators_trial'),
195 ] )
190 ] )
196
191
197 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
192 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
198 if sys.platform == 'win32':
193 if sys.platform == 'win32':
199 exclusions = [s.replace('\\','\\\\') for s in exclusions]
194 exclusions = [s.replace('\\','\\\\') for s in exclusions]
200
195
201 return exclusions
196 return exclusions
202
197
203
198
204 class IPTester(object):
199 class IPTester(object):
205 """Call that calls iptest or trial in a subprocess.
200 """Call that calls iptest or trial in a subprocess.
206 """
201 """
207 #: string, name of test runner that will be called
202 #: string, name of test runner that will be called
208 runner = None
203 runner = None
209 #: list, parameters for test runner
204 #: list, parameters for test runner
210 params = None
205 params = None
211 #: list, arguments of system call to be made to call test runner
206 #: list, arguments of system call to be made to call test runner
212 call_args = None
207 call_args = None
213 #: list, process ids of subprocesses we start (for cleanup)
208 #: list, process ids of subprocesses we start (for cleanup)
214 pids = None
209 pids = None
215
210
216 def __init__(self, runner='iptest', params=None):
211 def __init__(self, runner='iptest', params=None):
217 """Create new test runner."""
212 """Create new test runner."""
218 p = os.path
213 p = os.path
219 if runner == 'iptest':
214 if runner == 'iptest':
220 iptest_app = get_ipython_module_path('IPython.testing.iptest')
215 iptest_app = get_ipython_module_path('IPython.testing.iptest')
221 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
216 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
222 elif runner == 'trial':
217 elif runner == 'trial':
223 # For trial, it needs to be installed system-wide
218 # For trial, it needs to be installed system-wide
224 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
219 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
225 else:
220 else:
226 raise Exception('Not a valid test runner: %s' % repr(runner))
221 raise Exception('Not a valid test runner: %s' % repr(runner))
227 if params is None:
222 if params is None:
228 params = []
223 params = []
229 if isinstance(params, str):
224 if isinstance(params, str):
230 params = [params]
225 params = [params]
231 self.params = params
226 self.params = params
232
227
233 # Assemble call
228 # Assemble call
234 self.call_args = self.runner+self.params
229 self.call_args = self.runner+self.params
235
230
236 # Store pids of anything we start to clean up on deletion, if possible
231 # Store pids of anything we start to clean up on deletion, if possible
237 # (on posix only, since win32 has no os.kill)
232 # (on posix only, since win32 has no os.kill)
238 self.pids = []
233 self.pids = []
239
234
240 if sys.platform == 'win32':
235 if sys.platform == 'win32':
241 def _run_cmd(self):
236 def _run_cmd(self):
242 # On Windows, use os.system instead of subprocess.call, because I
237 # On Windows, use os.system instead of subprocess.call, because I
243 # was having problems with subprocess and I just don't know enough
238 # was having problems with subprocess and I just don't know enough
244 # about win32 to debug this reliably. Os.system may be the 'old
239 # about win32 to debug this reliably. Os.system may be the 'old
245 # fashioned' way to do it, but it works just fine. If someone
240 # fashioned' way to do it, but it works just fine. If someone
246 # later can clean this up that's fine, as long as the tests run
241 # later can clean this up that's fine, as long as the tests run
247 # reliably in win32.
242 # reliably in win32.
248 # What types of problems are you having. They may be related to
243 # What types of problems are you having. They may be related to
249 # running Python in unboffered mode. BG.
244 # running Python in unboffered mode. BG.
250 return os.system(' '.join(self.call_args))
245 return os.system(' '.join(self.call_args))
251 else:
246 else:
252 def _run_cmd(self):
247 def _run_cmd(self):
253 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
248 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
254 subp = subprocess.Popen(self.call_args)
249 subp = subprocess.Popen(self.call_args)
255 self.pids.append(subp.pid)
250 self.pids.append(subp.pid)
256 # If this fails, the pid will be left in self.pids and cleaned up
251 # If this fails, the pid will be left in self.pids and cleaned up
257 # later, but if the wait call succeeds, then we can clear the
252 # later, but if the wait call succeeds, then we can clear the
258 # stored pid.
253 # stored pid.
259 retcode = subp.wait()
254 retcode = subp.wait()
260 self.pids.pop()
255 self.pids.pop()
261 return retcode
256 return retcode
262
257
263 def run(self):
258 def run(self):
264 """Run the stored commands"""
259 """Run the stored commands"""
265 try:
260 try:
266 return self._run_cmd()
261 return self._run_cmd()
267 except:
262 except:
268 import traceback
263 import traceback
269 traceback.print_exc()
264 traceback.print_exc()
270 return 1 # signal failure
265 return 1 # signal failure
271
266
272 def __del__(self):
267 def __del__(self):
273 """Cleanup on exit by killing any leftover processes."""
268 """Cleanup on exit by killing any leftover processes."""
274
269
275 if not hasattr(os, 'kill'):
270 if not hasattr(os, 'kill'):
276 return
271 return
277
272
278 for pid in self.pids:
273 for pid in self.pids:
279 try:
274 try:
280 print 'Cleaning stale PID:', pid
275 print 'Cleaning stale PID:', pid
281 os.kill(pid, signal.SIGKILL)
276 os.kill(pid, signal.SIGKILL)
282 except OSError:
277 except OSError:
283 # This is just a best effort, if we fail or the process was
278 # This is just a best effort, if we fail or the process was
284 # really gone, ignore it.
279 # really gone, ignore it.
285 pass
280 pass
286
281
287
282
288 def make_runners():
283 def make_runners():
289 """Define the top-level packages that need to be tested.
284 """Define the top-level packages that need to be tested.
290 """
285 """
291
286
292 # Packages to be tested via nose, that only depend on the stdlib
287 # Packages to be tested via nose, that only depend on the stdlib
293 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
288 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
294 'scripts', 'testing', 'utils' ]
289 'scripts', 'testing', 'utils' ]
295 # The machinery in kernel needs twisted for real testing
290 # The machinery in kernel needs twisted for real testing
296 trial_pkg_names = []
291 trial_pkg_names = []
297
292
298 # And add twisted ones if conditions are met
293 # And add twisted ones if conditions are met
299 if have['zope.interface'] and have['twisted'] and have['foolscap']:
294 if have['zope.interface'] and have['twisted'] and have['foolscap']:
300 # We only list IPython.kernel for testing using twisted.trial as
295 # We only list IPython.kernel for testing using twisted.trial as
301 # nose and twisted.trial have conflicts that make the testing system
296 # nose and twisted.trial have conflicts that make the testing system
302 # unstable.
297 # unstable.
303 trial_pkg_names.append('kernel')
298 trial_pkg_names.append('kernel')
304
299
305 # For debugging this code, only load quick stuff
300 # For debugging this code, only load quick stuff
306 #nose_pkg_names = ['core', 'extensions'] # dbg
301 #nose_pkg_names = ['core', 'extensions'] # dbg
307 #trial_pkg_names = [] # dbg
302 #trial_pkg_names = [] # dbg
308
303
309 # Make fully qualified package names prepending 'IPython.' to our name lists
304 # Make fully qualified package names prepending 'IPython.' to our name lists
310 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
305 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
311 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
306 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
312
307
313 # Make runners
308 # Make runners
314 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
309 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
315 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
310 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
316
311
317 return runners
312 return runners
318
313
319
314
320 def run_iptest():
315 def run_iptest():
321 """Run the IPython test suite using nose.
316 """Run the IPython test suite using nose.
322
317
323 This function is called when this script is **not** called with the form
318 This function is called when this script is **not** called with the form
324 `iptest all`. It simply calls nose with appropriate command line flags
319 `iptest all`. It simply calls nose with appropriate command line flags
325 and accepts all of the standard nose arguments.
320 and accepts all of the standard nose arguments.
326 """
321 """
327
322
328 warnings.filterwarnings('ignore',
323 warnings.filterwarnings('ignore',
329 'This will be removed soon. Use IPython.testing.util instead')
324 'This will be removed soon. Use IPython.testing.util instead')
330
325
331 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
326 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
332
327
333 # Loading ipdoctest causes problems with Twisted, but
328 # Loading ipdoctest causes problems with Twisted, but
334 # our test suite runner now separates things and runs
329 # our test suite runner now separates things and runs
335 # all Twisted tests with trial.
330 # all Twisted tests with trial.
336 '--with-ipdoctest',
331 '--with-ipdoctest',
337 '--ipdoctest-tests','--ipdoctest-extension=txt',
332 '--ipdoctest-tests','--ipdoctest-extension=txt',
338
333
339 # We add --exe because of setuptools' imbecility (it
334 # We add --exe because of setuptools' imbecility (it
340 # blindly does chmod +x on ALL files). Nose does the
335 # blindly does chmod +x on ALL files). Nose does the
341 # right thing and it tries to avoid executables,
336 # right thing and it tries to avoid executables,
342 # setuptools unfortunately forces our hand here. This
337 # setuptools unfortunately forces our hand here. This
343 # has been discussed on the distutils list and the
338 # has been discussed on the distutils list and the
344 # setuptools devs refuse to fix this problem!
339 # setuptools devs refuse to fix this problem!
345 '--exe',
340 '--exe',
346 ]
341 ]
347
342
348 if nose.__version__ >= '0.11':
343 if nose.__version__ >= '0.11':
349 # I don't fully understand why we need this one, but depending on what
344 # I don't fully understand why we need this one, but depending on what
350 # directory the test suite is run from, if we don't give it, 0 tests
345 # directory the test suite is run from, if we don't give it, 0 tests
351 # get run. Specifically, if the test suite is run from the source dir
346 # get run. Specifically, if the test suite is run from the source dir
352 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
347 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
353 # even if the same call done in this directory works fine). It appears
348 # even if the same call done in this directory works fine). It appears
354 # that if the requested package is in the current dir, nose bails early
349 # that if the requested package is in the current dir, nose bails early
355 # by default. Since it's otherwise harmless, leave it in by default
350 # by default. Since it's otherwise harmless, leave it in by default
356 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
351 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
357 argv.append('--traverse-namespace')
352 argv.append('--traverse-namespace')
358
353
359 # Construct list of plugins, omitting the existing doctest plugin, which
354 # Construct list of plugins, omitting the existing doctest plugin, which
360 # ours replaces (and extends).
355 # ours replaces (and extends).
361 plugins = [IPythonDoctest(make_exclude())]
356 plugins = [IPythonDoctest(make_exclude())]
362 for p in nose.plugins.builtin.plugins:
357 for p in nose.plugins.builtin.plugins:
363 plug = p()
358 plug = p()
364 if plug.name == 'doctest':
359 if plug.name == 'doctest':
365 continue
360 continue
366 plugins.append(plug)
361 plugins.append(plug)
367
362
368 # We need a global ipython running in this process
363 # We need a global ipython running in this process
369 globalipapp.start_ipython()
364 globalipapp.start_ipython()
370 # Now nose can run
365 # Now nose can run
371 TestProgram(argv=argv, plugins=plugins)
366 TestProgram(argv=argv, plugins=plugins)
372
367
373
368
374 def run_iptestall():
369 def run_iptestall():
375 """Run the entire IPython test suite by calling nose and trial.
370 """Run the entire IPython test suite by calling nose and trial.
376
371
377 This function constructs :class:`IPTester` instances for all IPython
372 This function constructs :class:`IPTester` instances for all IPython
378 modules and package and then runs each of them. This causes the modules
373 modules and package and then runs each of them. This causes the modules
379 and packages of IPython to be tested each in their own subprocess using
374 and packages of IPython to be tested each in their own subprocess using
380 nose or twisted.trial appropriately.
375 nose or twisted.trial appropriately.
381 """
376 """
382
377
383 runners = make_runners()
378 runners = make_runners()
384
379
385 # Run the test runners in a temporary dir so we can nuke it when finished
380 # Run the test runners in a temporary dir so we can nuke it when finished
386 # to clean up any junk files left over by accident. This also makes it
381 # to clean up any junk files left over by accident. This also makes it
387 # robust against being run in non-writeable directories by mistake, as the
382 # robust against being run in non-writeable directories by mistake, as the
388 # temp dir will always be user-writeable.
383 # temp dir will always be user-writeable.
389 curdir = os.getcwd()
384 curdir = os.getcwd()
390 testdir = tempfile.gettempdir()
385 testdir = tempfile.gettempdir()
391 os.chdir(testdir)
386 os.chdir(testdir)
392
387
393 # Run all test runners, tracking execution time
388 # Run all test runners, tracking execution time
394 failed = []
389 failed = []
395 t_start = time.time()
390 t_start = time.time()
396 try:
391 try:
397 for (name, runner) in runners:
392 for (name, runner) in runners:
398 print '*'*70
393 print '*'*70
399 print 'IPython test group:',name
394 print 'IPython test group:',name
400 res = runner.run()
395 res = runner.run()
401 if res:
396 if res:
402 failed.append( (name, runner) )
397 failed.append( (name, runner) )
403 finally:
398 finally:
404 os.chdir(curdir)
399 os.chdir(curdir)
405 t_end = time.time()
400 t_end = time.time()
406 t_tests = t_end - t_start
401 t_tests = t_end - t_start
407 nrunners = len(runners)
402 nrunners = len(runners)
408 nfail = len(failed)
403 nfail = len(failed)
409 # summarize results
404 # summarize results
410 print
405 print
411 print '*'*70
406 print '*'*70
412 print 'Test suite completed for system with the following information:'
407 print 'Test suite completed for system with the following information:'
413 print report()
408 print report()
414 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
409 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
415 print
410 print
416 print 'Status:'
411 print 'Status:'
417 if not failed:
412 if not failed:
418 print 'OK'
413 print 'OK'
419 else:
414 else:
420 # If anything went wrong, point out what command to rerun manually to
415 # If anything went wrong, point out what command to rerun manually to
421 # see the actual errors and individual summary
416 # see the actual errors and individual summary
422 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
417 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
423 for name, failed_runner in failed:
418 for name, failed_runner in failed:
424 print '-'*40
419 print '-'*40
425 print 'Runner failed:',name
420 print 'Runner failed:',name
426 print 'You may wish to rerun this one individually, with:'
421 print 'You may wish to rerun this one individually, with:'
427 print ' '.join(failed_runner.call_args)
422 print ' '.join(failed_runner.call_args)
428 print
423 print
429
424
430
425
431 def main():
426 def main():
432 for arg in sys.argv[1:]:
427 for arg in sys.argv[1:]:
433 if arg.startswith('IPython'):
428 if arg.startswith('IPython'):
434 # This is in-process
429 # This is in-process
435 run_iptest()
430 run_iptest()
436 else:
431 else:
437 # This starts subprocesses
432 # This starts subprocesses
438 run_iptestall()
433 run_iptestall()
439
434
440
435
441 if __name__ == '__main__':
436 if __name__ == '__main__':
442 main()
437 main()
General Comments 0
You need to be logged in to leave comments. Login now