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