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