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