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