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