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