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