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