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