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