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