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