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