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