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