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