##// END OF EJS Templates
remove nbconvert
Min RK -
Show More

The requested changes are too big and content was truncated. Show full diff

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