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