##// END OF EJS Templates
exclude fabfile from tests...
MinRK -
Show More
@@ -1,602 +1,603 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['jinja2'] = test_for('jinja2')
166 166 have['wx'] = test_for('wx')
167 167 have['wx.aui'] = test_for('wx.aui')
168 168 have['azure'] = test_for('azure')
169 169
170 170 min_zmq = (2,1,11)
171 171
172 172 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
173 173
174 174 #-----------------------------------------------------------------------------
175 175 # Functions and classes
176 176 #-----------------------------------------------------------------------------
177 177
178 178 def report():
179 179 """Return a string with a summary report of test-related variables."""
180 180
181 181 out = [ sys_info(), '\n']
182 182
183 183 avail = []
184 184 not_avail = []
185 185
186 186 for k, is_avail in have.items():
187 187 if is_avail:
188 188 avail.append(k)
189 189 else:
190 190 not_avail.append(k)
191 191
192 192 if avail:
193 193 out.append('\nTools and libraries available at test time:\n')
194 194 avail.sort()
195 195 out.append(' ' + ' '.join(avail)+'\n')
196 196
197 197 if not_avail:
198 198 out.append('\nTools and libraries NOT available at test time:\n')
199 199 not_avail.sort()
200 200 out.append(' ' + ' '.join(not_avail)+'\n')
201 201
202 202 return ''.join(out)
203 203
204 204
205 205 def make_exclude():
206 206 """Make patterns of modules and packages to exclude from testing.
207 207
208 208 For the IPythonDoctest plugin, we need to exclude certain patterns that
209 209 cause testing problems. We should strive to minimize the number of
210 210 skipped modules, since this means untested code.
211 211
212 212 These modules and packages will NOT get scanned by nose at all for tests.
213 213 """
214 214 # Simple utility to make IPython paths more readably, we need a lot of
215 215 # these below
216 216 ipjoin = lambda *paths: pjoin('IPython', *paths)
217 217
218 218 exclusions = [ipjoin('external'),
219 219 ipjoin('quarantine'),
220 220 ipjoin('deathrow'),
221 221 # This guy is probably attic material
222 222 ipjoin('testing', 'mkdoctests'),
223 223 # Testing inputhook will need a lot of thought, to figure out
224 224 # how to have tests that don't lock up with the gui event
225 225 # loops in the picture
226 226 ipjoin('lib', 'inputhook'),
227 227 # Config files aren't really importable stand-alone
228 228 ipjoin('config', 'profile'),
229 229 # The notebook 'static' directory contains JS, css and other
230 230 # files for web serving. Occasionally projects may put a .py
231 231 # file in there (MathJax ships a conf.py), so we might as
232 232 # well play it safe and skip the whole thing.
233 ipjoin('frontend', 'html', 'notebook', 'static')
233 ipjoin('frontend', 'html', 'notebook', 'static'),
234 ipjoin('frontend', 'html', 'notebook', 'fabfile'),
234 235 ]
235 236 if not have['sqlite3']:
236 237 exclusions.append(ipjoin('core', 'tests', 'test_history'))
237 238 exclusions.append(ipjoin('core', 'history'))
238 239 if not have['wx']:
239 240 exclusions.append(ipjoin('lib', 'inputhookwx'))
240 241
241 242 if 'IPython.kernel.inprocess' not in sys.argv:
242 243 exclusions.append(ipjoin('kernel', 'inprocess'))
243 244
244 245 # FIXME: temporarily disable autoreload tests, as they can produce
245 246 # spurious failures in subsequent tests (cythonmagic).
246 247 exclusions.append(ipjoin('extensions', 'autoreload'))
247 248 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
248 249
249 250 # We do this unconditionally, so that the test suite doesn't import
250 251 # gtk, changing the default encoding and masking some unicode bugs.
251 252 exclusions.append(ipjoin('lib', 'inputhookgtk'))
252 253 exclusions.append(ipjoin('kernel', 'zmq', 'gui', 'gtkembed'))
253 254
254 255 # These have to be skipped on win32 because the use echo, rm, cd, etc.
255 256 # See ticket https://github.com/ipython/ipython/issues/87
256 257 if sys.platform == 'win32':
257 258 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
258 259 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
259 260
260 261 if not have['pexpect']:
261 262 exclusions.extend([ipjoin('lib', 'irunner'),
262 263 ipjoin('lib', 'tests', 'test_irunner'),
263 264 ipjoin('frontend', 'terminal', 'console'),
264 265 ])
265 266
266 267 if not have['zmq']:
267 268 exclusions.append(ipjoin('kernel'))
268 269 exclusions.append(ipjoin('frontend', 'qt'))
269 270 exclusions.append(ipjoin('frontend', 'html'))
270 271 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
271 272 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
272 273 exclusions.append(ipjoin('parallel'))
273 274 elif not have['qt'] or not have['pygments']:
274 275 exclusions.append(ipjoin('frontend', 'qt'))
275 276
276 277 if not have['pymongo']:
277 278 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
278 279 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
279 280
280 281 if not have['matplotlib']:
281 282 exclusions.extend([ipjoin('core', 'pylabtools'),
282 283 ipjoin('core', 'tests', 'test_pylabtools'),
283 284 ipjoin('kernel', 'zmq', 'pylab'),
284 285 ])
285 286
286 287 if not have['cython']:
287 288 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
288 289 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
289 290
290 291 if not have['oct2py']:
291 292 exclusions.extend([ipjoin('extensions', 'octavemagic')])
292 293 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
293 294
294 295 if not have['tornado']:
295 296 exclusions.append(ipjoin('frontend', 'html'))
296 297
297 298 if not have['jinja2']:
298 299 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'notebookapp'))
299 300
300 301 if not have['rpy2'] or not have['numpy']:
301 302 exclusions.append(ipjoin('extensions', 'rmagic'))
302 303 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
303 304
304 305 if not have['azure']:
305 306 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'azurenbmanager'))
306 307
307 308 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
308 309 if sys.platform == 'win32':
309 310 exclusions = [s.replace('\\','\\\\') for s in exclusions]
310 311
311 312 # check for any exclusions that don't seem to exist:
312 313 parent, _ = os.path.split(get_ipython_package_dir())
313 314 for exclusion in exclusions:
314 315 if exclusion.endswith(('deathrow', 'quarantine')):
315 316 # ignore deathrow/quarantine, which exist in dev, but not install
316 317 continue
317 318 fullpath = pjoin(parent, exclusion)
318 319 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
319 320 warn("Excluding nonexistent file: %r" % exclusion)
320 321
321 322 return exclusions
322 323
323 324
324 325 class IPTester(object):
325 326 """Call that calls iptest or trial in a subprocess.
326 327 """
327 328 #: string, name of test runner that will be called
328 329 runner = None
329 330 #: list, parameters for test runner
330 331 params = None
331 332 #: list, arguments of system call to be made to call test runner
332 333 call_args = None
333 334 #: list, subprocesses we start (for cleanup)
334 335 processes = None
335 336 #: str, coverage xml output file
336 337 coverage_xml = None
337 338
338 339 def __init__(self, runner='iptest', params=None):
339 340 """Create new test runner."""
340 341 p = os.path
341 342 if runner == 'iptest':
342 343 iptest_app = get_ipython_module_path('IPython.testing.iptest')
343 344 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
344 345 else:
345 346 raise Exception('Not a valid test runner: %s' % repr(runner))
346 347 if params is None:
347 348 params = []
348 349 if isinstance(params, str):
349 350 params = [params]
350 351 self.params = params
351 352
352 353 # Assemble call
353 354 self.call_args = self.runner+self.params
354 355
355 356 # Find the section we're testing (IPython.foo)
356 357 for sect in self.params:
357 358 if sect.startswith('IPython'): break
358 359 else:
359 360 raise ValueError("Section not found", self.params)
360 361
361 362 if '--with-xunit' in self.call_args:
362 363
363 364 self.call_args.append('--xunit-file')
364 365 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
365 366 xunit_file = path.abspath(sect+'.xunit.xml')
366 367 if sys.platform == 'win32':
367 368 xunit_file = '"%s"' % xunit_file
368 369 self.call_args.append(xunit_file)
369 370
370 371 if '--with-xml-coverage' in self.call_args:
371 372 self.coverage_xml = path.abspath(sect+".coverage.xml")
372 373 self.call_args.remove('--with-xml-coverage')
373 374 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
374 375
375 376 # Store anything we start to clean up on deletion
376 377 self.processes = []
377 378
378 379 def _run_cmd(self):
379 380 with TemporaryDirectory() as IPYTHONDIR:
380 381 env = os.environ.copy()
381 382 env['IPYTHONDIR'] = IPYTHONDIR
382 383 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
383 384 subp = subprocess.Popen(self.call_args, env=env)
384 385 self.processes.append(subp)
385 386 # If this fails, the process will be left in self.processes and
386 387 # cleaned up later, but if the wait call succeeds, then we can
387 388 # clear the stored process.
388 389 retcode = subp.wait()
389 390 self.processes.pop()
390 391 return retcode
391 392
392 393 def run(self):
393 394 """Run the stored commands"""
394 395 try:
395 396 retcode = self._run_cmd()
396 397 except KeyboardInterrupt:
397 398 return -signal.SIGINT
398 399 except:
399 400 import traceback
400 401 traceback.print_exc()
401 402 return 1 # signal failure
402 403
403 404 if self.coverage_xml:
404 405 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
405 406 return retcode
406 407
407 408 def __del__(self):
408 409 """Cleanup on exit by killing any leftover processes."""
409 410 for subp in self.processes:
410 411 if subp.poll() is not None:
411 412 continue # process is already dead
412 413
413 414 try:
414 415 print('Cleaning up stale PID: %d' % subp.pid)
415 416 subp.kill()
416 417 except: # (OSError, WindowsError) ?
417 418 # This is just a best effort, if we fail or the process was
418 419 # really gone, ignore it.
419 420 pass
420 421 else:
421 422 for i in range(10):
422 423 if subp.poll() is None:
423 424 time.sleep(0.1)
424 425 else:
425 426 break
426 427
427 428 if subp.poll() is None:
428 429 # The process did not die...
429 430 print('... failed. Manual cleanup may be required.')
430 431
431 432 def make_runners(inc_slow=False):
432 433 """Define the top-level packages that need to be tested.
433 434 """
434 435
435 436 # Packages to be tested via nose, that only depend on the stdlib
436 437 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
437 438 'testing', 'utils', 'nbformat' ]
438 439
439 440 if have['zmq']:
440 441 nose_pkg_names.append('kernel')
441 442 nose_pkg_names.append('kernel.inprocess')
442 443 if inc_slow:
443 444 nose_pkg_names.append('parallel')
444 445
445 446 # For debugging this code, only load quick stuff
446 447 #nose_pkg_names = ['core', 'extensions'] # dbg
447 448
448 449 # Make fully qualified package names prepending 'IPython.' to our name lists
449 450 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
450 451
451 452 # Make runners
452 453 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
453 454
454 455 return runners
455 456
456 457
457 458 def run_iptest():
458 459 """Run the IPython test suite using nose.
459 460
460 461 This function is called when this script is **not** called with the form
461 462 `iptest all`. It simply calls nose with appropriate command line flags
462 463 and accepts all of the standard nose arguments.
463 464 """
464 465 # Apply our monkeypatch to Xunit
465 466 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
466 467 monkeypatch_xunit()
467 468
468 469 warnings.filterwarnings('ignore',
469 470 'This will be removed soon. Use IPython.testing.util instead')
470 471
471 472 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
472 473
473 474 '--with-ipdoctest',
474 475 '--ipdoctest-tests','--ipdoctest-extension=txt',
475 476
476 477 # We add --exe because of setuptools' imbecility (it
477 478 # blindly does chmod +x on ALL files). Nose does the
478 479 # right thing and it tries to avoid executables,
479 480 # setuptools unfortunately forces our hand here. This
480 481 # has been discussed on the distutils list and the
481 482 # setuptools devs refuse to fix this problem!
482 483 '--exe',
483 484 ]
484 485 if '-a' not in argv and '-A' not in argv:
485 486 argv = argv + ['-a', '!crash']
486 487
487 488 if nose.__version__ >= '0.11':
488 489 # I don't fully understand why we need this one, but depending on what
489 490 # directory the test suite is run from, if we don't give it, 0 tests
490 491 # get run. Specifically, if the test suite is run from the source dir
491 492 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
492 493 # even if the same call done in this directory works fine). It appears
493 494 # that if the requested package is in the current dir, nose bails early
494 495 # by default. Since it's otherwise harmless, leave it in by default
495 496 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
496 497 argv.append('--traverse-namespace')
497 498
498 499 # use our plugin for doctesting. It will remove the standard doctest plugin
499 500 # if it finds it enabled
500 501 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
501 502
502 503 # We need a global ipython running in this process, but the special
503 504 # in-process group spawns its own IPython kernels, so for *that* group we
504 505 # must avoid also opening the global one (otherwise there's a conflict of
505 506 # singletons). Ultimately the solution to this problem is to refactor our
506 507 # assumptions about what needs to be a singleton and what doesn't (app
507 508 # objects should, individual shells shouldn't). But for now, this
508 509 # workaround allows the test suite for the inprocess module to complete.
509 510 if not 'IPython.kernel.inprocess' in sys.argv:
510 511 globalipapp.start_ipython()
511 512
512 513 # Now nose can run
513 514 TestProgram(argv=argv, addplugins=plugins)
514 515
515 516
516 517 def run_iptestall(inc_slow=False):
517 518 """Run the entire IPython test suite by calling nose and trial.
518 519
519 520 This function constructs :class:`IPTester` instances for all IPython
520 521 modules and package and then runs each of them. This causes the modules
521 522 and packages of IPython to be tested each in their own subprocess using
522 523 nose.
523 524
524 525 Parameters
525 526 ----------
526 527
527 528 inc_slow : bool, optional
528 529 Include slow tests, like IPython.parallel. By default, these tests aren't
529 530 run.
530 531 """
531 532
532 533 runners = make_runners(inc_slow=inc_slow)
533 534
534 535 # Run the test runners in a temporary dir so we can nuke it when finished
535 536 # to clean up any junk files left over by accident. This also makes it
536 537 # robust against being run in non-writeable directories by mistake, as the
537 538 # temp dir will always be user-writeable.
538 539 curdir = os.getcwdu()
539 540 testdir = tempfile.gettempdir()
540 541 os.chdir(testdir)
541 542
542 543 # Run all test runners, tracking execution time
543 544 failed = []
544 545 t_start = time.time()
545 546 try:
546 547 for (name, runner) in runners:
547 548 print('*'*70)
548 549 print('IPython test group:',name)
549 550 res = runner.run()
550 551 if res:
551 552 failed.append( (name, runner) )
552 553 if res == -signal.SIGINT:
553 554 print("Interrupted")
554 555 break
555 556 finally:
556 557 os.chdir(curdir)
557 558 t_end = time.time()
558 559 t_tests = t_end - t_start
559 560 nrunners = len(runners)
560 561 nfail = len(failed)
561 562 # summarize results
562 563 print()
563 564 print('*'*70)
564 565 print('Test suite completed for system with the following information:')
565 566 print(report())
566 567 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
567 568 print()
568 569 print('Status:')
569 570 if not failed:
570 571 print('OK')
571 572 else:
572 573 # If anything went wrong, point out what command to rerun manually to
573 574 # see the actual errors and individual summary
574 575 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
575 576 for name, failed_runner in failed:
576 577 print('-'*40)
577 578 print('Runner failed:',name)
578 579 print('You may wish to rerun this one individually, with:')
579 580 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
580 581 print(u' '.join(failed_call_args))
581 582 print()
582 583 # Ensure that our exit code indicates failure
583 584 sys.exit(1)
584 585
585 586
586 587 def main():
587 588 for arg in sys.argv[1:]:
588 589 if arg.startswith('IPython'):
589 590 # This is in-process
590 591 run_iptest()
591 592 else:
592 593 if "--all" in sys.argv:
593 594 sys.argv.remove("--all")
594 595 inc_slow = True
595 596 else:
596 597 inc_slow = False
597 598 # This starts subprocesses
598 599 run_iptestall(inc_slow=inc_slow)
599 600
600 601
601 602 if __name__ == '__main__':
602 603 main()
General Comments 0
You need to be logged in to leave comments. Login now