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