##// END OF EJS Templates
Minor cleanup in iptest.py and growl.py.
Brian Granger -
Show More
@@ -1,471 +1,467 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) or trial recursively. This
8 calling this script (with different arguments) or trial 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 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2009 The IPython Development Team
20 # Copyright (C) 2009 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # Stdlib
30 # Stdlib
31 import os
31 import os
32 import os.path as path
32 import os.path as path
33 import signal
33 import signal
34 import sys
34 import sys
35 import subprocess
35 import subprocess
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import warnings
38 import warnings
39
39
40
41 # Ugly, but necessary hack to ensure the test suite finds our version of
42 # IPython and not a possibly different one that may exist system-wide.
43 # Note that this must be done here, so the imports that come next work
44 # correctly even if IPython isn't installed yet.
45 p = os.path
46 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
47 sys.path.insert(0, ippath)
48
49 # Note: monkeypatch!
40 # Note: monkeypatch!
50 # We need to monkeypatch a small problem in nose itself first, before importing
41 # We need to monkeypatch a small problem in nose itself first, before importing
51 # it for actual use. This should get into nose upstream, but its release cycle
42 # it for actual use. This should get into nose upstream, but its release cycle
52 # is slow and we need it for our parametric tests to work correctly.
43 # is slow and we need it for our parametric tests to work correctly.
53 from IPython.testing import nosepatch
44 from IPython.testing import nosepatch
54 # Now, proceed to import nose itself
45 # Now, proceed to import nose itself
55 import nose.plugins.builtin
46 import nose.plugins.builtin
56 from nose.core import TestProgram
47 from nose.core import TestProgram
57
48
58 # Our own imports
49 # Our own imports
59 from IPython.utils.path import get_ipython_module_path
50 from IPython.utils.path import get_ipython_module_path
60 from IPython.utils.process import find_cmd, pycmd2argv
51 from IPython.utils.process import find_cmd, pycmd2argv
61 from IPython.utils.sysinfo import sys_info
52 from IPython.utils.sysinfo import sys_info
62
53
63 from IPython.testing import globalipapp
54 from IPython.testing import globalipapp
64 from IPython.testing.plugin.ipdoctest import IPythonDoctest
55 from IPython.testing.plugin.ipdoctest import IPythonDoctest
65
56
66 pjoin = path.join
57 pjoin = path.join
67
58
68
59
69 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
70 # Globals
61 # Globals
71 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
72
63
73
64
74 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
75 # Warnings control
66 # Warnings control
76 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
77 # 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
78 # that imports 'sets' as of today
70 # that imports 'sets' as of today
79 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
80 DeprecationWarning )
72 DeprecationWarning )
81
73
82 # This one also comes from Twisted
74 # This one also comes from Twisted
83 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
84 DeprecationWarning)
76 DeprecationWarning)
85
77
86 # Wx on Fedora11 spits these out
78 # Wx on Fedora11 spits these out
87 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
88 UserWarning)
80 UserWarning)
89
81
90 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
91 # Logic for skipping doctests
83 # Logic for skipping doctests
92 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
93
85
94 def test_for(mod):
86 def test_for(mod):
95 """Test to see if mod is importable."""
87 """Test to see if mod is importable."""
96 try:
88 try:
97 __import__(mod)
89 __import__(mod)
98 except (ImportError, RuntimeError):
90 except (ImportError, RuntimeError):
99 # GTK reports Runtime error if it can't be initialized even if it's
91 # GTK reports Runtime error if it can't be initialized even if it's
100 # importable.
92 # importable.
101 return False
93 return False
102 else:
94 else:
103 return True
95 return True
104
96
105 # Global dict where we can store information on what we have and what we don't
97 # Global dict where we can store information on what we have and what we don't
106 # have available at test run time
98 # have available at test run time
107 have = {}
99 have = {}
108
100
109 have['curses'] = test_for('_curses')
101 have['curses'] = test_for('_curses')
110 have['wx'] = test_for('wx')
102 have['wx'] = test_for('wx')
111 have['wx.aui'] = test_for('wx.aui')
103 have['wx.aui'] = test_for('wx.aui')
112 have['zope.interface'] = test_for('zope.interface')
104 have['zope.interface'] = test_for('zope.interface')
113 have['twisted'] = test_for('twisted')
105 have['twisted'] = test_for('twisted')
114 have['foolscap'] = test_for('foolscap')
106 have['foolscap'] = test_for('foolscap')
115 have['objc'] = test_for('objc')
107 have['objc'] = test_for('objc')
116 have['pexpect'] = test_for('pexpect')
108 have['pexpect'] = test_for('pexpect')
117 have['gtk'] = test_for('gtk')
109 have['gtk'] = test_for('gtk')
118 have['gobject'] = test_for('gobject')
110 have['gobject'] = test_for('gobject')
119
111
120 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
121 # Functions and classes
113 # Functions and classes
122 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
123
115
124 def report():
116 def report():
125 """Return a string with a summary report of test-related variables."""
117 """Return a string with a summary report of test-related variables."""
126
118
127 out = [ sys_info() ]
119 out = [ sys_info() ]
128
120
129 avail = []
121 avail = []
130 not_avail = []
122 not_avail = []
131
123
132 for k, is_avail in have.items():
124 for k, is_avail in have.items():
133 if is_avail:
125 if is_avail:
134 avail.append(k)
126 avail.append(k)
135 else:
127 else:
136 not_avail.append(k)
128 not_avail.append(k)
137
129
138 if avail:
130 if avail:
139 out.append('\nTools and libraries available at test time:\n')
131 out.append('\nTools and libraries available at test time:\n')
140 avail.sort()
132 avail.sort()
141 out.append(' ' + ' '.join(avail)+'\n')
133 out.append(' ' + ' '.join(avail)+'\n')
142
134
143 if not_avail:
135 if not_avail:
144 out.append('\nTools and libraries NOT available at test time:\n')
136 out.append('\nTools and libraries NOT available at test time:\n')
145 not_avail.sort()
137 not_avail.sort()
146 out.append(' ' + ' '.join(not_avail)+'\n')
138 out.append(' ' + ' '.join(not_avail)+'\n')
147
139
148 return ''.join(out)
140 return ''.join(out)
149
141
150
142
151 def make_exclude():
143 def make_exclude():
152 """Make patterns of modules and packages to exclude from testing.
144 """Make patterns of modules and packages to exclude from testing.
153
145
154 For the IPythonDoctest plugin, we need to exclude certain patterns that
146 For the IPythonDoctest plugin, we need to exclude certain patterns that
155 cause testing problems. We should strive to minimize the number of
147 cause testing problems. We should strive to minimize the number of
156 skipped modules, since this means untested code. As the testing
148 skipped modules, since this means untested code.
157 machinery solidifies, this list should eventually become empty.
149
158 These modules and packages will NOT get scanned by nose at all for tests.
150 These modules and packages will NOT get scanned by nose at all for tests.
159 """
151 """
160 # Simple utility to make IPython paths more readably, we need a lot of
152 # Simple utility to make IPython paths more readably, we need a lot of
161 # these below
153 # these below
162 ipjoin = lambda *paths: pjoin('IPython', *paths)
154 ipjoin = lambda *paths: pjoin('IPython', *paths)
163
155
164 exclusions = [ipjoin('external'),
156 exclusions = [ipjoin('external'),
165 ipjoin('frontend', 'process', 'winprocess.py'),
157 ipjoin('frontend', 'process', 'winprocess.py'),
166 # Deprecated old Shell and iplib modules, skip to avoid
158 # Deprecated old Shell and iplib modules, skip to avoid
167 # warnings
159 # warnings
168 ipjoin('Shell'),
160 ipjoin('Shell'),
169 ipjoin('iplib'),
161 ipjoin('iplib'),
170 pjoin('IPython_doctest_plugin'),
162 pjoin('IPython_doctest_plugin'),
171 ipjoin('quarantine'),
163 ipjoin('quarantine'),
172 ipjoin('deathrow'),
164 ipjoin('deathrow'),
173 ipjoin('testing', 'attic'),
165 ipjoin('testing', 'attic'),
174 # This guy is probably attic material
166 # This guy is probably attic material
175 ipjoin('testing', 'mkdoctests'),
167 ipjoin('testing', 'mkdoctests'),
176 # Testing inputhook will need a lot of thought, to figure out
168 # Testing inputhook will need a lot of thought, to figure out
177 # how to have tests that don't lock up with the gui event
169 # how to have tests that don't lock up with the gui event
178 # loops in the picture
170 # loops in the picture
179 ipjoin('lib', 'inputhook'),
171 ipjoin('lib', 'inputhook'),
180 # Config files aren't really importable stand-alone
172 # Config files aren't really importable stand-alone
181 ipjoin('config', 'default'),
173 ipjoin('config', 'default'),
182 ipjoin('config', 'profile'),
174 ipjoin('config', 'profile'),
183 ]
175 ]
184
176
185 if not have['wx']:
177 if not have['wx']:
186 exclusions.append(ipjoin('gui'))
178 exclusions.append(ipjoin('gui'))
187 exclusions.append(ipjoin('frontend', 'wx'))
179 exclusions.append(ipjoin('frontend', 'wx'))
188 exclusions.append(ipjoin('lib', 'inputhookwx'))
180 exclusions.append(ipjoin('lib', 'inputhookwx'))
189
181
190 if not have['gtk'] or not have['gobject']:
182 if not have['gtk'] or not have['gobject']:
191 exclusions.append(ipjoin('lib', 'inputhookgtk'))
183 exclusions.append(ipjoin('lib', 'inputhookgtk'))
192
184
193 if not have['wx.aui']:
185 if not have['wx.aui']:
194 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
186 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
195
187
196 if not have['objc']:
188 if not have['objc']:
197 exclusions.append(ipjoin('frontend', 'cocoa'))
189 exclusions.append(ipjoin('frontend', 'cocoa'))
198
190
199 # These have to be skipped on win32 because the use echo, rm, cd, etc.
191 # These have to be skipped on win32 because the use echo, rm, cd, etc.
200 # See ticket https://bugs.launchpad.net/bugs/366982
192 # See ticket https://bugs.launchpad.net/bugs/366982
201 if sys.platform == 'win32':
193 if sys.platform == 'win32':
202 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
194 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
203 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
195 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
204
196
205 if not have['pexpect']:
197 if not have['pexpect']:
206 exclusions.extend([ipjoin('scripts', 'irunner'),
198 exclusions.extend([ipjoin('scripts', 'irunner'),
207 ipjoin('lib', 'irunner')])
199 ipjoin('lib', 'irunner')])
208
200
209 # This is scary. We still have things in frontend and testing that
201 # This is scary. We still have things in frontend and testing that
210 # are being tested by nose that use twisted. We need to rethink
202 # are being tested by nose that use twisted. We need to rethink
211 # how we are isolating dependencies in testing.
203 # how we are isolating dependencies in testing.
212 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
204 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
213 exclusions.extend(
205 exclusions.extend(
214 [ipjoin('frontend', 'asyncfrontendbase'),
206 [ipjoin('frontend', 'asyncfrontendbase'),
215 ipjoin('frontend', 'prefilterfrontend'),
207 ipjoin('frontend', 'prefilterfrontend'),
216 ipjoin('frontend', 'frontendbase'),
208 ipjoin('frontend', 'frontendbase'),
217 ipjoin('frontend', 'linefrontendbase'),
209 ipjoin('frontend', 'linefrontendbase'),
218 ipjoin('frontend', 'tests', 'test_linefrontend'),
210 ipjoin('frontend', 'tests', 'test_linefrontend'),
219 ipjoin('frontend', 'tests', 'test_frontendbase'),
211 ipjoin('frontend', 'tests', 'test_frontendbase'),
220 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
212 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
221 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
213 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
222 ipjoin('testing', 'parametric'),
214 ipjoin('testing', 'parametric'),
223 ipjoin('testing', 'util'),
215 ipjoin('testing', 'util'),
224 ipjoin('testing', 'tests', 'test_decorators_trial'),
216 ipjoin('testing', 'tests', 'test_decorators_trial'),
225 ] )
217 ] )
226
218
227 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
219 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
228 if sys.platform == 'win32':
220 if sys.platform == 'win32':
229 exclusions = [s.replace('\\','\\\\') for s in exclusions]
221 exclusions = [s.replace('\\','\\\\') for s in exclusions]
230
222
231 return exclusions
223 return exclusions
232
224
233
225
234 class IPTester(object):
226 class IPTester(object):
235 """Call that calls iptest or trial in a subprocess.
227 """Call that calls iptest or trial in a subprocess.
236 """
228 """
237 #: string, name of test runner that will be called
229 #: string, name of test runner that will be called
238 runner = None
230 runner = None
239 #: list, parameters for test runner
231 #: list, parameters for test runner
240 params = None
232 params = None
241 #: list, arguments of system call to be made to call test runner
233 #: list, arguments of system call to be made to call test runner
242 call_args = None
234 call_args = None
243 #: list, process ids of subprocesses we start (for cleanup)
235 #: list, process ids of subprocesses we start (for cleanup)
244 pids = None
236 pids = None
245
237
246 def __init__(self, runner='iptest', params=None):
238 def __init__(self, runner='iptest', params=None):
247 """Create new test runner."""
239 """Create new test runner."""
248 p = os.path
240 p = os.path
249 if runner == 'iptest':
241 if runner == 'iptest':
250 iptest_app = get_ipython_module_path('IPython.testing.iptest')
242 iptest_app = get_ipython_module_path('IPython.testing.iptest')
251 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
243 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
252 else:
244 elif runner == 'trial':
253 # For trial, it needs to be installed system-wide
245 # For trial, it needs to be installed system-wide
254 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
246 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
247 else:
248 raise Exception('Not a valid test runner: %s' % repr(runner))
255 if params is None:
249 if params is None:
256 params = []
250 params = []
257 if isinstance(params, str):
251 if isinstance(params, str):
258 params = [params]
252 params = [params]
259 self.params = params
253 self.params = params
260
254
261 # Assemble call
255 # Assemble call
262 self.call_args = self.runner+self.params
256 self.call_args = self.runner+self.params
263
257
264 # Store pids of anything we start to clean up on deletion, if possible
258 # Store pids of anything we start to clean up on deletion, if possible
265 # (on posix only, since win32 has no os.kill)
259 # (on posix only, since win32 has no os.kill)
266 self.pids = []
260 self.pids = []
267
261
268 if sys.platform == 'win32':
262 if sys.platform == 'win32':
269 def _run_cmd(self):
263 def _run_cmd(self):
270 # On Windows, use os.system instead of subprocess.call, because I
264 # On Windows, use os.system instead of subprocess.call, because I
271 # was having problems with subprocess and I just don't know enough
265 # was having problems with subprocess and I just don't know enough
272 # about win32 to debug this reliably. Os.system may be the 'old
266 # about win32 to debug this reliably. Os.system may be the 'old
273 # fashioned' way to do it, but it works just fine. If someone
267 # fashioned' way to do it, but it works just fine. If someone
274 # later can clean this up that's fine, as long as the tests run
268 # later can clean this up that's fine, as long as the tests run
275 # reliably in win32.
269 # reliably in win32.
270 # What types of problems are you having. They may be related to
271 # running Python in unboffered mode. BG.
276 return os.system(' '.join(self.call_args))
272 return os.system(' '.join(self.call_args))
277 else:
273 else:
278 def _run_cmd(self):
274 def _run_cmd(self):
279 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
275 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
280 subp = subprocess.Popen(self.call_args)
276 subp = subprocess.Popen(self.call_args)
281 self.pids.append(subp.pid)
277 self.pids.append(subp.pid)
282 # If this fails, the pid will be left in self.pids and cleaned up
278 # If this fails, the pid will be left in self.pids and cleaned up
283 # later, but if the wait call succeeds, then we can clear the
279 # later, but if the wait call succeeds, then we can clear the
284 # stored pid.
280 # stored pid.
285 retcode = subp.wait()
281 retcode = subp.wait()
286 self.pids.pop()
282 self.pids.pop()
287 return retcode
283 return retcode
288
284
289 def run(self):
285 def run(self):
290 """Run the stored commands"""
286 """Run the stored commands"""
291 try:
287 try:
292 return self._run_cmd()
288 return self._run_cmd()
293 except:
289 except:
294 import traceback
290 import traceback
295 traceback.print_exc()
291 traceback.print_exc()
296 return 1 # signal failure
292 return 1 # signal failure
297
293
298 def __del__(self):
294 def __del__(self):
299 """Cleanup on exit by killing any leftover processes."""
295 """Cleanup on exit by killing any leftover processes."""
300
296
301 if not hasattr(os, 'kill'):
297 if not hasattr(os, 'kill'):
302 return
298 return
303
299
304 for pid in self.pids:
300 for pid in self.pids:
305 try:
301 try:
306 print 'Cleaning stale PID:', pid
302 print 'Cleaning stale PID:', pid
307 os.kill(pid, signal.SIGKILL)
303 os.kill(pid, signal.SIGKILL)
308 except OSError:
304 except OSError:
309 # This is just a best effort, if we fail or the process was
305 # This is just a best effort, if we fail or the process was
310 # really gone, ignore it.
306 # really gone, ignore it.
311 pass
307 pass
312
308
313
309
314 def make_runners():
310 def make_runners():
315 """Define the top-level packages that need to be tested.
311 """Define the top-level packages that need to be tested.
316 """
312 """
317
313
318 # Packages to be tested via nose, that only depend on the stdlib
314 # Packages to be tested via nose, that only depend on the stdlib
319 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
315 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
320 'scripts', 'testing', 'utils' ]
316 'scripts', 'testing', 'utils' ]
321 # The machinery in kernel needs twisted for real testing
317 # The machinery in kernel needs twisted for real testing
322 trial_pkg_names = []
318 trial_pkg_names = []
323
319
324 if have['wx']:
320 if have['wx']:
325 nose_pkg_names.append('gui')
321 nose_pkg_names.append('gui')
326
322
327 # And add twisted ones if conditions are met
323 # And add twisted ones if conditions are met
328 if have['zope.interface'] and have['twisted'] and have['foolscap']:
324 if have['zope.interface'] and have['twisted'] and have['foolscap']:
329 # We only list IPython.kernel for testing using twisted.trial as
325 # We only list IPython.kernel for testing using twisted.trial as
330 # nose and twisted.trial have conflicts that make the testing system
326 # nose and twisted.trial have conflicts that make the testing system
331 # unstable.
327 # unstable.
332 trial_pkg_names.append('kernel')
328 trial_pkg_names.append('kernel')
333
329
334 # For debugging this code, only load quick stuff
330 # For debugging this code, only load quick stuff
335 #nose_pkg_names = ['core', 'extensions'] # dbg
331 #nose_pkg_names = ['core', 'extensions'] # dbg
336 #trial_pkg_names = [] # dbg
332 #trial_pkg_names = [] # dbg
337
333
338 # Make fully qualified package names prepending 'IPython.' to our name lists
334 # Make fully qualified package names prepending 'IPython.' to our name lists
339 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
335 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
340 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
336 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
341
337
342 # Make runners
338 # Make runners
343 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
339 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
344 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
340 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
345
341
346 return runners
342 return runners
347
343
348
344
349 def run_iptest():
345 def run_iptest():
350 """Run the IPython test suite using nose.
346 """Run the IPython test suite using nose.
351
347
352 This function is called when this script is **not** called with the form
348 This function is called when this script is **not** called with the form
353 `iptest all`. It simply calls nose with appropriate command line flags
349 `iptest all`. It simply calls nose with appropriate command line flags
354 and accepts all of the standard nose arguments.
350 and accepts all of the standard nose arguments.
355 """
351 """
356
352
357 warnings.filterwarnings('ignore',
353 warnings.filterwarnings('ignore',
358 'This will be removed soon. Use IPython.testing.util instead')
354 'This will be removed soon. Use IPython.testing.util instead')
359
355
360 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
356 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
361
357
362 # Loading ipdoctest causes problems with Twisted, but
358 # Loading ipdoctest causes problems with Twisted, but
363 # our test suite runner now separates things and runs
359 # our test suite runner now separates things and runs
364 # all Twisted tests with trial.
360 # all Twisted tests with trial.
365 '--with-ipdoctest',
361 '--with-ipdoctest',
366 '--ipdoctest-tests','--ipdoctest-extension=txt',
362 '--ipdoctest-tests','--ipdoctest-extension=txt',
367
363
368 # We add --exe because of setuptools' imbecility (it
364 # We add --exe because of setuptools' imbecility (it
369 # blindly does chmod +x on ALL files). Nose does the
365 # blindly does chmod +x on ALL files). Nose does the
370 # right thing and it tries to avoid executables,
366 # right thing and it tries to avoid executables,
371 # setuptools unfortunately forces our hand here. This
367 # setuptools unfortunately forces our hand here. This
372 # has been discussed on the distutils list and the
368 # has been discussed on the distutils list and the
373 # setuptools devs refuse to fix this problem!
369 # setuptools devs refuse to fix this problem!
374 '--exe',
370 '--exe',
375 ]
371 ]
376
372
377 if nose.__version__ >= '0.11':
373 if nose.__version__ >= '0.11':
378 # I don't fully understand why we need this one, but depending on what
374 # I don't fully understand why we need this one, but depending on what
379 # directory the test suite is run from, if we don't give it, 0 tests
375 # directory the test suite is run from, if we don't give it, 0 tests
380 # get run. Specifically, if the test suite is run from the source dir
376 # get run. Specifically, if the test suite is run from the source dir
381 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
377 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
382 # even if the same call done in this directory works fine). It appears
378 # even if the same call done in this directory works fine). It appears
383 # that if the requested package is in the current dir, nose bails early
379 # that if the requested package is in the current dir, nose bails early
384 # by default. Since it's otherwise harmless, leave it in by default
380 # by default. Since it's otherwise harmless, leave it in by default
385 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
381 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
386 argv.append('--traverse-namespace')
382 argv.append('--traverse-namespace')
387
383
388 # Construct list of plugins, omitting the existing doctest plugin, which
384 # Construct list of plugins, omitting the existing doctest plugin, which
389 # ours replaces (and extends).
385 # ours replaces (and extends).
390 plugins = [IPythonDoctest(make_exclude())]
386 plugins = [IPythonDoctest(make_exclude())]
391 for p in nose.plugins.builtin.plugins:
387 for p in nose.plugins.builtin.plugins:
392 plug = p()
388 plug = p()
393 if plug.name == 'doctest':
389 if plug.name == 'doctest':
394 continue
390 continue
395 plugins.append(plug)
391 plugins.append(plug)
396
392
397 # We need a global ipython running in this process
393 # We need a global ipython running in this process
398 globalipapp.start_ipython()
394 globalipapp.start_ipython()
399 # Now nose can run
395 # Now nose can run
400 TestProgram(argv=argv, plugins=plugins)
396 TestProgram(argv=argv, plugins=plugins)
401
397
402
398
403 def run_iptestall():
399 def run_iptestall():
404 """Run the entire IPython test suite by calling nose and trial.
400 """Run the entire IPython test suite by calling nose and trial.
405
401
406 This function constructs :class:`IPTester` instances for all IPython
402 This function constructs :class:`IPTester` instances for all IPython
407 modules and package and then runs each of them. This causes the modules
403 modules and package and then runs each of them. This causes the modules
408 and packages of IPython to be tested each in their own subprocess using
404 and packages of IPython to be tested each in their own subprocess using
409 nose or twisted.trial appropriately.
405 nose or twisted.trial appropriately.
410 """
406 """
411
407
412 runners = make_runners()
408 runners = make_runners()
413
409
414 # Run the test runners in a temporary dir so we can nuke it when finished
410 # Run the test runners in a temporary dir so we can nuke it when finished
415 # to clean up any junk files left over by accident. This also makes it
411 # to clean up any junk files left over by accident. This also makes it
416 # robust against being run in non-writeable directories by mistake, as the
412 # robust against being run in non-writeable directories by mistake, as the
417 # temp dir will always be user-writeable.
413 # temp dir will always be user-writeable.
418 curdir = os.getcwd()
414 curdir = os.getcwd()
419 testdir = tempfile.gettempdir()
415 testdir = tempfile.gettempdir()
420 os.chdir(testdir)
416 os.chdir(testdir)
421
417
422 # Run all test runners, tracking execution time
418 # Run all test runners, tracking execution time
423 failed = []
419 failed = []
424 t_start = time.time()
420 t_start = time.time()
425 try:
421 try:
426 for (name, runner) in runners:
422 for (name, runner) in runners:
427 print '*'*70
423 print '*'*70
428 print 'IPython test group:',name
424 print 'IPython test group:',name
429 res = runner.run()
425 res = runner.run()
430 if res:
426 if res:
431 failed.append( (name, runner) )
427 failed.append( (name, runner) )
432 finally:
428 finally:
433 os.chdir(curdir)
429 os.chdir(curdir)
434 t_end = time.time()
430 t_end = time.time()
435 t_tests = t_end - t_start
431 t_tests = t_end - t_start
436 nrunners = len(runners)
432 nrunners = len(runners)
437 nfail = len(failed)
433 nfail = len(failed)
438 # summarize results
434 # summarize results
439 print
435 print
440 print '*'*70
436 print '*'*70
441 print 'Test suite completed for system with the following information:'
437 print 'Test suite completed for system with the following information:'
442 print report()
438 print report()
443 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
439 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
444 print
440 print
445 print 'Status:'
441 print 'Status:'
446 if not failed:
442 if not failed:
447 print 'OK'
443 print 'OK'
448 else:
444 else:
449 # If anything went wrong, point out what command to rerun manually to
445 # If anything went wrong, point out what command to rerun manually to
450 # see the actual errors and individual summary
446 # see the actual errors and individual summary
451 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
447 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
452 for name, failed_runner in failed:
448 for name, failed_runner in failed:
453 print '-'*40
449 print '-'*40
454 print 'Runner failed:',name
450 print 'Runner failed:',name
455 print 'You may wish to rerun this one individually, with:'
451 print 'You may wish to rerun this one individually, with:'
456 print ' '.join(failed_runner.call_args)
452 print ' '.join(failed_runner.call_args)
457 print
453 print
458
454
459
455
460 def main():
456 def main():
461 for arg in sys.argv[1:]:
457 for arg in sys.argv[1:]:
462 if arg.startswith('IPython'):
458 if arg.startswith('IPython'):
463 # This is in-process
459 # This is in-process
464 run_iptest()
460 run_iptest()
465 else:
461 else:
466 # This starts subprocesses
462 # This starts subprocesses
467 run_iptestall()
463 run_iptestall()
468
464
469
465
470 if __name__ == '__main__':
466 if __name__ == '__main__':
471 main()
467 main()
@@ -1,49 +1,65 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
1 # encoding: utf-8
2 """
3 Utilities using Growl on OS X for notifications.
4 """
3
5
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16
17 #-----------------------------------------------------------------------------
18 # Code
19 #-----------------------------------------------------------------------------
4
20
5 class IPythonGrowlError(Exception):
21 class IPythonGrowlError(Exception):
6 pass
22 pass
7
23
8 class Notifier(object):
24 class Notifier(object):
9
25
10 def __init__(self, app_name):
26 def __init__(self, app_name):
11 try:
27 try:
12 import Growl
28 import Growl
13 except ImportError:
29 except ImportError:
14 self.g_notifier = None
30 self.g_notifier = None
15 else:
31 else:
16 self.g_notifier = Growl.GrowlNotifier(app_name, ['kernel', 'core'])
32 self.g_notifier = Growl.GrowlNotifier(app_name, ['kernel', 'core'])
17 self.g_notifier.register()
33 self.g_notifier.register()
18
34
19 def _notify(self, title, msg):
35 def _notify(self, title, msg):
20 if self.g_notifier is not None:
36 if self.g_notifier is not None:
21 self.g_notifier.notify('core', title, msg)
37 self.g_notifier.notify('core', title, msg)
22
38
23 def notify(self, title, msg):
39 def notify(self, title, msg):
24 self._notify(title, msg)
40 self._notify(title, msg)
25
41
26 def notify_deferred(self, r, msg):
42 def notify_deferred(self, r, msg):
27 title = "Deferred Result"
43 title = "Deferred Result"
28 msg = msg + '\n' + repr(r)
44 msg = msg + '\n' + repr(r)
29 self._notify(title, msg)
45 self._notify(title, msg)
30 return r
46 return r
31
47
32 _notifier = None
48 _notifier = None
33
49
34 def notify(title, msg):
50 def notify(title, msg):
35 pass
51 pass
36
52
37 def notify_deferred(r, msg):
53 def notify_deferred(r, msg):
38 return r
54 return r
39
55
40 def start(app_name):
56 def start(app_name):
41 global _notifier, notify, notify_deferred
57 global _notifier, notify, notify_deferred
42 if _notifier is not None:
58 if _notifier is not None:
43 raise IPythonGrowlError("this process is already registered with Growl")
59 raise IPythonGrowlError("this process is already registered with Growl")
44 else:
60 else:
45 _notifier = Notifier(app_name)
61 _notifier = Notifier(app_name)
46 notify = _notifier.notify
62 notify = _notifier.notify
47 notify_deferred = _notifier.notify_deferred
63 notify_deferred = _notifier.notify_deferred
48
64
49
65
General Comments 0
You need to be logged in to leave comments. Login now