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