##// END OF EJS Templates
Adding exclusion for the azure module in iptest.
Brian Granger -
Show More
@@ -1,579 +1,583 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['wx'] = test_for('wx')
165 have['wx'] = test_for('wx')
166 have['wx.aui'] = test_for('wx.aui')
166 have['wx.aui'] = test_for('wx.aui')
167 have['azure'] = test_for('azure')
167
168
168 if os.name == 'nt':
169 if os.name == 'nt':
169 min_zmq = (2,1,7)
170 min_zmq = (2,1,7)
170 else:
171 else:
171 min_zmq = (2,1,4)
172 min_zmq = (2,1,4)
172
173
173 def version_tuple(mod):
174 def version_tuple(mod):
174 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
175 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
175 # turn 'dev' into 999, because Python3 rejects str-int comparisons
176 # turn 'dev' into 999, because Python3 rejects str-int comparisons
176 vs = mod.__version__.replace('dev', '.999')
177 vs = mod.__version__.replace('dev', '.999')
177 tup = tuple([int(v) for v in vs.split('.') ])
178 tup = tuple([int(v) for v in vs.split('.') ])
178 return tup
179 return tup
179
180
180 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
181 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
181
182
182 #-----------------------------------------------------------------------------
183 #-----------------------------------------------------------------------------
183 # Functions and classes
184 # Functions and classes
184 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
185
186
186 def report():
187 def report():
187 """Return a string with a summary report of test-related variables."""
188 """Return a string with a summary report of test-related variables."""
188
189
189 out = [ sys_info(), '\n']
190 out = [ sys_info(), '\n']
190
191
191 avail = []
192 avail = []
192 not_avail = []
193 not_avail = []
193
194
194 for k, is_avail in have.items():
195 for k, is_avail in have.items():
195 if is_avail:
196 if is_avail:
196 avail.append(k)
197 avail.append(k)
197 else:
198 else:
198 not_avail.append(k)
199 not_avail.append(k)
199
200
200 if avail:
201 if avail:
201 out.append('\nTools and libraries available at test time:\n')
202 out.append('\nTools and libraries available at test time:\n')
202 avail.sort()
203 avail.sort()
203 out.append(' ' + ' '.join(avail)+'\n')
204 out.append(' ' + ' '.join(avail)+'\n')
204
205
205 if not_avail:
206 if not_avail:
206 out.append('\nTools and libraries NOT available at test time:\n')
207 out.append('\nTools and libraries NOT available at test time:\n')
207 not_avail.sort()
208 not_avail.sort()
208 out.append(' ' + ' '.join(not_avail)+'\n')
209 out.append(' ' + ' '.join(not_avail)+'\n')
209
210
210 return ''.join(out)
211 return ''.join(out)
211
212
212
213
213 def make_exclude():
214 def make_exclude():
214 """Make patterns of modules and packages to exclude from testing.
215 """Make patterns of modules and packages to exclude from testing.
215
216
216 For the IPythonDoctest plugin, we need to exclude certain patterns that
217 For the IPythonDoctest plugin, we need to exclude certain patterns that
217 cause testing problems. We should strive to minimize the number of
218 cause testing problems. We should strive to minimize the number of
218 skipped modules, since this means untested code.
219 skipped modules, since this means untested code.
219
220
220 These modules and packages will NOT get scanned by nose at all for tests.
221 These modules and packages will NOT get scanned by nose at all for tests.
221 """
222 """
222 # Simple utility to make IPython paths more readably, we need a lot of
223 # Simple utility to make IPython paths more readably, we need a lot of
223 # these below
224 # these below
224 ipjoin = lambda *paths: pjoin('IPython', *paths)
225 ipjoin = lambda *paths: pjoin('IPython', *paths)
225
226
226 exclusions = [ipjoin('external'),
227 exclusions = [ipjoin('external'),
227 ipjoin('quarantine'),
228 ipjoin('quarantine'),
228 ipjoin('deathrow'),
229 ipjoin('deathrow'),
229 # This guy is probably attic material
230 # This guy is probably attic material
230 ipjoin('testing', 'mkdoctests'),
231 ipjoin('testing', 'mkdoctests'),
231 # Testing inputhook will need a lot of thought, to figure out
232 # Testing inputhook will need a lot of thought, to figure out
232 # how to have tests that don't lock up with the gui event
233 # how to have tests that don't lock up with the gui event
233 # loops in the picture
234 # loops in the picture
234 ipjoin('lib', 'inputhook'),
235 ipjoin('lib', 'inputhook'),
235 # Config files aren't really importable stand-alone
236 # Config files aren't really importable stand-alone
236 ipjoin('config', 'profile'),
237 ipjoin('config', 'profile'),
237 # The notebook 'static' directory contains JS, css and other
238 # The notebook 'static' directory contains JS, css and other
238 # files for web serving. Occasionally projects may put a .py
239 # files for web serving. Occasionally projects may put a .py
239 # file in there (MathJax ships a conf.py), so we might as
240 # file in there (MathJax ships a conf.py), so we might as
240 # well play it safe and skip the whole thing.
241 # well play it safe and skip the whole thing.
241 ipjoin('frontend', 'html', 'notebook', 'static')
242 ipjoin('frontend', 'html', 'notebook', 'static')
242 ]
243 ]
243 if not have['sqlite3']:
244 if not have['sqlite3']:
244 exclusions.append(ipjoin('core', 'tests', 'test_history'))
245 exclusions.append(ipjoin('core', 'tests', 'test_history'))
245 exclusions.append(ipjoin('core', 'history'))
246 exclusions.append(ipjoin('core', 'history'))
246 if not have['wx']:
247 if not have['wx']:
247 exclusions.append(ipjoin('lib', 'inputhookwx'))
248 exclusions.append(ipjoin('lib', 'inputhookwx'))
248
249
249 # FIXME: temporarily disable autoreload tests, as they can produce
250 # FIXME: temporarily disable autoreload tests, as they can produce
250 # spurious failures in subsequent tests (cythonmagic).
251 # spurious failures in subsequent tests (cythonmagic).
251 exclusions.append(ipjoin('extensions', 'autoreload'))
252 exclusions.append(ipjoin('extensions', 'autoreload'))
252 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
253 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
253
254
254 # We do this unconditionally, so that the test suite doesn't import
255 # We do this unconditionally, so that the test suite doesn't import
255 # gtk, changing the default encoding and masking some unicode bugs.
256 # gtk, changing the default encoding and masking some unicode bugs.
256 exclusions.append(ipjoin('lib', 'inputhookgtk'))
257 exclusions.append(ipjoin('lib', 'inputhookgtk'))
257 exclusions.append(ipjoin('zmq', 'gui', 'gtkembed'))
258 exclusions.append(ipjoin('zmq', 'gui', 'gtkembed'))
258
259
259 # These have to be skipped on win32 because the use echo, rm, cd, etc.
260 # These have to be skipped on win32 because the use echo, rm, cd, etc.
260 # See ticket https://github.com/ipython/ipython/issues/87
261 # See ticket https://github.com/ipython/ipython/issues/87
261 if sys.platform == 'win32':
262 if sys.platform == 'win32':
262 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
263 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
263 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
264 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
264
265
265 if not have['pexpect']:
266 if not have['pexpect']:
266 exclusions.extend([ipjoin('lib', 'irunner'),
267 exclusions.extend([ipjoin('lib', 'irunner'),
267 ipjoin('lib', 'tests', 'test_irunner'),
268 ipjoin('lib', 'tests', 'test_irunner'),
268 ipjoin('frontend', 'terminal', 'console'),
269 ipjoin('frontend', 'terminal', 'console'),
269 ])
270 ])
270
271
271 if not have['zmq']:
272 if not have['zmq']:
272 exclusions.append(ipjoin('zmq'))
273 exclusions.append(ipjoin('zmq'))
273 exclusions.append(ipjoin('frontend', 'qt'))
274 exclusions.append(ipjoin('frontend', 'qt'))
274 exclusions.append(ipjoin('frontend', 'html'))
275 exclusions.append(ipjoin('frontend', 'html'))
275 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
276 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
276 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
277 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
277 exclusions.append(ipjoin('parallel'))
278 exclusions.append(ipjoin('parallel'))
278 elif not have['qt'] or not have['pygments']:
279 elif not have['qt'] or not have['pygments']:
279 exclusions.append(ipjoin('frontend', 'qt'))
280 exclusions.append(ipjoin('frontend', 'qt'))
280
281
281 if not have['pymongo']:
282 if not have['pymongo']:
282 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
283 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
283 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
284 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
284
285
285 if not have['matplotlib']:
286 if not have['matplotlib']:
286 exclusions.extend([ipjoin('core', 'pylabtools'),
287 exclusions.extend([ipjoin('core', 'pylabtools'),
287 ipjoin('core', 'tests', 'test_pylabtools'),
288 ipjoin('core', 'tests', 'test_pylabtools'),
288 ipjoin('zmq', 'pylab'),
289 ipjoin('zmq', 'pylab'),
289 ])
290 ])
290
291
291 if not have['cython']:
292 if not have['cython']:
292 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
293 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
293 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
294 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
294
295
295 if not have['oct2py']:
296 if not have['oct2py']:
296 exclusions.extend([ipjoin('extensions', 'octavemagic')])
297 exclusions.extend([ipjoin('extensions', 'octavemagic')])
297 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
298 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
298
299
299 if not have['tornado']:
300 if not have['tornado']:
300 exclusions.append(ipjoin('frontend', 'html'))
301 exclusions.append(ipjoin('frontend', 'html'))
301
302
302 if not have['rpy2'] or not have['numpy']:
303 if not have['rpy2'] or not have['numpy']:
303 exclusions.append(ipjoin('extensions', 'rmagic'))
304 exclusions.append(ipjoin('extensions', 'rmagic'))
304 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
305 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
305
306
307 if not have['azure']:
308 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'azurenbmanager'))
309
306 # 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.
307 if sys.platform == 'win32':
311 if sys.platform == 'win32':
308 exclusions = [s.replace('\\','\\\\') for s in exclusions]
312 exclusions = [s.replace('\\','\\\\') for s in exclusions]
309
313
310 # check for any exclusions that don't seem to exist:
314 # check for any exclusions that don't seem to exist:
311 parent, _ = os.path.split(get_ipython_package_dir())
315 parent, _ = os.path.split(get_ipython_package_dir())
312 for exclusion in exclusions:
316 for exclusion in exclusions:
313 if exclusion.endswith(('deathrow', 'quarantine')):
317 if exclusion.endswith(('deathrow', 'quarantine')):
314 # ignore deathrow/quarantine, which exist in dev, but not install
318 # ignore deathrow/quarantine, which exist in dev, but not install
315 continue
319 continue
316 fullpath = pjoin(parent, exclusion)
320 fullpath = pjoin(parent, exclusion)
317 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
321 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
318 warn("Excluding nonexistent file: %r\n" % exclusion)
322 warn("Excluding nonexistent file: %r\n" % exclusion)
319
323
320 return exclusions
324 return exclusions
321
325
322
326
323 class IPTester(object):
327 class IPTester(object):
324 """Call that calls iptest or trial in a subprocess.
328 """Call that calls iptest or trial in a subprocess.
325 """
329 """
326 #: string, name of test runner that will be called
330 #: string, name of test runner that will be called
327 runner = None
331 runner = None
328 #: list, parameters for test runner
332 #: list, parameters for test runner
329 params = None
333 params = None
330 #: 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
331 call_args = None
335 call_args = None
332 #: list, subprocesses we start (for cleanup)
336 #: list, subprocesses we start (for cleanup)
333 processes = None
337 processes = None
334 #: str, coverage xml output file
338 #: str, coverage xml output file
335 coverage_xml = None
339 coverage_xml = None
336
340
337 def __init__(self, runner='iptest', params=None):
341 def __init__(self, runner='iptest', params=None):
338 """Create new test runner."""
342 """Create new test runner."""
339 p = os.path
343 p = os.path
340 if runner == 'iptest':
344 if runner == 'iptest':
341 iptest_app = get_ipython_module_path('IPython.testing.iptest')
345 iptest_app = get_ipython_module_path('IPython.testing.iptest')
342 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
346 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
343 else:
347 else:
344 raise Exception('Not a valid test runner: %s' % repr(runner))
348 raise Exception('Not a valid test runner: %s' % repr(runner))
345 if params is None:
349 if params is None:
346 params = []
350 params = []
347 if isinstance(params, str):
351 if isinstance(params, str):
348 params = [params]
352 params = [params]
349 self.params = params
353 self.params = params
350
354
351 # Assemble call
355 # Assemble call
352 self.call_args = self.runner+self.params
356 self.call_args = self.runner+self.params
353
357
354 # Find the section we're testing (IPython.foo)
358 # Find the section we're testing (IPython.foo)
355 for sect in self.params:
359 for sect in self.params:
356 if sect.startswith('IPython'): break
360 if sect.startswith('IPython'): break
357 else:
361 else:
358 raise ValueError("Section not found", self.params)
362 raise ValueError("Section not found", self.params)
359
363
360 if '--with-xunit' in self.call_args:
364 if '--with-xunit' in self.call_args:
361
365
362 self.call_args.append('--xunit-file')
366 self.call_args.append('--xunit-file')
363 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
367 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
364 xunit_file = path.abspath(sect+'.xunit.xml')
368 xunit_file = path.abspath(sect+'.xunit.xml')
365 if sys.platform == 'win32':
369 if sys.platform == 'win32':
366 xunit_file = '"%s"' % xunit_file
370 xunit_file = '"%s"' % xunit_file
367 self.call_args.append(xunit_file)
371 self.call_args.append(xunit_file)
368
372
369 if '--with-xml-coverage' in self.call_args:
373 if '--with-xml-coverage' in self.call_args:
370 self.coverage_xml = path.abspath(sect+".coverage.xml")
374 self.coverage_xml = path.abspath(sect+".coverage.xml")
371 self.call_args.remove('--with-xml-coverage')
375 self.call_args.remove('--with-xml-coverage')
372 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
376 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
373
377
374 # Store anything we start to clean up on deletion
378 # Store anything we start to clean up on deletion
375 self.processes = []
379 self.processes = []
376
380
377 def _run_cmd(self):
381 def _run_cmd(self):
378 with TemporaryDirectory() as IPYTHONDIR:
382 with TemporaryDirectory() as IPYTHONDIR:
379 env = os.environ.copy()
383 env = os.environ.copy()
380 env['IPYTHONDIR'] = IPYTHONDIR
384 env['IPYTHONDIR'] = IPYTHONDIR
381 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
385 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
382 subp = subprocess.Popen(self.call_args, env=env)
386 subp = subprocess.Popen(self.call_args, env=env)
383 self.processes.append(subp)
387 self.processes.append(subp)
384 # If this fails, the process will be left in self.processes and
388 # If this fails, the process will be left in self.processes and
385 # cleaned up later, but if the wait call succeeds, then we can
389 # cleaned up later, but if the wait call succeeds, then we can
386 # clear the stored process.
390 # clear the stored process.
387 retcode = subp.wait()
391 retcode = subp.wait()
388 self.processes.pop()
392 self.processes.pop()
389 return retcode
393 return retcode
390
394
391 def run(self):
395 def run(self):
392 """Run the stored commands"""
396 """Run the stored commands"""
393 try:
397 try:
394 retcode = self._run_cmd()
398 retcode = self._run_cmd()
395 except:
399 except:
396 import traceback
400 import traceback
397 traceback.print_exc()
401 traceback.print_exc()
398 return 1 # signal failure
402 return 1 # signal failure
399
403
400 if self.coverage_xml:
404 if self.coverage_xml:
401 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
405 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
402 return retcode
406 return retcode
403
407
404 def __del__(self):
408 def __del__(self):
405 """Cleanup on exit by killing any leftover processes."""
409 """Cleanup on exit by killing any leftover processes."""
406 for subp in self.processes:
410 for subp in self.processes:
407 if subp.poll() is not None:
411 if subp.poll() is not None:
408 continue # process is already dead
412 continue # process is already dead
409
413
410 try:
414 try:
411 print('Cleaning stale PID: %d' % subp.pid)
415 print('Cleaning stale PID: %d' % subp.pid)
412 subp.kill()
416 subp.kill()
413 except: # (OSError, WindowsError) ?
417 except: # (OSError, WindowsError) ?
414 # 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
415 # really gone, ignore it.
419 # really gone, ignore it.
416 pass
420 pass
417
421
418 if subp.poll() is None:
422 if subp.poll() is None:
419 # The process did not die...
423 # The process did not die...
420 print('... failed. Manual cleanup may be required.'
424 print('... failed. Manual cleanup may be required.'
421 % subp.pid)
425 % subp.pid)
422
426
423 def make_runners(inc_slow=False):
427 def make_runners(inc_slow=False):
424 """Define the top-level packages that need to be tested.
428 """Define the top-level packages that need to be tested.
425 """
429 """
426
430
427 # Packages to be tested via nose, that only depend on the stdlib
431 # Packages to be tested via nose, that only depend on the stdlib
428 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
432 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
429 'testing', 'utils', 'nbformat' ]
433 'testing', 'utils', 'nbformat' ]
430
434
431 if have['zmq']:
435 if have['zmq']:
432 nose_pkg_names.append('zmq')
436 nose_pkg_names.append('zmq')
433 if inc_slow:
437 if inc_slow:
434 nose_pkg_names.append('parallel')
438 nose_pkg_names.append('parallel')
435
439
436 # For debugging this code, only load quick stuff
440 # For debugging this code, only load quick stuff
437 #nose_pkg_names = ['core', 'extensions'] # dbg
441 #nose_pkg_names = ['core', 'extensions'] # dbg
438
442
439 # Make fully qualified package names prepending 'IPython.' to our name lists
443 # Make fully qualified package names prepending 'IPython.' to our name lists
440 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
444 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
441
445
442 # Make runners
446 # Make runners
443 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
447 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
444
448
445 return runners
449 return runners
446
450
447
451
448 def run_iptest():
452 def run_iptest():
449 """Run the IPython test suite using nose.
453 """Run the IPython test suite using nose.
450
454
451 This function is called when this script is **not** called with the form
455 This function is called when this script is **not** called with the form
452 `iptest all`. It simply calls nose with appropriate command line flags
456 `iptest all`. It simply calls nose with appropriate command line flags
453 and accepts all of the standard nose arguments.
457 and accepts all of the standard nose arguments.
454 """
458 """
455 # Apply our monkeypatch to Xunit
459 # Apply our monkeypatch to Xunit
456 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
460 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
457 monkeypatch_xunit()
461 monkeypatch_xunit()
458
462
459 warnings.filterwarnings('ignore',
463 warnings.filterwarnings('ignore',
460 'This will be removed soon. Use IPython.testing.util instead')
464 'This will be removed soon. Use IPython.testing.util instead')
461
465
462 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
466 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
463
467
464 '--with-ipdoctest',
468 '--with-ipdoctest',
465 '--ipdoctest-tests','--ipdoctest-extension=txt',
469 '--ipdoctest-tests','--ipdoctest-extension=txt',
466
470
467 # We add --exe because of setuptools' imbecility (it
471 # We add --exe because of setuptools' imbecility (it
468 # blindly does chmod +x on ALL files). Nose does the
472 # blindly does chmod +x on ALL files). Nose does the
469 # right thing and it tries to avoid executables,
473 # right thing and it tries to avoid executables,
470 # setuptools unfortunately forces our hand here. This
474 # setuptools unfortunately forces our hand here. This
471 # has been discussed on the distutils list and the
475 # has been discussed on the distutils list and the
472 # setuptools devs refuse to fix this problem!
476 # setuptools devs refuse to fix this problem!
473 '--exe',
477 '--exe',
474 ]
478 ]
475
479
476 if nose.__version__ >= '0.11':
480 if nose.__version__ >= '0.11':
477 # I don't fully understand why we need this one, but depending on what
481 # I don't fully understand why we need this one, but depending on what
478 # directory the test suite is run from, if we don't give it, 0 tests
482 # directory the test suite is run from, if we don't give it, 0 tests
479 # get run. Specifically, if the test suite is run from the source dir
483 # get run. Specifically, if the test suite is run from the source dir
480 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
484 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
481 # even if the same call done in this directory works fine). It appears
485 # even if the same call done in this directory works fine). It appears
482 # that if the requested package is in the current dir, nose bails early
486 # that if the requested package is in the current dir, nose bails early
483 # by default. Since it's otherwise harmless, leave it in by default
487 # by default. Since it's otherwise harmless, leave it in by default
484 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
488 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
485 argv.append('--traverse-namespace')
489 argv.append('--traverse-namespace')
486
490
487 # use our plugin for doctesting. It will remove the standard doctest plugin
491 # use our plugin for doctesting. It will remove the standard doctest plugin
488 # if it finds it enabled
492 # if it finds it enabled
489 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
493 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
490 # We need a global ipython running in this process
494 # We need a global ipython running in this process
491 globalipapp.start_ipython()
495 globalipapp.start_ipython()
492 # Now nose can run
496 # Now nose can run
493 TestProgram(argv=argv, addplugins=plugins)
497 TestProgram(argv=argv, addplugins=plugins)
494
498
495
499
496 def run_iptestall(inc_slow=False):
500 def run_iptestall(inc_slow=False):
497 """Run the entire IPython test suite by calling nose and trial.
501 """Run the entire IPython test suite by calling nose and trial.
498
502
499 This function constructs :class:`IPTester` instances for all IPython
503 This function constructs :class:`IPTester` instances for all IPython
500 modules and package and then runs each of them. This causes the modules
504 modules and package and then runs each of them. This causes the modules
501 and packages of IPython to be tested each in their own subprocess using
505 and packages of IPython to be tested each in their own subprocess using
502 nose.
506 nose.
503
507
504 Parameters
508 Parameters
505 ----------
509 ----------
506
510
507 inc_slow : bool, optional
511 inc_slow : bool, optional
508 Include slow tests, like IPython.parallel. By default, these tests aren't
512 Include slow tests, like IPython.parallel. By default, these tests aren't
509 run.
513 run.
510 """
514 """
511
515
512 runners = make_runners(inc_slow=inc_slow)
516 runners = make_runners(inc_slow=inc_slow)
513
517
514 # Run the test runners in a temporary dir so we can nuke it when finished
518 # Run the test runners in a temporary dir so we can nuke it when finished
515 # to clean up any junk files left over by accident. This also makes it
519 # to clean up any junk files left over by accident. This also makes it
516 # robust against being run in non-writeable directories by mistake, as the
520 # robust against being run in non-writeable directories by mistake, as the
517 # temp dir will always be user-writeable.
521 # temp dir will always be user-writeable.
518 curdir = os.getcwdu()
522 curdir = os.getcwdu()
519 testdir = tempfile.gettempdir()
523 testdir = tempfile.gettempdir()
520 os.chdir(testdir)
524 os.chdir(testdir)
521
525
522 # Run all test runners, tracking execution time
526 # Run all test runners, tracking execution time
523 failed = []
527 failed = []
524 t_start = time.time()
528 t_start = time.time()
525 try:
529 try:
526 for (name, runner) in runners:
530 for (name, runner) in runners:
527 print('*'*70)
531 print('*'*70)
528 print('IPython test group:',name)
532 print('IPython test group:',name)
529 res = runner.run()
533 res = runner.run()
530 if res:
534 if res:
531 failed.append( (name, runner) )
535 failed.append( (name, runner) )
532 finally:
536 finally:
533 os.chdir(curdir)
537 os.chdir(curdir)
534 t_end = time.time()
538 t_end = time.time()
535 t_tests = t_end - t_start
539 t_tests = t_end - t_start
536 nrunners = len(runners)
540 nrunners = len(runners)
537 nfail = len(failed)
541 nfail = len(failed)
538 # summarize results
542 # summarize results
539 print()
543 print()
540 print('*'*70)
544 print('*'*70)
541 print('Test suite completed for system with the following information:')
545 print('Test suite completed for system with the following information:')
542 print(report())
546 print(report())
543 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
547 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
544 print()
548 print()
545 print('Status:')
549 print('Status:')
546 if not failed:
550 if not failed:
547 print('OK')
551 print('OK')
548 else:
552 else:
549 # If anything went wrong, point out what command to rerun manually to
553 # If anything went wrong, point out what command to rerun manually to
550 # see the actual errors and individual summary
554 # see the actual errors and individual summary
551 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
555 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
552 for name, failed_runner in failed:
556 for name, failed_runner in failed:
553 print('-'*40)
557 print('-'*40)
554 print('Runner failed:',name)
558 print('Runner failed:',name)
555 print('You may wish to rerun this one individually, with:')
559 print('You may wish to rerun this one individually, with:')
556 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
560 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
557 print(u' '.join(failed_call_args))
561 print(u' '.join(failed_call_args))
558 print()
562 print()
559 # Ensure that our exit code indicates failure
563 # Ensure that our exit code indicates failure
560 sys.exit(1)
564 sys.exit(1)
561
565
562
566
563 def main():
567 def main():
564 for arg in sys.argv[1:]:
568 for arg in sys.argv[1:]:
565 if arg.startswith('IPython'):
569 if arg.startswith('IPython'):
566 # This is in-process
570 # This is in-process
567 run_iptest()
571 run_iptest()
568 else:
572 else:
569 if "--all" in sys.argv:
573 if "--all" in sys.argv:
570 sys.argv.remove("--all")
574 sys.argv.remove("--all")
571 inc_slow = True
575 inc_slow = True
572 else:
576 else:
573 inc_slow = False
577 inc_slow = False
574 # This starts subprocesses
578 # This starts subprocesses
575 run_iptestall(inc_slow=inc_slow)
579 run_iptestall(inc_slow=inc_slow)
576
580
577
581
578 if __name__ == '__main__':
582 if __name__ == '__main__':
579 main()
583 main()
General Comments 0
You need to be logged in to leave comments. Login now