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