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