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