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