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