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