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