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