##// END OF EJS Templates
include IPython.zmq in iptest groups
MinRK -
Show More
@@ -1,520 +1,524 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-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 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.plugins.xunit import Xunit
45 from nose.plugins.xunit import Xunit
46 from nose import SkipTest
46 from nose import SkipTest
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.importstring import import_item
50 from IPython.utils.importstring import import_item
51 from IPython.utils.path import get_ipython_module_path
51 from IPython.utils.path import get_ipython_module_path
52 from IPython.utils.process import find_cmd, pycmd2argv
52 from IPython.utils.process import find_cmd, pycmd2argv
53 from IPython.utils.sysinfo import sys_info
53 from IPython.utils.sysinfo import sys_info
54
54
55 from IPython.testing import globalipapp
55 from IPython.testing import globalipapp
56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57 from IPython.external.decorators import KnownFailure, knownfailureif
57 from IPython.external.decorators import KnownFailure, knownfailureif
58
58
59 pjoin = path.join
59 pjoin = path.join
60
60
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Globals
63 # Globals
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Warnings control
68 # Warnings control
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 # Twisted generates annoying warnings with Python 2.6, as will do other code
71 # Twisted generates annoying warnings with Python 2.6, as will do other code
72 # that imports 'sets' as of today
72 # that imports 'sets' as of today
73 warnings.filterwarnings('ignore', 'the sets module is deprecated',
73 warnings.filterwarnings('ignore', 'the sets module is deprecated',
74 DeprecationWarning )
74 DeprecationWarning )
75
75
76 # This one also comes from Twisted
76 # This one also comes from Twisted
77 warnings.filterwarnings('ignore', 'the sha module is deprecated',
77 warnings.filterwarnings('ignore', 'the sha module is deprecated',
78 DeprecationWarning)
78 DeprecationWarning)
79
79
80 # Wx on Fedora11 spits these out
80 # Wx on Fedora11 spits these out
81 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
81 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
82 UserWarning)
82 UserWarning)
83
83
84 # ------------------------------------------------------------------------------
84 # ------------------------------------------------------------------------------
85 # Monkeypatch Xunit to count known failures as skipped.
85 # Monkeypatch Xunit to count known failures as skipped.
86 # ------------------------------------------------------------------------------
86 # ------------------------------------------------------------------------------
87 def monkeypatch_xunit():
87 def monkeypatch_xunit():
88 try:
88 try:
89 knownfailureif(True)(lambda: None)()
89 knownfailureif(True)(lambda: None)()
90 except Exception as e:
90 except Exception as e:
91 KnownFailureTest = type(e)
91 KnownFailureTest = type(e)
92
92
93 def addError(self, test, err, capt=None):
93 def addError(self, test, err, capt=None):
94 if issubclass(err[0], KnownFailureTest):
94 if issubclass(err[0], KnownFailureTest):
95 err = (SkipTest,) + err[1:]
95 err = (SkipTest,) + err[1:]
96 return self.orig_addError(test, err, capt)
96 return self.orig_addError(test, err, capt)
97
97
98 Xunit.orig_addError = Xunit.addError
98 Xunit.orig_addError = Xunit.addError
99 Xunit.addError = addError
99 Xunit.addError = addError
100
100
101 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
102 # Logic for skipping doctests
102 # Logic for skipping doctests
103 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
104 def extract_version(mod):
104 def extract_version(mod):
105 return mod.__version__
105 return mod.__version__
106
106
107 def test_for(item, min_version=None, callback=extract_version):
107 def test_for(item, min_version=None, callback=extract_version):
108 """Test to see if item is importable, and optionally check against a minimum
108 """Test to see if item is importable, and optionally check against a minimum
109 version.
109 version.
110
110
111 If min_version is given, the default behavior is to check against the
111 If min_version is given, the default behavior is to check against the
112 `__version__` attribute of the item, but specifying `callback` allows you to
112 `__version__` attribute of the item, but specifying `callback` allows you to
113 extract the value you are interested in. e.g::
113 extract the value you are interested in. e.g::
114
114
115 In [1]: import sys
115 In [1]: import sys
116
116
117 In [2]: from IPython.testing.iptest import test_for
117 In [2]: from IPython.testing.iptest import test_for
118
118
119 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
119 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
120 Out[3]: True
120 Out[3]: True
121
121
122 """
122 """
123 try:
123 try:
124 check = import_item(item)
124 check = import_item(item)
125 except (ImportError, RuntimeError):
125 except (ImportError, RuntimeError):
126 # GTK reports Runtime error if it can't be initialized even if it's
126 # GTK reports Runtime error if it can't be initialized even if it's
127 # importable.
127 # importable.
128 return False
128 return False
129 else:
129 else:
130 if min_version:
130 if min_version:
131 if callback:
131 if callback:
132 # extra processing step to get version to compare
132 # extra processing step to get version to compare
133 check = callback(check)
133 check = callback(check)
134
134
135 return check >= min_version
135 return check >= min_version
136 else:
136 else:
137 return True
137 return True
138
138
139 # Global dict where we can store information on what we have and what we don't
139 # Global dict where we can store information on what we have and what we don't
140 # have available at test run time
140 # have available at test run time
141 have = {}
141 have = {}
142
142
143 have['curses'] = test_for('_curses')
143 have['curses'] = test_for('_curses')
144 have['matplotlib'] = test_for('matplotlib')
144 have['matplotlib'] = test_for('matplotlib')
145 have['pexpect'] = test_for('IPython.external.pexpect')
145 have['pexpect'] = test_for('IPython.external.pexpect')
146 have['pymongo'] = test_for('pymongo')
146 have['pymongo'] = test_for('pymongo')
147 have['wx'] = test_for('wx')
147 have['wx'] = test_for('wx')
148 have['wx.aui'] = test_for('wx.aui')
148 have['wx.aui'] = test_for('wx.aui')
149 have['qt'] = test_for('IPython.external.qt')
149 have['qt'] = test_for('IPython.external.qt')
150 have['sqlite3'] = test_for('sqlite3')
150 have['sqlite3'] = test_for('sqlite3')
151
151
152 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
152 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
153
153
154 if os.name == 'nt':
154 if os.name == 'nt':
155 min_zmq = (2,1,7)
155 min_zmq = (2,1,7)
156 else:
156 else:
157 min_zmq = (2,1,4)
157 min_zmq = (2,1,4)
158
158
159 def version_tuple(mod):
159 def version_tuple(mod):
160 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
160 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
161 # turn 'dev' into 999, because Python3 rejects str-int comparisons
161 # turn 'dev' into 999, because Python3 rejects str-int comparisons
162 vs = mod.__version__.replace('dev', '.999')
162 vs = mod.__version__.replace('dev', '.999')
163 tup = tuple([int(v) for v in vs.split('.') ])
163 tup = tuple([int(v) for v in vs.split('.') ])
164 return tup
164 return tup
165
165
166 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
166 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
167
167
168 #-----------------------------------------------------------------------------
168 #-----------------------------------------------------------------------------
169 # Functions and classes
169 # Functions and classes
170 #-----------------------------------------------------------------------------
170 #-----------------------------------------------------------------------------
171
171
172 def report():
172 def report():
173 """Return a string with a summary report of test-related variables."""
173 """Return a string with a summary report of test-related variables."""
174
174
175 out = [ sys_info(), '\n']
175 out = [ sys_info(), '\n']
176
176
177 avail = []
177 avail = []
178 not_avail = []
178 not_avail = []
179
179
180 for k, is_avail in have.items():
180 for k, is_avail in have.items():
181 if is_avail:
181 if is_avail:
182 avail.append(k)
182 avail.append(k)
183 else:
183 else:
184 not_avail.append(k)
184 not_avail.append(k)
185
185
186 if avail:
186 if avail:
187 out.append('\nTools and libraries available at test time:\n')
187 out.append('\nTools and libraries available at test time:\n')
188 avail.sort()
188 avail.sort()
189 out.append(' ' + ' '.join(avail)+'\n')
189 out.append(' ' + ' '.join(avail)+'\n')
190
190
191 if not_avail:
191 if not_avail:
192 out.append('\nTools and libraries NOT available at test time:\n')
192 out.append('\nTools and libraries NOT available at test time:\n')
193 not_avail.sort()
193 not_avail.sort()
194 out.append(' ' + ' '.join(not_avail)+'\n')
194 out.append(' ' + ' '.join(not_avail)+'\n')
195
195
196 return ''.join(out)
196 return ''.join(out)
197
197
198
198
199 def make_exclude():
199 def make_exclude():
200 """Make patterns of modules and packages to exclude from testing.
200 """Make patterns of modules and packages to exclude from testing.
201
201
202 For the IPythonDoctest plugin, we need to exclude certain patterns that
202 For the IPythonDoctest plugin, we need to exclude certain patterns that
203 cause testing problems. We should strive to minimize the number of
203 cause testing problems. We should strive to minimize the number of
204 skipped modules, since this means untested code.
204 skipped modules, since this means untested code.
205
205
206 These modules and packages will NOT get scanned by nose at all for tests.
206 These modules and packages will NOT get scanned by nose at all for tests.
207 """
207 """
208 # Simple utility to make IPython paths more readably, we need a lot of
208 # Simple utility to make IPython paths more readably, we need a lot of
209 # these below
209 # these below
210 ipjoin = lambda *paths: pjoin('IPython', *paths)
210 ipjoin = lambda *paths: pjoin('IPython', *paths)
211
211
212 exclusions = [ipjoin('external'),
212 exclusions = [ipjoin('external'),
213 pjoin('IPython_doctest_plugin'),
213 pjoin('IPython_doctest_plugin'),
214 ipjoin('quarantine'),
214 ipjoin('quarantine'),
215 ipjoin('deathrow'),
215 ipjoin('deathrow'),
216 ipjoin('testing', 'attic'),
216 ipjoin('testing', 'attic'),
217 # This guy is probably attic material
217 # This guy is probably attic material
218 ipjoin('testing', 'mkdoctests'),
218 ipjoin('testing', 'mkdoctests'),
219 # Testing inputhook will need a lot of thought, to figure out
219 # Testing inputhook will need a lot of thought, to figure out
220 # how to have tests that don't lock up with the gui event
220 # how to have tests that don't lock up with the gui event
221 # loops in the picture
221 # loops in the picture
222 ipjoin('lib', 'inputhook'),
222 ipjoin('lib', 'inputhook'),
223 # Config files aren't really importable stand-alone
223 # Config files aren't really importable stand-alone
224 ipjoin('config', 'default'),
224 ipjoin('config', 'default'),
225 ipjoin('config', 'profile'),
225 ipjoin('config', 'profile'),
226 ]
226 ]
227 if not have['sqlite3']:
227 if not have['sqlite3']:
228 exclusions.append(ipjoin('core', 'tests', 'test_history'))
228 exclusions.append(ipjoin('core', 'tests', 'test_history'))
229 exclusions.append(ipjoin('core', 'history'))
229 exclusions.append(ipjoin('core', 'history'))
230 if not have['wx']:
230 if not have['wx']:
231 exclusions.append(ipjoin('lib', 'inputhookwx'))
231 exclusions.append(ipjoin('lib', 'inputhookwx'))
232
232
233 # We do this unconditionally, so that the test suite doesn't import
233 # We do this unconditionally, so that the test suite doesn't import
234 # gtk, changing the default encoding and masking some unicode bugs.
234 # gtk, changing the default encoding and masking some unicode bugs.
235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
236 exclusions.append(ipjoin('zmq', 'gui', 'gtkembed'))
236
237
237 # These have to be skipped on win32 because the use echo, rm, cd, etc.
238 # These have to be skipped on win32 because the use echo, rm, cd, etc.
238 # See ticket https://github.com/ipython/ipython/issues/87
239 # See ticket https://github.com/ipython/ipython/issues/87
239 if sys.platform == 'win32':
240 if sys.platform == 'win32':
240 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
241 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
241 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
242 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
242
243
243 if not have['pexpect']:
244 if not have['pexpect']:
244 exclusions.extend([ipjoin('scripts', 'irunner'),
245 exclusions.extend([ipjoin('scripts', 'irunner'),
245 ipjoin('lib', 'irunner'),
246 ipjoin('lib', 'irunner'),
246 ipjoin('lib', 'tests', 'test_irunner'),
247 ipjoin('lib', 'tests', 'test_irunner'),
247 ipjoin('frontend', 'terminal', 'console'),
248 ipjoin('frontend', 'terminal', 'console'),
248 ])
249 ])
249
250
250 if not have['zmq']:
251 if not have['zmq']:
251 exclusions.append(ipjoin('zmq'))
252 exclusions.append(ipjoin('zmq'))
252 exclusions.append(ipjoin('frontend', 'qt'))
253 exclusions.append(ipjoin('frontend', 'qt'))
253 exclusions.append(ipjoin('frontend', 'html'))
254 exclusions.append(ipjoin('frontend', 'html'))
254 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
255 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
255 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
256 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
256 exclusions.append(ipjoin('parallel'))
257 exclusions.append(ipjoin('parallel'))
257 elif not have['qt']:
258 elif not have['qt']:
258 exclusions.append(ipjoin('frontend', 'qt'))
259 exclusions.append(ipjoin('frontend', 'qt'))
259
260
260 if not have['pymongo']:
261 if not have['pymongo']:
261 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
262 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
262 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
263 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
263
264
264 if not have['matplotlib']:
265 if not have['matplotlib']:
265 exclusions.extend([ipjoin('core', 'pylabtools'),
266 exclusions.extend([ipjoin('core', 'pylabtools'),
266 ipjoin('core', 'tests', 'test_pylabtools')])
267 ipjoin('core', 'tests', 'test_pylabtools'),
268 ipjoin('zmq', 'pylab'),
269 ])
267
270
268 if not have['tornado']:
271 if not have['tornado']:
269 exclusions.append(ipjoin('frontend', 'html'))
272 exclusions.append(ipjoin('frontend', 'html'))
270
273
271 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
274 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
272 if sys.platform == 'win32':
275 if sys.platform == 'win32':
273 exclusions = [s.replace('\\','\\\\') for s in exclusions]
276 exclusions = [s.replace('\\','\\\\') for s in exclusions]
274
277
275 return exclusions
278 return exclusions
276
279
277
280
278 class IPTester(object):
281 class IPTester(object):
279 """Call that calls iptest or trial in a subprocess.
282 """Call that calls iptest or trial in a subprocess.
280 """
283 """
281 #: string, name of test runner that will be called
284 #: string, name of test runner that will be called
282 runner = None
285 runner = None
283 #: list, parameters for test runner
286 #: list, parameters for test runner
284 params = None
287 params = None
285 #: list, arguments of system call to be made to call test runner
288 #: list, arguments of system call to be made to call test runner
286 call_args = None
289 call_args = None
287 #: list, process ids of subprocesses we start (for cleanup)
290 #: list, process ids of subprocesses we start (for cleanup)
288 pids = None
291 pids = None
289 #: str, coverage xml output file
292 #: str, coverage xml output file
290 coverage_xml = None
293 coverage_xml = None
291
294
292 def __init__(self, runner='iptest', params=None):
295 def __init__(self, runner='iptest', params=None):
293 """Create new test runner."""
296 """Create new test runner."""
294 p = os.path
297 p = os.path
295 if runner == 'iptest':
298 if runner == 'iptest':
296 iptest_app = get_ipython_module_path('IPython.testing.iptest')
299 iptest_app = get_ipython_module_path('IPython.testing.iptest')
297 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
300 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
298 else:
301 else:
299 raise Exception('Not a valid test runner: %s' % repr(runner))
302 raise Exception('Not a valid test runner: %s' % repr(runner))
300 if params is None:
303 if params is None:
301 params = []
304 params = []
302 if isinstance(params, str):
305 if isinstance(params, str):
303 params = [params]
306 params = [params]
304 self.params = params
307 self.params = params
305
308
306 # Assemble call
309 # Assemble call
307 self.call_args = self.runner+self.params
310 self.call_args = self.runner+self.params
308
311
309 # Find the section we're testing (IPython.foo)
312 # Find the section we're testing (IPython.foo)
310 for sect in self.params:
313 for sect in self.params:
311 if sect.startswith('IPython'): break
314 if sect.startswith('IPython'): break
312 else:
315 else:
313 raise ValueError("Section not found", self.params)
316 raise ValueError("Section not found", self.params)
314
317
315 if '--with-xunit' in self.call_args:
318 if '--with-xunit' in self.call_args:
316 self.call_args.append('--xunit-file=%s' % path.abspath(sect+'.xunit.xml'))
319 self.call_args.append('--xunit-file=%s' % path.abspath(sect+'.xunit.xml'))
317
320
318 if '--with-xml-coverage' in self.call_args:
321 if '--with-xml-coverage' in self.call_args:
319 self.coverage_xml = path.abspath(sect+".coverage.xml")
322 self.coverage_xml = path.abspath(sect+".coverage.xml")
320 self.call_args.remove('--with-xml-coverage')
323 self.call_args.remove('--with-xml-coverage')
321 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
324 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
322
325
323 # Store pids of anything we start to clean up on deletion, if possible
326 # Store pids of anything we start to clean up on deletion, if possible
324 # (on posix only, since win32 has no os.kill)
327 # (on posix only, since win32 has no os.kill)
325 self.pids = []
328 self.pids = []
326
329
327 if sys.platform == 'win32':
330 if sys.platform == 'win32':
328 def _run_cmd(self):
331 def _run_cmd(self):
329 # On Windows, use os.system instead of subprocess.call, because I
332 # On Windows, use os.system instead of subprocess.call, because I
330 # was having problems with subprocess and I just don't know enough
333 # was having problems with subprocess and I just don't know enough
331 # about win32 to debug this reliably. Os.system may be the 'old
334 # about win32 to debug this reliably. Os.system may be the 'old
332 # fashioned' way to do it, but it works just fine. If someone
335 # fashioned' way to do it, but it works just fine. If someone
333 # later can clean this up that's fine, as long as the tests run
336 # later can clean this up that's fine, as long as the tests run
334 # reliably in win32.
337 # reliably in win32.
335 # What types of problems are you having. They may be related to
338 # What types of problems are you having. They may be related to
336 # running Python in unboffered mode. BG.
339 # running Python in unboffered mode. BG.
337 return os.system(' '.join(self.call_args))
340 return os.system(' '.join(self.call_args))
338 else:
341 else:
339 def _run_cmd(self):
342 def _run_cmd(self):
340 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
343 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
341 subp = subprocess.Popen(self.call_args)
344 subp = subprocess.Popen(self.call_args)
342 self.pids.append(subp.pid)
345 self.pids.append(subp.pid)
343 # If this fails, the pid will be left in self.pids and cleaned up
346 # If this fails, the pid will be left in self.pids and cleaned up
344 # later, but if the wait call succeeds, then we can clear the
347 # later, but if the wait call succeeds, then we can clear the
345 # stored pid.
348 # stored pid.
346 retcode = subp.wait()
349 retcode = subp.wait()
347 self.pids.pop()
350 self.pids.pop()
348 return retcode
351 return retcode
349
352
350 def run(self):
353 def run(self):
351 """Run the stored commands"""
354 """Run the stored commands"""
352 try:
355 try:
353 retcode = self._run_cmd()
356 retcode = self._run_cmd()
354 except:
357 except:
355 import traceback
358 import traceback
356 traceback.print_exc()
359 traceback.print_exc()
357 return 1 # signal failure
360 return 1 # signal failure
358
361
359 if self.coverage_xml:
362 if self.coverage_xml:
360 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
363 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
361 return retcode
364 return retcode
362
365
363 def __del__(self):
366 def __del__(self):
364 """Cleanup on exit by killing any leftover processes."""
367 """Cleanup on exit by killing any leftover processes."""
365
368
366 if not hasattr(os, 'kill'):
369 if not hasattr(os, 'kill'):
367 return
370 return
368
371
369 for pid in self.pids:
372 for pid in self.pids:
370 try:
373 try:
371 print 'Cleaning stale PID:', pid
374 print 'Cleaning stale PID:', pid
372 os.kill(pid, signal.SIGKILL)
375 os.kill(pid, signal.SIGKILL)
373 except OSError:
376 except OSError:
374 # This is just a best effort, if we fail or the process was
377 # This is just a best effort, if we fail or the process was
375 # really gone, ignore it.
378 # really gone, ignore it.
376 pass
379 pass
377
380
378
381
379 def make_runners():
382 def make_runners():
380 """Define the top-level packages that need to be tested.
383 """Define the top-level packages that need to be tested.
381 """
384 """
382
385
383 # Packages to be tested via nose, that only depend on the stdlib
386 # Packages to be tested via nose, that only depend on the stdlib
384 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
387 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
385 'scripts', 'testing', 'utils', 'nbformat' ]
388 'scripts', 'testing', 'utils', 'nbformat' ]
386
389
387 if have['zmq']:
390 if have['zmq']:
391 nose_pkg_names.append('zmq')
388 nose_pkg_names.append('parallel')
392 nose_pkg_names.append('parallel')
389
393
390 # For debugging this code, only load quick stuff
394 # For debugging this code, only load quick stuff
391 #nose_pkg_names = ['core', 'extensions'] # dbg
395 #nose_pkg_names = ['core', 'extensions'] # dbg
392
396
393 # Make fully qualified package names prepending 'IPython.' to our name lists
397 # Make fully qualified package names prepending 'IPython.' to our name lists
394 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
398 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
395
399
396 # Make runners
400 # Make runners
397 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
401 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
398
402
399 return runners
403 return runners
400
404
401
405
402 def run_iptest():
406 def run_iptest():
403 """Run the IPython test suite using nose.
407 """Run the IPython test suite using nose.
404
408
405 This function is called when this script is **not** called with the form
409 This function is called when this script is **not** called with the form
406 `iptest all`. It simply calls nose with appropriate command line flags
410 `iptest all`. It simply calls nose with appropriate command line flags
407 and accepts all of the standard nose arguments.
411 and accepts all of the standard nose arguments.
408 """
412 """
409 # Apply our monkeypatch to Xunit
413 # Apply our monkeypatch to Xunit
410 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
414 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
411 monkeypatch_xunit()
415 monkeypatch_xunit()
412
416
413 warnings.filterwarnings('ignore',
417 warnings.filterwarnings('ignore',
414 'This will be removed soon. Use IPython.testing.util instead')
418 'This will be removed soon. Use IPython.testing.util instead')
415
419
416 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
420 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
417
421
418 '--with-ipdoctest',
422 '--with-ipdoctest',
419 '--ipdoctest-tests','--ipdoctest-extension=txt',
423 '--ipdoctest-tests','--ipdoctest-extension=txt',
420
424
421 # We add --exe because of setuptools' imbecility (it
425 # We add --exe because of setuptools' imbecility (it
422 # blindly does chmod +x on ALL files). Nose does the
426 # blindly does chmod +x on ALL files). Nose does the
423 # right thing and it tries to avoid executables,
427 # right thing and it tries to avoid executables,
424 # setuptools unfortunately forces our hand here. This
428 # setuptools unfortunately forces our hand here. This
425 # has been discussed on the distutils list and the
429 # has been discussed on the distutils list and the
426 # setuptools devs refuse to fix this problem!
430 # setuptools devs refuse to fix this problem!
427 '--exe',
431 '--exe',
428 ]
432 ]
429
433
430 if nose.__version__ >= '0.11':
434 if nose.__version__ >= '0.11':
431 # I don't fully understand why we need this one, but depending on what
435 # I don't fully understand why we need this one, but depending on what
432 # directory the test suite is run from, if we don't give it, 0 tests
436 # directory the test suite is run from, if we don't give it, 0 tests
433 # get run. Specifically, if the test suite is run from the source dir
437 # get run. Specifically, if the test suite is run from the source dir
434 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
438 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
435 # even if the same call done in this directory works fine). It appears
439 # even if the same call done in this directory works fine). It appears
436 # that if the requested package is in the current dir, nose bails early
440 # that if the requested package is in the current dir, nose bails early
437 # by default. Since it's otherwise harmless, leave it in by default
441 # by default. Since it's otherwise harmless, leave it in by default
438 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
442 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
439 argv.append('--traverse-namespace')
443 argv.append('--traverse-namespace')
440
444
441 # use our plugin for doctesting. It will remove the standard doctest plugin
445 # use our plugin for doctesting. It will remove the standard doctest plugin
442 # if it finds it enabled
446 # if it finds it enabled
443 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
447 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
444 # We need a global ipython running in this process
448 # We need a global ipython running in this process
445 globalipapp.start_ipython()
449 globalipapp.start_ipython()
446 # Now nose can run
450 # Now nose can run
447 TestProgram(argv=argv, addplugins=plugins)
451 TestProgram(argv=argv, addplugins=plugins)
448
452
449
453
450 def run_iptestall():
454 def run_iptestall():
451 """Run the entire IPython test suite by calling nose and trial.
455 """Run the entire IPython test suite by calling nose and trial.
452
456
453 This function constructs :class:`IPTester` instances for all IPython
457 This function constructs :class:`IPTester` instances for all IPython
454 modules and package and then runs each of them. This causes the modules
458 modules and package and then runs each of them. This causes the modules
455 and packages of IPython to be tested each in their own subprocess using
459 and packages of IPython to be tested each in their own subprocess using
456 nose.
460 nose.
457 """
461 """
458
462
459 runners = make_runners()
463 runners = make_runners()
460
464
461 # Run the test runners in a temporary dir so we can nuke it when finished
465 # Run the test runners in a temporary dir so we can nuke it when finished
462 # to clean up any junk files left over by accident. This also makes it
466 # to clean up any junk files left over by accident. This also makes it
463 # robust against being run in non-writeable directories by mistake, as the
467 # robust against being run in non-writeable directories by mistake, as the
464 # temp dir will always be user-writeable.
468 # temp dir will always be user-writeable.
465 curdir = os.getcwdu()
469 curdir = os.getcwdu()
466 testdir = tempfile.gettempdir()
470 testdir = tempfile.gettempdir()
467 os.chdir(testdir)
471 os.chdir(testdir)
468
472
469 # Run all test runners, tracking execution time
473 # Run all test runners, tracking execution time
470 failed = []
474 failed = []
471 t_start = time.time()
475 t_start = time.time()
472 try:
476 try:
473 for (name, runner) in runners:
477 for (name, runner) in runners:
474 print '*'*70
478 print '*'*70
475 print 'IPython test group:',name
479 print 'IPython test group:',name
476 res = runner.run()
480 res = runner.run()
477 if res:
481 if res:
478 failed.append( (name, runner) )
482 failed.append( (name, runner) )
479 finally:
483 finally:
480 os.chdir(curdir)
484 os.chdir(curdir)
481 t_end = time.time()
485 t_end = time.time()
482 t_tests = t_end - t_start
486 t_tests = t_end - t_start
483 nrunners = len(runners)
487 nrunners = len(runners)
484 nfail = len(failed)
488 nfail = len(failed)
485 # summarize results
489 # summarize results
486 print
490 print
487 print '*'*70
491 print '*'*70
488 print 'Test suite completed for system with the following information:'
492 print 'Test suite completed for system with the following information:'
489 print report()
493 print report()
490 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
494 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
491 print
495 print
492 print 'Status:'
496 print 'Status:'
493 if not failed:
497 if not failed:
494 print 'OK'
498 print 'OK'
495 else:
499 else:
496 # If anything went wrong, point out what command to rerun manually to
500 # If anything went wrong, point out what command to rerun manually to
497 # see the actual errors and individual summary
501 # see the actual errors and individual summary
498 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
502 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
499 for name, failed_runner in failed:
503 for name, failed_runner in failed:
500 print '-'*40
504 print '-'*40
501 print 'Runner failed:',name
505 print 'Runner failed:',name
502 print 'You may wish to rerun this one individually, with:'
506 print 'You may wish to rerun this one individually, with:'
503 print ' '.join(failed_runner.call_args)
507 print ' '.join(failed_runner.call_args)
504 print
508 print
505 # Ensure that our exit code indicates failure
509 # Ensure that our exit code indicates failure
506 sys.exit(1)
510 sys.exit(1)
507
511
508
512
509 def main():
513 def main():
510 for arg in sys.argv[1:]:
514 for arg in sys.argv[1:]:
511 if arg.startswith('IPython'):
515 if arg.startswith('IPython'):
512 # This is in-process
516 # This is in-process
513 run_iptest()
517 run_iptest()
514 else:
518 else:
515 # This starts subprocesses
519 # This starts subprocesses
516 run_iptestall()
520 run_iptestall()
517
521
518
522
519 if __name__ == '__main__':
523 if __name__ == '__main__':
520 main()
524 main()
General Comments 0
You need to be logged in to leave comments. Login now