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