##// END OF EJS Templates
Don't check for wx in the test suite....
Thomas Kluyver -
Show More
@@ -1,520 +1,519 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-2011 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 from __future__ import print_function
28 28
29 29 # Stdlib
30 30 import glob
31 31 from io import BytesIO
32 32 import os
33 33 import os.path as path
34 34 import sys
35 35 from threading import Thread, Lock, Event
36 36 import warnings
37 37
38 38 # Now, proceed to import nose itself
39 39 import nose.plugins.builtin
40 40 from nose.plugins.xunit import Xunit
41 41 from nose import SkipTest
42 42 from nose.core import TestProgram
43 43 from nose.plugins import Plugin
44 44 from nose.util import safe_str
45 45
46 46 # Our own imports
47 47 from IPython.utils.process import is_cmd_found
48 48 from IPython.utils.importstring import import_item
49 49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
50 50 from IPython.external.decorators import KnownFailure, knownfailureif
51 51
52 52 pjoin = path.join
53 53
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Globals
57 57 #-----------------------------------------------------------------------------
58 58
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Warnings control
62 62 #-----------------------------------------------------------------------------
63 63
64 64 # Twisted generates annoying warnings with Python 2.6, as will do other code
65 65 # that imports 'sets' as of today
66 66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
67 67 DeprecationWarning )
68 68
69 69 # This one also comes from Twisted
70 70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
71 71 DeprecationWarning)
72 72
73 73 # Wx on Fedora11 spits these out
74 74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
75 75 UserWarning)
76 76
77 77 # ------------------------------------------------------------------------------
78 78 # Monkeypatch Xunit to count known failures as skipped.
79 79 # ------------------------------------------------------------------------------
80 80 def monkeypatch_xunit():
81 81 try:
82 82 knownfailureif(True)(lambda: None)()
83 83 except Exception as e:
84 84 KnownFailureTest = type(e)
85 85
86 86 def addError(self, test, err, capt=None):
87 87 if issubclass(err[0], KnownFailureTest):
88 88 err = (SkipTest,) + err[1:]
89 89 return self.orig_addError(test, err, capt)
90 90
91 91 Xunit.orig_addError = Xunit.addError
92 92 Xunit.addError = addError
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Check which dependencies are installed and greater than minimum version.
96 96 #-----------------------------------------------------------------------------
97 97 def extract_version(mod):
98 98 return mod.__version__
99 99
100 100 def test_for(item, min_version=None, callback=extract_version):
101 101 """Test to see if item is importable, and optionally check against a minimum
102 102 version.
103 103
104 104 If min_version is given, the default behavior is to check against the
105 105 `__version__` attribute of the item, but specifying `callback` allows you to
106 106 extract the value you are interested in. e.g::
107 107
108 108 In [1]: import sys
109 109
110 110 In [2]: from IPython.testing.iptest import test_for
111 111
112 112 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
113 113 Out[3]: True
114 114
115 115 """
116 116 try:
117 117 check = import_item(item)
118 118 except (ImportError, RuntimeError):
119 119 # GTK reports Runtime error if it can't be initialized even if it's
120 120 # importable.
121 121 return False
122 122 else:
123 123 if min_version:
124 124 if callback:
125 125 # extra processing step to get version to compare
126 126 check = callback(check)
127 127
128 128 return check >= min_version
129 129 else:
130 130 return True
131 131
132 132 # Global dict where we can store information on what we have and what we don't
133 133 # have available at test run time
134 134 have = {}
135 135
136 136 have['curses'] = test_for('_curses')
137 137 have['matplotlib'] = test_for('matplotlib')
138 138 have['numpy'] = test_for('numpy')
139 139 have['pexpect'] = test_for('IPython.external.pexpect')
140 140 have['pymongo'] = test_for('pymongo')
141 141 have['pygments'] = test_for('pygments')
142 142 have['qt'] = test_for('IPython.external.qt')
143 143 have['rpy2'] = test_for('rpy2')
144 144 have['sqlite3'] = test_for('sqlite3')
145 145 have['cython'] = test_for('Cython')
146 146 have['oct2py'] = test_for('oct2py')
147 147 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
148 148 have['jinja2'] = test_for('jinja2')
149 have['wx'] = test_for('wx')
150 have['wx.aui'] = test_for('wx.aui')
151 149 have['azure'] = test_for('azure')
152 150 have['requests'] = test_for('requests')
153 151 have['sphinx'] = test_for('sphinx')
154 152 have['casperjs'] = is_cmd_found('casperjs')
155 153
156 154 min_zmq = (2,1,11)
157 155
158 156 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
159 157
160 158 #-----------------------------------------------------------------------------
161 159 # Test suite definitions
162 160 #-----------------------------------------------------------------------------
163 161
164 162 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
165 163 'extensions', 'lib', 'terminal', 'testing', 'utils',
166 164 'nbformat', 'qt', 'html', 'nbconvert'
167 165 ]
168 166
169 167 class TestSection(object):
170 168 def __init__(self, name, includes):
171 169 self.name = name
172 170 self.includes = includes
173 171 self.excludes = []
174 172 self.dependencies = []
175 173 self.enabled = True
176 174
177 175 def exclude(self, module):
178 176 if not module.startswith('IPython'):
179 177 module = self.includes[0] + "." + module
180 178 self.excludes.append(module.replace('.', os.sep))
181 179
182 180 def requires(self, *packages):
183 181 self.dependencies.extend(packages)
184 182
185 183 @property
186 184 def will_run(self):
187 185 return self.enabled and all(have[p] for p in self.dependencies)
188 186
189 187 # Name -> (include, exclude, dependencies_met)
190 188 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
191 189
192 190 # Exclusions and dependencies
193 191 # ---------------------------
194 192
195 193 # core:
196 194 sec = test_sections['core']
197 195 if not have['sqlite3']:
198 196 sec.exclude('tests.test_history')
199 197 sec.exclude('history')
200 198 if not have['matplotlib']:
201 199 sec.exclude('pylabtools'),
202 200 sec.exclude('tests.test_pylabtools')
203 201
204 202 # lib:
205 203 sec = test_sections['lib']
206 if not have['wx']:
207 sec.exclude('inputhookwx')
208 204 if not have['pexpect']:
209 205 sec.exclude('irunner')
210 206 sec.exclude('tests.test_irunner')
211 207 if not have['zmq']:
212 208 sec.exclude('kernel')
213 209 # We do this unconditionally, so that the test suite doesn't import
214 210 # gtk, changing the default encoding and masking some unicode bugs.
215 211 sec.exclude('inputhookgtk')
212 # We also do this unconditionally, because wx can interfere with Unix signals.
213 # There are currently no tests for it anyway.
214 sec.exclude('inputhookwx')
216 215 # Testing inputhook will need a lot of thought, to figure out
217 216 # how to have tests that don't lock up with the gui event
218 217 # loops in the picture
219 218 sec.exclude('inputhook')
220 219
221 220 # testing:
222 221 sec = test_sections['testing']
223 222 # This guy is probably attic material
224 223 sec.exclude('mkdoctests')
225 224 # These have to be skipped on win32 because they use echo, rm, cd, etc.
226 225 # See ticket https://github.com/ipython/ipython/issues/87
227 226 if sys.platform == 'win32':
228 227 sec.exclude('plugin.test_exampleip')
229 228 sec.exclude('plugin.dtexample')
230 229
231 230 # terminal:
232 231 if (not have['pexpect']) or (not have['zmq']):
233 232 test_sections['terminal'].exclude('console')
234 233
235 234 # parallel
236 235 sec = test_sections['parallel']
237 236 sec.requires('zmq')
238 237 if not have['pymongo']:
239 238 sec.exclude('controller.mongodb')
240 239 sec.exclude('tests.test_mongodb')
241 240
242 241 # kernel:
243 242 sec = test_sections['kernel']
244 243 sec.requires('zmq')
245 244 # The in-process kernel tests are done in a separate section
246 245 sec.exclude('inprocess')
247 246 # importing gtk sets the default encoding, which we want to avoid
248 247 sec.exclude('zmq.gui.gtkembed')
249 248 if not have['matplotlib']:
250 249 sec.exclude('zmq.pylab')
251 250
252 251 # kernel.inprocess:
253 252 test_sections['kernel.inprocess'].requires('zmq')
254 253
255 254 # extensions:
256 255 sec = test_sections['extensions']
257 256 if not have['cython']:
258 257 sec.exclude('cythonmagic')
259 258 sec.exclude('tests.test_cythonmagic')
260 259 if not have['oct2py']:
261 260 sec.exclude('octavemagic')
262 261 sec.exclude('tests.test_octavemagic')
263 262 if not have['rpy2'] or not have['numpy']:
264 263 sec.exclude('rmagic')
265 264 sec.exclude('tests.test_rmagic')
266 265 # autoreload does some strange stuff, so move it to its own test section
267 266 sec.exclude('autoreload')
268 267 sec.exclude('tests.test_autoreload')
269 268 test_sections['autoreload'] = TestSection('autoreload',
270 269 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
271 270 test_group_names.append('autoreload')
272 271
273 272 # qt:
274 273 test_sections['qt'].requires('zmq', 'qt', 'pygments')
275 274
276 275 # html:
277 276 sec = test_sections['html']
278 277 sec.requires('zmq', 'tornado', 'requests')
279 278 # The notebook 'static' directory contains JS, css and other
280 279 # files for web serving. Occasionally projects may put a .py
281 280 # file in there (MathJax ships a conf.py), so we might as
282 281 # well play it safe and skip the whole thing.
283 282 sec.exclude('static')
284 283 sec.exclude('fabfile')
285 284 if not have['jinja2']:
286 285 sec.exclude('notebookapp')
287 286 if not have['azure']:
288 287 sec.exclude('services.notebooks.azurenbmanager')
289 288
290 289 # config:
291 290 # Config files aren't really importable stand-alone
292 291 test_sections['config'].exclude('profile')
293 292
294 293 # nbconvert:
295 294 sec = test_sections['nbconvert']
296 295 sec.requires('pygments', 'jinja2', 'sphinx')
297 296 # Exclude nbconvert directories containing config files used to test.
298 297 # Executing the config files with iptest would cause an exception.
299 298 sec.exclude('tests.files')
300 299 sec.exclude('exporters.tests.files')
301 300 if not have['tornado']:
302 301 sec.exclude('nbconvert.post_processors.serve')
303 302 sec.exclude('nbconvert.post_processors.tests.test_serve')
304 303
305 304 #-----------------------------------------------------------------------------
306 305 # Functions and classes
307 306 #-----------------------------------------------------------------------------
308 307
309 308 def check_exclusions_exist():
310 309 from IPython.utils.path import get_ipython_package_dir
311 310 from IPython.utils.warn import warn
312 311 parent = os.path.dirname(get_ipython_package_dir())
313 312 for sec in test_sections:
314 313 for pattern in sec.exclusions:
315 314 fullpath = pjoin(parent, pattern)
316 315 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
317 316 warn("Excluding nonexistent file: %r" % pattern)
318 317
319 318
320 319 class ExclusionPlugin(Plugin):
321 320 """A nose plugin to effect our exclusions of files and directories.
322 321 """
323 322 name = 'exclusions'
324 323 score = 3000 # Should come before any other plugins
325 324
326 325 def __init__(self, exclude_patterns=None):
327 326 """
328 327 Parameters
329 328 ----------
330 329
331 330 exclude_patterns : sequence of strings, optional
332 331 Filenames containing these patterns (as raw strings, not as regular
333 332 expressions) are excluded from the tests.
334 333 """
335 334 self.exclude_patterns = exclude_patterns or []
336 335 super(ExclusionPlugin, self).__init__()
337 336
338 337 def options(self, parser, env=os.environ):
339 338 Plugin.options(self, parser, env)
340 339
341 340 def configure(self, options, config):
342 341 Plugin.configure(self, options, config)
343 342 # Override nose trying to disable plugin.
344 343 self.enabled = True
345 344
346 345 def wantFile(self, filename):
347 346 """Return whether the given filename should be scanned for tests.
348 347 """
349 348 if any(pat in filename for pat in self.exclude_patterns):
350 349 return False
351 350 return None
352 351
353 352 def wantDirectory(self, directory):
354 353 """Return whether the given directory should be scanned for tests.
355 354 """
356 355 if any(pat in directory for pat in self.exclude_patterns):
357 356 return False
358 357 return None
359 358
360 359
361 360 class StreamCapturer(Thread):
362 361 daemon = True # Don't hang if main thread crashes
363 362 started = False
364 363 def __init__(self):
365 364 super(StreamCapturer, self).__init__()
366 365 self.streams = []
367 366 self.buffer = BytesIO()
368 367 self.readfd, self.writefd = os.pipe()
369 368 self.buffer_lock = Lock()
370 369 self.stop = Event()
371 370
372 371 def run(self):
373 372 self.started = True
374 373
375 374 while not self.stop.is_set():
376 375 chunk = os.read(self.readfd, 1024)
377 376
378 377 with self.buffer_lock:
379 378 self.buffer.write(chunk)
380 379
381 380 os.close(self.readfd)
382 381 os.close(self.writefd)
383 382
384 383 def reset_buffer(self):
385 384 with self.buffer_lock:
386 385 self.buffer.truncate(0)
387 386 self.buffer.seek(0)
388 387
389 388 def get_buffer(self):
390 389 with self.buffer_lock:
391 390 return self.buffer.getvalue()
392 391
393 392 def ensure_started(self):
394 393 if not self.started:
395 394 self.start()
396 395
397 396 def halt(self):
398 397 """Safely stop the thread."""
399 398 if not self.started:
400 399 return
401 400
402 401 self.stop.set()
403 402 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
404 403 self.join()
405 404
406 405 class SubprocessStreamCapturePlugin(Plugin):
407 406 name='subprocstreams'
408 407 def __init__(self):
409 408 Plugin.__init__(self)
410 409 self.stream_capturer = StreamCapturer()
411 410 # This is ugly, but distant parts of the test machinery need to be able
412 411 # to redirect streams, so we make the object globally accessible.
413 412 nose.ipy_stream_capturer = self.stream_capturer
414 413
415 414 def configure(self, options, config):
416 415 Plugin.configure(self, options, config)
417 416 # Override nose trying to disable plugin.
418 417 self.enabled = True
419 418
420 419 def startTest(self, test):
421 420 # Reset log capture
422 421 self.stream_capturer.reset_buffer()
423 422
424 423 def formatFailure(self, test, err):
425 424 # Show output
426 425 ec, ev, tb = err
427 426 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
428 427 if captured.strip():
429 428 ev = safe_str(ev)
430 429 out = [ev, '>> begin captured subprocess output <<',
431 430 captured,
432 431 '>> end captured subprocess output <<']
433 432 return ec, '\n'.join(out), tb
434 433
435 434 return err
436 435
437 436 formatError = formatFailure
438 437
439 438 def finalize(self, result):
440 439 self.stream_capturer.halt()
441 440
442 441
443 442 def run_iptest():
444 443 """Run the IPython test suite using nose.
445 444
446 445 This function is called when this script is **not** called with the form
447 446 `iptest all`. It simply calls nose with appropriate command line flags
448 447 and accepts all of the standard nose arguments.
449 448 """
450 449 # Apply our monkeypatch to Xunit
451 450 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
452 451 monkeypatch_xunit()
453 452
454 453 warnings.filterwarnings('ignore',
455 454 'This will be removed soon. Use IPython.testing.util instead')
456 455
457 456 arg1 = sys.argv[1]
458 457 if arg1 in test_sections:
459 458 section = test_sections[arg1]
460 459 sys.argv[1:2] = section.includes
461 460 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
462 461 section = test_sections[arg1[8:]]
463 462 sys.argv[1:2] = section.includes
464 463 else:
465 464 section = TestSection(arg1, includes=[arg1])
466 465
467 466
468 467 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
469 468
470 469 '--with-ipdoctest',
471 470 '--ipdoctest-tests','--ipdoctest-extension=txt',
472 471
473 472 # We add --exe because of setuptools' imbecility (it
474 473 # blindly does chmod +x on ALL files). Nose does the
475 474 # right thing and it tries to avoid executables,
476 475 # setuptools unfortunately forces our hand here. This
477 476 # has been discussed on the distutils list and the
478 477 # setuptools devs refuse to fix this problem!
479 478 '--exe',
480 479 ]
481 480 if '-a' not in argv and '-A' not in argv:
482 481 argv = argv + ['-a', '!crash']
483 482
484 483 if nose.__version__ >= '0.11':
485 484 # I don't fully understand why we need this one, but depending on what
486 485 # directory the test suite is run from, if we don't give it, 0 tests
487 486 # get run. Specifically, if the test suite is run from the source dir
488 487 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
489 488 # even if the same call done in this directory works fine). It appears
490 489 # that if the requested package is in the current dir, nose bails early
491 490 # by default. Since it's otherwise harmless, leave it in by default
492 491 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
493 492 argv.append('--traverse-namespace')
494 493
495 494 # use our plugin for doctesting. It will remove the standard doctest plugin
496 495 # if it finds it enabled
497 496 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
498 497 SubprocessStreamCapturePlugin() ]
499 498
500 499 # Use working directory set by parent process (see iptestcontroller)
501 500 if 'IPTEST_WORKING_DIR' in os.environ:
502 501 os.chdir(os.environ['IPTEST_WORKING_DIR'])
503 502
504 503 # We need a global ipython running in this process, but the special
505 504 # in-process group spawns its own IPython kernels, so for *that* group we
506 505 # must avoid also opening the global one (otherwise there's a conflict of
507 506 # singletons). Ultimately the solution to this problem is to refactor our
508 507 # assumptions about what needs to be a singleton and what doesn't (app
509 508 # objects should, individual shells shouldn't). But for now, this
510 509 # workaround allows the test suite for the inprocess module to complete.
511 510 if 'kernel.inprocess' not in section.name:
512 511 from IPython.testing import globalipapp
513 512 globalipapp.start_ipython()
514 513
515 514 # Now nose can run
516 515 TestProgram(argv=argv, addplugins=plugins)
517 516
518 517 if __name__ == '__main__':
519 518 run_iptest()
520 519
General Comments 0
You need to be logged in to leave comments. Login now