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