##// END OF EJS Templates
Make the test suite runnable without X11 connections....
Fernando Perez -
Show More
@@ -1,432 +1,434 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) or trial 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 For now, this script requires that both nose and twisted are installed. This
16 16 will change in the future.
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Module imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # Stdlib
24 24 import os
25 25 import os.path as path
26 26 import signal
27 27 import sys
28 28 import subprocess
29 29 import tempfile
30 30 import time
31 31 import warnings
32 32
33 33
34 34 # Ugly, but necessary hack to ensure the test suite finds our version of
35 35 # IPython and not a possibly different one that may exist system-wide.
36 36 # Note that this must be done here, so the imports that come next work
37 37 # correctly even if IPython isn't installed yet.
38 38 p = os.path
39 39 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
40 40 sys.path.insert(0, ippath)
41 41
42 42 # Note: monkeypatch!
43 43 # We need to monkeypatch a small problem in nose itself first, before importing
44 44 # it for actual use. This should get into nose upstream, but its release cycle
45 45 # is slow and we need it for our parametric tests to work correctly.
46 46 from IPython.testing import nosepatch
47 47 # Now, proceed to import nose itself
48 48 import nose.plugins.builtin
49 49 from nose.core import TestProgram
50 50
51 51 # Our own imports
52 52 from IPython.utils import genutils
53 53 from IPython.utils.platutils import find_cmd, FindCmdError
54 54 from IPython.testing import globalipapp
55 55 from IPython.testing import tools
56 56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57 57
58 58 pjoin = path.join
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Warnings control
62 62 #-----------------------------------------------------------------------------
63 63 # Twisted generates annoying warnings with Python 2.6, as will do other code
64 64 # that imports 'sets' as of today
65 65 warnings.filterwarnings('ignore', 'the sets module is deprecated',
66 66 DeprecationWarning )
67 67
68 68 # This one also comes from Twisted
69 69 warnings.filterwarnings('ignore', 'the sha module is deprecated',
70 70 DeprecationWarning)
71 71
72 72 # Wx on Fedora11 spits these out
73 73 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
74 74 UserWarning)
75 75
76 76 #-----------------------------------------------------------------------------
77 77 # Logic for skipping doctests
78 78 #-----------------------------------------------------------------------------
79 79
80 80 def test_for(mod):
81 81 """Test to see if mod is importable."""
82 82 try:
83 83 __import__(mod)
84 except ImportError:
84 except (ImportError, RuntimeError):
85 # GTK reports Runtime error if it can't be initialized even if it's
86 # importable.
85 87 return False
86 88 else:
87 89 return True
88 90
89 91
90 92 have_curses = test_for('_curses')
91 93 have_wx = test_for('wx')
92 94 have_wx_aui = test_for('wx.aui')
93 95 have_zi = test_for('zope.interface')
94 96 have_twisted = test_for('twisted')
95 97 have_foolscap = test_for('foolscap')
96 98 have_objc = test_for('objc')
97 99 have_pexpect = test_for('pexpect')
98 100 have_gtk = test_for('gtk')
99 101 have_gobject = test_for('gobject')
100 102
101 103
102 104 def make_exclude():
103 105 """Make patterns of modules and packages to exclude from testing.
104 106
105 107 For the IPythonDoctest plugin, we need to exclude certain patterns that
106 108 cause testing problems. We should strive to minimize the number of
107 109 skipped modules, since this means untested code. As the testing
108 110 machinery solidifies, this list should eventually become empty.
109 111 These modules and packages will NOT get scanned by nose at all for tests.
110 112 """
111 113 # Simple utility to make IPython paths more readably, we need a lot of
112 114 # these below
113 115 ipjoin = lambda *paths: pjoin('IPython', *paths)
114 116
115 117 exclusions = [ipjoin('external'),
116 118 ipjoin('frontend', 'process', 'winprocess.py'),
117 119 # Deprecated old Shell and iplib modules, skip to avoid
118 120 # warnings
119 121 ipjoin('Shell'),
120 122 ipjoin('iplib'),
121 123 pjoin('IPython_doctest_plugin'),
122 124 ipjoin('quarantine'),
123 125 ipjoin('deathrow'),
124 126 ipjoin('testing', 'attic'),
125 127 # This guy is probably attic material
126 128 ipjoin('testing', 'mkdoctests'),
127 129 # Testing inputhook will need a lot of thought, to figure out
128 130 # how to have tests that don't lock up with the gui event
129 131 # loops in the picture
130 132 ipjoin('lib', 'inputhook'),
131 133 # Config files aren't really importable stand-alone
132 134 ipjoin('config', 'default'),
133 135 ipjoin('config', 'profile'),
134 136 ]
135 137
136 138 if not have_wx:
137 139 exclusions.append(ipjoin('gui'))
138 140 exclusions.append(ipjoin('frontend', 'wx'))
139 141 exclusions.append(ipjoin('lib', 'inputhookwx'))
140 142
141 143 if not have_gtk or not have_gobject:
142 144 exclusions.append(ipjoin('lib', 'inputhookgtk'))
143 145
144 146 if not have_wx_aui:
145 147 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
146 148
147 149 if not have_objc:
148 150 exclusions.append(ipjoin('frontend', 'cocoa'))
149 151
150 152 if not sys.platform == 'win32':
151 153 exclusions.append(ipjoin('utils', 'platutils_win32'))
152 154
153 155 # These have to be skipped on win32 because the use echo, rm, cd, etc.
154 156 # See ticket https://bugs.launchpad.net/bugs/366982
155 157 if sys.platform == 'win32':
156 158 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
157 159 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
158 160
159 161 if not os.name == 'posix':
160 162 exclusions.append(ipjoin('utils', 'platutils_posix'))
161 163
162 164 if not have_pexpect:
163 165 exclusions.extend([ipjoin('scripts', 'irunner'),
164 166 ipjoin('lib', 'irunner')])
165 167
166 168 # This is scary. We still have things in frontend and testing that
167 169 # are being tested by nose that use twisted. We need to rethink
168 170 # how we are isolating dependencies in testing.
169 171 if not (have_twisted and have_zi and have_foolscap):
170 172 exclusions.extend(
171 173 [ipjoin('frontend', 'asyncfrontendbase'),
172 174 ipjoin('frontend', 'prefilterfrontend'),
173 175 ipjoin('frontend', 'frontendbase'),
174 176 ipjoin('frontend', 'linefrontendbase'),
175 177 ipjoin('frontend', 'tests', 'test_linefrontend'),
176 178 ipjoin('frontend', 'tests', 'test_frontendbase'),
177 179 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
178 180 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
179 181 ipjoin('testing', 'parametric'),
180 182 ipjoin('testing', 'util'),
181 183 ipjoin('testing', 'tests', 'test_decorators_trial'),
182 184 ] )
183 185
184 186 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
185 187 if sys.platform == 'win32':
186 188 exclusions = [s.replace('\\','\\\\') for s in exclusions]
187 189
188 190 return exclusions
189 191
190 192
191 193 #-----------------------------------------------------------------------------
192 194 # Functions and classes
193 195 #-----------------------------------------------------------------------------
194 196
195 197 class IPTester(object):
196 198 """Call that calls iptest or trial in a subprocess.
197 199 """
198 200 #: string, name of test runner that will be called
199 201 runner = None
200 202 #: list, parameters for test runner
201 203 params = None
202 204 #: list, arguments of system call to be made to call test runner
203 205 call_args = None
204 206 #: list, process ids of subprocesses we start (for cleanup)
205 207 pids = None
206 208
207 209 def __init__(self, runner='iptest', params=None):
208 210 """Create new test runner."""
209 211 if runner == 'iptest':
210 212 # Find our own 'iptest' script OS-level entry point. Don't look
211 213 # system-wide, so we are sure we pick up *this one*. And pass
212 214 # through to subprocess call our own sys.argv
213 215 self.runner = tools.cmd2argv(os.path.abspath(__file__)) + \
214 216 sys.argv[1:]
215 217 else:
216 218 self.runner = tools.cmd2argv(os.path.abspath(find_cmd('trial')))
217 219 if params is None:
218 220 params = []
219 221 if isinstance(params, str):
220 222 params = [params]
221 223 self.params = params
222 224
223 225 # Assemble call
224 226 self.call_args = self.runner+self.params
225 227
226 228 # Store pids of anything we start to clean up on deletion, if possible
227 229 # (on posix only, since win32 has no os.kill)
228 230 self.pids = []
229 231
230 232 if sys.platform == 'win32':
231 233 def _run_cmd(self):
232 234 # On Windows, use os.system instead of subprocess.call, because I
233 235 # was having problems with subprocess and I just don't know enough
234 236 # about win32 to debug this reliably. Os.system may be the 'old
235 237 # fashioned' way to do it, but it works just fine. If someone
236 238 # later can clean this up that's fine, as long as the tests run
237 239 # reliably in win32.
238 240 return os.system(' '.join(self.call_args))
239 241 else:
240 242 def _run_cmd(self):
241 243 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
242 244 subp = subprocess.Popen(self.call_args)
243 245 self.pids.append(subp.pid)
244 246 # If this fails, the pid will be left in self.pids and cleaned up
245 247 # later, but if the wait call succeeds, then we can clear the
246 248 # stored pid.
247 249 retcode = subp.wait()
248 250 self.pids.pop()
249 251 return retcode
250 252
251 253 def run(self):
252 254 """Run the stored commands"""
253 255 try:
254 256 return self._run_cmd()
255 257 except:
256 258 import traceback
257 259 traceback.print_exc()
258 260 return 1 # signal failure
259 261
260 262 def __del__(self):
261 263 """Cleanup on exit by killing any leftover processes."""
262 264
263 265 if not hasattr(os, 'kill'):
264 266 return
265 267
266 268 for pid in self.pids:
267 269 try:
268 270 print 'Cleaning stale PID:', pid
269 271 os.kill(pid, signal.SIGKILL)
270 272 except OSError:
271 273 # This is just a best effort, if we fail or the process was
272 274 # really gone, ignore it.
273 275 pass
274 276
275 277
276 278 def make_runners():
277 279 """Define the top-level packages that need to be tested.
278 280 """
279 281
280 282 # Packages to be tested via nose, that only depend on the stdlib
281 283 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
282 284 'scripts', 'testing', 'utils' ]
283 285 # The machinery in kernel needs twisted for real testing
284 286 trial_pkg_names = []
285 287
286 288 if have_wx:
287 289 nose_pkg_names.append('gui')
288 290
289 291 # And add twisted ones if conditions are met
290 292 if have_zi and have_twisted and have_foolscap:
291 293 # Note that we list the kernel here, though the bulk of it is
292 294 # twisted-based, because nose picks up doctests that twisted doesn't.
293 295 nose_pkg_names.append('kernel')
294 296 trial_pkg_names.append('kernel')
295 297
296 298 # For debugging this code, only load quick stuff
297 299 #nose_pkg_names = ['core'] # dbg
298 300 #trial_pkg_names = [] # dbg
299 301
300 302 # Make fully qualified package names prepending 'IPython.' to our name lists
301 303 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
302 304 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
303 305
304 306 # Make runners
305 307 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
306 308 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
307 309
308 310 return runners
309 311
310 312
311 313 def run_iptest():
312 314 """Run the IPython test suite using nose.
313 315
314 316 This function is called when this script is **not** called with the form
315 317 `iptest all`. It simply calls nose with appropriate command line flags
316 318 and accepts all of the standard nose arguments.
317 319 """
318 320
319 321 warnings.filterwarnings('ignore',
320 322 'This will be removed soon. Use IPython.testing.util instead')
321 323
322 324 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
323 325
324 326 # I don't fully understand why we need this one, but
325 327 # depending on what directory the test suite is run
326 328 # from, if we don't give it, 0 tests get run.
327 329 # Specifically, if the test suite is run from the
328 330 # source dir with an argument (like 'iptest.py
329 331 # IPython.core', 0 tests are run, even if the same call
330 332 # done in this directory works fine). It appears that
331 333 # if the requested package is in the current dir,
332 334 # nose bails early by default. Since it's otherwise
333 335 # harmless, leave it in by default.
334 336 '--traverse-namespace',
335 337
336 338 # Loading ipdoctest causes problems with Twisted, but
337 339 # our test suite runner now separates things and runs
338 340 # all Twisted tests with trial.
339 341 '--with-ipdoctest',
340 342 '--ipdoctest-tests','--ipdoctest-extension=txt',
341 343
342 344 # We add --exe because of setuptools' imbecility (it
343 345 # blindly does chmod +x on ALL files). Nose does the
344 346 # right thing and it tries to avoid executables,
345 347 # setuptools unfortunately forces our hand here. This
346 348 # has been discussed on the distutils list and the
347 349 # setuptools devs refuse to fix this problem!
348 350 '--exe',
349 351 ]
350 352
351 353
352 354 # Construct list of plugins, omitting the existing doctest plugin, which
353 355 # ours replaces (and extends).
354 356 plugins = [IPythonDoctest(make_exclude())]
355 357 for p in nose.plugins.builtin.plugins:
356 358 plug = p()
357 359 if plug.name == 'doctest':
358 360 continue
359 361 plugins.append(plug)
360 362
361 363 # We need a global ipython running in this process
362 364 globalipapp.start_ipython()
363 365 # Now nose can run
364 366 TestProgram(argv=argv, plugins=plugins)
365 367
366 368
367 369 def run_iptestall():
368 370 """Run the entire IPython test suite by calling nose and trial.
369 371
370 372 This function constructs :class:`IPTester` instances for all IPython
371 373 modules and package and then runs each of them. This causes the modules
372 374 and packages of IPython to be tested each in their own subprocess using
373 375 nose or twisted.trial appropriately.
374 376 """
375 377
376 378 runners = make_runners()
377 379
378 380 # Run the test runners in a temporary dir so we can nuke it when finished
379 381 # to clean up any junk files left over by accident. This also makes it
380 382 # robust against being run in non-writeable directories by mistake, as the
381 383 # temp dir will always be user-writeable.
382 384 curdir = os.getcwd()
383 385 testdir = tempfile.gettempdir()
384 386 os.chdir(testdir)
385 387
386 388 # Run all test runners, tracking execution time
387 389 failed = []
388 390 t_start = time.time()
389 391 try:
390 392 for (name, runner) in runners:
391 393 print '*'*70
392 394 print 'IPython test group:',name
393 395 res = runner.run()
394 396 if res:
395 397 failed.append( (name, runner) )
396 398 finally:
397 399 os.chdir(curdir)
398 400 t_end = time.time()
399 401 t_tests = t_end - t_start
400 402 nrunners = len(runners)
401 403 nfail = len(failed)
402 404 # summarize results
403 405 print
404 406 print '*'*70
405 407 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
406 408 print
407 409 if not failed:
408 410 print 'OK'
409 411 else:
410 412 # If anything went wrong, point out what command to rerun manually to
411 413 # see the actual errors and individual summary
412 414 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
413 415 for name, failed_runner in failed:
414 416 print '-'*40
415 417 print 'Runner failed:',name
416 418 print 'You may wish to rerun this one individually, with:'
417 419 print ' '.join(failed_runner.call_args)
418 420 print
419 421
420 422
421 423 def main():
422 424 for arg in sys.argv[1:]:
423 425 if arg.startswith('IPython'):
424 426 # This is in-process
425 427 run_iptest()
426 428 else:
427 429 # This starts subprocesses
428 430 run_iptestall()
429 431
430 432
431 433 if __name__ == '__main__':
432 434 main()
General Comments 0
You need to be logged in to leave comments. Login now