##// END OF EJS Templates
remove jupyter_console
Min RK -
Show More
@@ -1,495 +1,494 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 148 test_group_names = ['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 'html': 'jupyter_notebook',
175 175 }
176 176
177 177 # Name -> (include, exclude, dependencies_met)
178 178 test_sections = {n:TestSection(n, [shims.get(n, 'IPython.%s' % n)]) for n in test_group_names}
179 179
180 180
181 181 # Exclusions and dependencies
182 182 # ---------------------------
183 183
184 184 # core:
185 185 sec = test_sections['core']
186 186 if not have['sqlite3']:
187 187 sec.exclude('tests.test_history')
188 188 sec.exclude('history')
189 189 if not have['matplotlib']:
190 190 sec.exclude('pylabtools'),
191 191 sec.exclude('tests.test_pylabtools')
192 192
193 193 # lib:
194 194 sec = test_sections['lib']
195 195 if not have['zmq']:
196 196 sec.exclude('kernel')
197 197 # We do this unconditionally, so that the test suite doesn't import
198 198 # gtk, changing the default encoding and masking some unicode bugs.
199 199 sec.exclude('inputhookgtk')
200 200 # We also do this unconditionally, because wx can interfere with Unix signals.
201 201 # There are currently no tests for it anyway.
202 202 sec.exclude('inputhookwx')
203 203 # Testing inputhook will need a lot of thought, to figure out
204 204 # how to have tests that don't lock up with the gui event
205 205 # loops in the picture
206 206 sec.exclude('inputhook')
207 207
208 208 # testing:
209 209 sec = test_sections['testing']
210 210 # These have to be skipped on win32 because they use echo, rm, cd, etc.
211 211 # See ticket https://github.com/ipython/ipython/issues/87
212 212 if sys.platform == 'win32':
213 213 sec.exclude('plugin.test_exampleip')
214 214 sec.exclude('plugin.dtexample')
215 215
216 # terminal:
217 if (not have['pexpect']) or (not have['zmq']):
218 test_sections['terminal'].exclude('console')
216 # don't run jupyter_console tests found via shim
217 test_sections['terminal'].exclude('console')
219 218
220 219 # extensions:
221 220 sec = test_sections['extensions']
222 221 # This is deprecated in favour of rpy2
223 222 sec.exclude('rmagic')
224 223 # autoreload does some strange stuff, so move it to its own test section
225 224 sec.exclude('autoreload')
226 225 sec.exclude('tests.test_autoreload')
227 226 test_sections['autoreload'] = TestSection('autoreload',
228 227 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
229 228 test_group_names.append('autoreload')
230 229
231 230 # qt:
232 231 test_sections['qt'].requires('zmq', 'qt', 'pygments')
233 232
234 233 # html:
235 234 sec = test_sections['html']
236 235 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
237 236 # The notebook 'static' directory contains JS, css and other
238 237 # files for web serving. Occasionally projects may put a .py
239 238 # file in there (MathJax ships a conf.py), so we might as
240 239 # well play it safe and skip the whole thing.
241 240 sec.exclude('static')
242 241 sec.exclude('tasks')
243 242 if not have['jinja2']:
244 243 sec.exclude('notebookapp')
245 244 if not have['pygments'] or not have['jinja2']:
246 245 sec.exclude('nbconvert')
247 246 if not have['terminado']:
248 247 sec.exclude('terminal')
249 248
250 249 # nbconvert:
251 250 sec = test_sections['nbconvert']
252 251 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
253 252 # Exclude nbconvert directories containing config files used to test.
254 253 # Executing the config files with iptest would cause an exception.
255 254 sec.exclude('tests.files')
256 255 sec.exclude('exporters.tests.files')
257 256 if not have['tornado']:
258 257 sec.exclude('nbconvert.post_processors.serve')
259 258 sec.exclude('nbconvert.post_processors.tests.test_serve')
260 259
261 260
262 261 #-----------------------------------------------------------------------------
263 262 # Functions and classes
264 263 #-----------------------------------------------------------------------------
265 264
266 265 def check_exclusions_exist():
267 266 from IPython.utils.path import get_ipython_package_dir
268 267 from IPython.utils.warn import warn
269 268 parent = os.path.dirname(get_ipython_package_dir())
270 269 for sec in test_sections:
271 270 for pattern in sec.exclusions:
272 271 fullpath = pjoin(parent, pattern)
273 272 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
274 273 warn("Excluding nonexistent file: %r" % pattern)
275 274
276 275
277 276 class ExclusionPlugin(Plugin):
278 277 """A nose plugin to effect our exclusions of files and directories.
279 278 """
280 279 name = 'exclusions'
281 280 score = 3000 # Should come before any other plugins
282 281
283 282 def __init__(self, exclude_patterns=None):
284 283 """
285 284 Parameters
286 285 ----------
287 286
288 287 exclude_patterns : sequence of strings, optional
289 288 Filenames containing these patterns (as raw strings, not as regular
290 289 expressions) are excluded from the tests.
291 290 """
292 291 self.exclude_patterns = exclude_patterns or []
293 292 super(ExclusionPlugin, self).__init__()
294 293
295 294 def options(self, parser, env=os.environ):
296 295 Plugin.options(self, parser, env)
297 296
298 297 def configure(self, options, config):
299 298 Plugin.configure(self, options, config)
300 299 # Override nose trying to disable plugin.
301 300 self.enabled = True
302 301
303 302 def wantFile(self, filename):
304 303 """Return whether the given filename should be scanned for tests.
305 304 """
306 305 if any(pat in filename for pat in self.exclude_patterns):
307 306 return False
308 307 return None
309 308
310 309 def wantDirectory(self, directory):
311 310 """Return whether the given directory should be scanned for tests.
312 311 """
313 312 if any(pat in directory for pat in self.exclude_patterns):
314 313 return False
315 314 return None
316 315
317 316
318 317 class StreamCapturer(Thread):
319 318 daemon = True # Don't hang if main thread crashes
320 319 started = False
321 320 def __init__(self, echo=False):
322 321 super(StreamCapturer, self).__init__()
323 322 self.echo = echo
324 323 self.streams = []
325 324 self.buffer = BytesIO()
326 325 self.readfd, self.writefd = os.pipe()
327 326 self.buffer_lock = Lock()
328 327 self.stop = Event()
329 328
330 329 def run(self):
331 330 self.started = True
332 331
333 332 while not self.stop.is_set():
334 333 chunk = os.read(self.readfd, 1024)
335 334
336 335 with self.buffer_lock:
337 336 self.buffer.write(chunk)
338 337 if self.echo:
339 338 sys.stdout.write(bytes_to_str(chunk))
340 339
341 340 os.close(self.readfd)
342 341 os.close(self.writefd)
343 342
344 343 def reset_buffer(self):
345 344 with self.buffer_lock:
346 345 self.buffer.truncate(0)
347 346 self.buffer.seek(0)
348 347
349 348 def get_buffer(self):
350 349 with self.buffer_lock:
351 350 return self.buffer.getvalue()
352 351
353 352 def ensure_started(self):
354 353 if not self.started:
355 354 self.start()
356 355
357 356 def halt(self):
358 357 """Safely stop the thread."""
359 358 if not self.started:
360 359 return
361 360
362 361 self.stop.set()
363 362 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
364 363 self.join()
365 364
366 365 class SubprocessStreamCapturePlugin(Plugin):
367 366 name='subprocstreams'
368 367 def __init__(self):
369 368 Plugin.__init__(self)
370 369 self.stream_capturer = StreamCapturer()
371 370 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
372 371 # This is ugly, but distant parts of the test machinery need to be able
373 372 # to redirect streams, so we make the object globally accessible.
374 373 nose.iptest_stdstreams_fileno = self.get_write_fileno
375 374
376 375 def get_write_fileno(self):
377 376 if self.destination == 'capture':
378 377 self.stream_capturer.ensure_started()
379 378 return self.stream_capturer.writefd
380 379 elif self.destination == 'discard':
381 380 return os.open(os.devnull, os.O_WRONLY)
382 381 else:
383 382 return sys.__stdout__.fileno()
384 383
385 384 def configure(self, options, config):
386 385 Plugin.configure(self, options, config)
387 386 # Override nose trying to disable plugin.
388 387 if self.destination == 'capture':
389 388 self.enabled = True
390 389
391 390 def startTest(self, test):
392 391 # Reset log capture
393 392 self.stream_capturer.reset_buffer()
394 393
395 394 def formatFailure(self, test, err):
396 395 # Show output
397 396 ec, ev, tb = err
398 397 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
399 398 if captured.strip():
400 399 ev = safe_str(ev)
401 400 out = [ev, '>> begin captured subprocess output <<',
402 401 captured,
403 402 '>> end captured subprocess output <<']
404 403 return ec, '\n'.join(out), tb
405 404
406 405 return err
407 406
408 407 formatError = formatFailure
409 408
410 409 def finalize(self, result):
411 410 self.stream_capturer.halt()
412 411
413 412
414 413 def run_iptest():
415 414 """Run the IPython test suite using nose.
416 415
417 416 This function is called when this script is **not** called with the form
418 417 `iptest all`. It simply calls nose with appropriate command line flags
419 418 and accepts all of the standard nose arguments.
420 419 """
421 420 # Apply our monkeypatch to Xunit
422 421 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
423 422 monkeypatch_xunit()
424 423
425 424 warnings.filterwarnings('ignore',
426 425 'This will be removed soon. Use IPython.testing.util instead')
427 426
428 427 arg1 = sys.argv[1]
429 428 if arg1 in test_sections:
430 429 section = test_sections[arg1]
431 430 sys.argv[1:2] = section.includes
432 431 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
433 432 section = test_sections[arg1[8:]]
434 433 sys.argv[1:2] = section.includes
435 434 else:
436 435 section = TestSection(arg1, includes=[arg1])
437 436
438 437
439 438 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
440 439 # We add --exe because of setuptools' imbecility (it
441 440 # blindly does chmod +x on ALL files). Nose does the
442 441 # right thing and it tries to avoid executables,
443 442 # setuptools unfortunately forces our hand here. This
444 443 # has been discussed on the distutils list and the
445 444 # setuptools devs refuse to fix this problem!
446 445 '--exe',
447 446 ]
448 447 if '-a' not in argv and '-A' not in argv:
449 448 argv = argv + ['-a', '!crash']
450 449
451 450 if nose.__version__ >= '0.11':
452 451 # I don't fully understand why we need this one, but depending on what
453 452 # directory the test suite is run from, if we don't give it, 0 tests
454 453 # get run. Specifically, if the test suite is run from the source dir
455 454 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
456 455 # even if the same call done in this directory works fine). It appears
457 456 # that if the requested package is in the current dir, nose bails early
458 457 # by default. Since it's otherwise harmless, leave it in by default
459 458 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
460 459 argv.append('--traverse-namespace')
461 460
462 461 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
463 462 SubprocessStreamCapturePlugin() ]
464 463
465 464 # we still have some vestigial doctests in core
466 465 if (section.name.startswith(('core', 'IPython.core'))):
467 466 plugins.append(IPythonDoctest())
468 467 argv.extend([
469 468 '--with-ipdoctest',
470 469 '--ipdoctest-tests',
471 470 '--ipdoctest-extension=txt',
472 471 ])
473 472
474 473
475 474 # Use working directory set by parent process (see iptestcontroller)
476 475 if 'IPTEST_WORKING_DIR' in os.environ:
477 476 os.chdir(os.environ['IPTEST_WORKING_DIR'])
478 477
479 478 # We need a global ipython running in this process, but the special
480 479 # in-process group spawns its own IPython kernels, so for *that* group we
481 480 # must avoid also opening the global one (otherwise there's a conflict of
482 481 # singletons). Ultimately the solution to this problem is to refactor our
483 482 # assumptions about what needs to be a singleton and what doesn't (app
484 483 # objects should, individual shells shouldn't). But for now, this
485 484 # workaround allows the test suite for the inprocess module to complete.
486 485 if 'kernel.inprocess' not in section.name:
487 486 from IPython.testing import globalipapp
488 487 globalipapp.start_ipython()
489 488
490 489 # Now nose can run
491 490 TestProgram(argv=argv, addplugins=plugins)
492 491
493 492 if __name__ == '__main__':
494 493 run_iptest()
495 494
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (585 lines changed) Show them Hide them
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now