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