##// END OF EJS Templates
enable doctests for IPython.utils
Paul Ivanov -
Show More
@@ -1,433 +1,433
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 import version_info
37 from IPython import version_info
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 # Enable printing all warnings raise by IPython's modules
46 # Enable printing all warnings raise by IPython's modules
47 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
47 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
48 if sys.version_info > (3,0):
48 if sys.version_info > (3,0):
49 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
49 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
50 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
50 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
51 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
51 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
52
52
53 if version_info < (6,):
53 if version_info < (6,):
54 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
54 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
55 # warning with the runner they also import from standard import library. (as of Dec 2015)
55 # warning with the runner they also import from standard import library. (as of Dec 2015)
56 # Ignore, let's revisit that in a couple of years for IPython 6.
56 # Ignore, let's revisit that in a couple of years for IPython 6.
57 warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
57 warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
58
58
59
59
60 # ------------------------------------------------------------------------------
60 # ------------------------------------------------------------------------------
61 # Monkeypatch Xunit to count known failures as skipped.
61 # Monkeypatch Xunit to count known failures as skipped.
62 # ------------------------------------------------------------------------------
62 # ------------------------------------------------------------------------------
63 def monkeypatch_xunit():
63 def monkeypatch_xunit():
64 try:
64 try:
65 knownfailureif(True)(lambda: None)()
65 knownfailureif(True)(lambda: None)()
66 except Exception as e:
66 except Exception as e:
67 KnownFailureTest = type(e)
67 KnownFailureTest = type(e)
68
68
69 def addError(self, test, err, capt=None):
69 def addError(self, test, err, capt=None):
70 if issubclass(err[0], KnownFailureTest):
70 if issubclass(err[0], KnownFailureTest):
71 err = (SkipTest,) + err[1:]
71 err = (SkipTest,) + err[1:]
72 return self.orig_addError(test, err, capt)
72 return self.orig_addError(test, err, capt)
73
73
74 Xunit.orig_addError = Xunit.addError
74 Xunit.orig_addError = Xunit.addError
75 Xunit.addError = addError
75 Xunit.addError = addError
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Check which dependencies are installed and greater than minimum version.
78 # Check which dependencies are installed and greater than minimum version.
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 def extract_version(mod):
80 def extract_version(mod):
81 return mod.__version__
81 return mod.__version__
82
82
83 def test_for(item, min_version=None, callback=extract_version):
83 def test_for(item, min_version=None, callback=extract_version):
84 """Test to see if item is importable, and optionally check against a minimum
84 """Test to see if item is importable, and optionally check against a minimum
85 version.
85 version.
86
86
87 If min_version is given, the default behavior is to check against the
87 If min_version is given, the default behavior is to check against the
88 `__version__` attribute of the item, but specifying `callback` allows you to
88 `__version__` attribute of the item, but specifying `callback` allows you to
89 extract the value you are interested in. e.g::
89 extract the value you are interested in. e.g::
90
90
91 In [1]: import sys
91 In [1]: import sys
92
92
93 In [2]: from IPython.testing.iptest import test_for
93 In [2]: from IPython.testing.iptest import test_for
94
94
95 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
95 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
96 Out[3]: True
96 Out[3]: True
97
97
98 """
98 """
99 try:
99 try:
100 check = import_item(item)
100 check = import_item(item)
101 except (ImportError, RuntimeError):
101 except (ImportError, RuntimeError):
102 # GTK reports Runtime error if it can't be initialized even if it's
102 # GTK reports Runtime error if it can't be initialized even if it's
103 # importable.
103 # importable.
104 return False
104 return False
105 else:
105 else:
106 if min_version:
106 if min_version:
107 if callback:
107 if callback:
108 # extra processing step to get version to compare
108 # extra processing step to get version to compare
109 check = callback(check)
109 check = callback(check)
110
110
111 return check >= min_version
111 return check >= min_version
112 else:
112 else:
113 return True
113 return True
114
114
115 # Global dict where we can store information on what we have and what we don't
115 # Global dict where we can store information on what we have and what we don't
116 # have available at test run time
116 # have available at test run time
117 have = {'matplotlib': test_for('matplotlib'),
117 have = {'matplotlib': test_for('matplotlib'),
118 'pygments': test_for('pygments'),
118 'pygments': test_for('pygments'),
119 'sqlite3': test_for('sqlite3')}
119 'sqlite3': test_for('sqlite3')}
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # Test suite definitions
122 # Test suite definitions
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124
124
125 test_group_names = ['core',
125 test_group_names = ['core',
126 'extensions', 'lib', 'terminal', 'testing', 'utils',
126 'extensions', 'lib', 'terminal', 'testing', 'utils',
127 ]
127 ]
128
128
129 class TestSection(object):
129 class TestSection(object):
130 def __init__(self, name, includes):
130 def __init__(self, name, includes):
131 self.name = name
131 self.name = name
132 self.includes = includes
132 self.includes = includes
133 self.excludes = []
133 self.excludes = []
134 self.dependencies = []
134 self.dependencies = []
135 self.enabled = True
135 self.enabled = True
136
136
137 def exclude(self, module):
137 def exclude(self, module):
138 if not module.startswith('IPython'):
138 if not module.startswith('IPython'):
139 module = self.includes[0] + "." + module
139 module = self.includes[0] + "." + module
140 self.excludes.append(module.replace('.', os.sep))
140 self.excludes.append(module.replace('.', os.sep))
141
141
142 def requires(self, *packages):
142 def requires(self, *packages):
143 self.dependencies.extend(packages)
143 self.dependencies.extend(packages)
144
144
145 @property
145 @property
146 def will_run(self):
146 def will_run(self):
147 return self.enabled and all(have[p] for p in self.dependencies)
147 return self.enabled and all(have[p] for p in self.dependencies)
148
148
149 # Name -> (include, exclude, dependencies_met)
149 # Name -> (include, exclude, dependencies_met)
150 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
150 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
151
151
152
152
153 # Exclusions and dependencies
153 # Exclusions and dependencies
154 # ---------------------------
154 # ---------------------------
155
155
156 # core:
156 # core:
157 sec = test_sections['core']
157 sec = test_sections['core']
158 if not have['sqlite3']:
158 if not have['sqlite3']:
159 sec.exclude('tests.test_history')
159 sec.exclude('tests.test_history')
160 sec.exclude('history')
160 sec.exclude('history')
161 if not have['matplotlib']:
161 if not have['matplotlib']:
162 sec.exclude('pylabtools'),
162 sec.exclude('pylabtools'),
163 sec.exclude('tests.test_pylabtools')
163 sec.exclude('tests.test_pylabtools')
164
164
165 # lib:
165 # lib:
166 sec = test_sections['lib']
166 sec = test_sections['lib']
167 sec.exclude('kernel')
167 sec.exclude('kernel')
168 if not have['pygments']:
168 if not have['pygments']:
169 sec.exclude('tests.test_lexers')
169 sec.exclude('tests.test_lexers')
170 # We do this unconditionally, so that the test suite doesn't import
170 # We do this unconditionally, so that the test suite doesn't import
171 # gtk, changing the default encoding and masking some unicode bugs.
171 # gtk, changing the default encoding and masking some unicode bugs.
172 sec.exclude('inputhookgtk')
172 sec.exclude('inputhookgtk')
173 # We also do this unconditionally, because wx can interfere with Unix signals.
173 # We also do this unconditionally, because wx can interfere with Unix signals.
174 # There are currently no tests for it anyway.
174 # There are currently no tests for it anyway.
175 sec.exclude('inputhookwx')
175 sec.exclude('inputhookwx')
176 # Testing inputhook will need a lot of thought, to figure out
176 # Testing inputhook will need a lot of thought, to figure out
177 # how to have tests that don't lock up with the gui event
177 # how to have tests that don't lock up with the gui event
178 # loops in the picture
178 # loops in the picture
179 sec.exclude('inputhook')
179 sec.exclude('inputhook')
180
180
181 # testing:
181 # testing:
182 sec = test_sections['testing']
182 sec = test_sections['testing']
183 # These have to be skipped on win32 because they use echo, rm, cd, etc.
183 # These have to be skipped on win32 because they use echo, rm, cd, etc.
184 # See ticket https://github.com/ipython/ipython/issues/87
184 # See ticket https://github.com/ipython/ipython/issues/87
185 if sys.platform == 'win32':
185 if sys.platform == 'win32':
186 sec.exclude('plugin.test_exampleip')
186 sec.exclude('plugin.test_exampleip')
187 sec.exclude('plugin.dtexample')
187 sec.exclude('plugin.dtexample')
188
188
189 # don't run jupyter_console tests found via shim
189 # don't run jupyter_console tests found via shim
190 test_sections['terminal'].exclude('console')
190 test_sections['terminal'].exclude('console')
191
191
192 # extensions:
192 # extensions:
193 sec = test_sections['extensions']
193 sec = test_sections['extensions']
194 # This is deprecated in favour of rpy2
194 # This is deprecated in favour of rpy2
195 sec.exclude('rmagic')
195 sec.exclude('rmagic')
196 # autoreload does some strange stuff, so move it to its own test section
196 # autoreload does some strange stuff, so move it to its own test section
197 sec.exclude('autoreload')
197 sec.exclude('autoreload')
198 sec.exclude('tests.test_autoreload')
198 sec.exclude('tests.test_autoreload')
199 test_sections['autoreload'] = TestSection('autoreload',
199 test_sections['autoreload'] = TestSection('autoreload',
200 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
200 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
201 test_group_names.append('autoreload')
201 test_group_names.append('autoreload')
202
202
203
203
204 #-----------------------------------------------------------------------------
204 #-----------------------------------------------------------------------------
205 # Functions and classes
205 # Functions and classes
206 #-----------------------------------------------------------------------------
206 #-----------------------------------------------------------------------------
207
207
208 def check_exclusions_exist():
208 def check_exclusions_exist():
209 from IPython.paths import get_ipython_package_dir
209 from IPython.paths import get_ipython_package_dir
210 from warnings import warn
210 from warnings import warn
211 parent = os.path.dirname(get_ipython_package_dir())
211 parent = os.path.dirname(get_ipython_package_dir())
212 for sec in test_sections:
212 for sec in test_sections:
213 for pattern in sec.exclusions:
213 for pattern in sec.exclusions:
214 fullpath = pjoin(parent, pattern)
214 fullpath = pjoin(parent, pattern)
215 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
215 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
216 warn("Excluding nonexistent file: %r" % pattern)
216 warn("Excluding nonexistent file: %r" % pattern)
217
217
218
218
219 class ExclusionPlugin(Plugin):
219 class ExclusionPlugin(Plugin):
220 """A nose plugin to effect our exclusions of files and directories.
220 """A nose plugin to effect our exclusions of files and directories.
221 """
221 """
222 name = 'exclusions'
222 name = 'exclusions'
223 score = 3000 # Should come before any other plugins
223 score = 3000 # Should come before any other plugins
224
224
225 def __init__(self, exclude_patterns=None):
225 def __init__(self, exclude_patterns=None):
226 """
226 """
227 Parameters
227 Parameters
228 ----------
228 ----------
229
229
230 exclude_patterns : sequence of strings, optional
230 exclude_patterns : sequence of strings, optional
231 Filenames containing these patterns (as raw strings, not as regular
231 Filenames containing these patterns (as raw strings, not as regular
232 expressions) are excluded from the tests.
232 expressions) are excluded from the tests.
233 """
233 """
234 self.exclude_patterns = exclude_patterns or []
234 self.exclude_patterns = exclude_patterns or []
235 super(ExclusionPlugin, self).__init__()
235 super(ExclusionPlugin, self).__init__()
236
236
237 def options(self, parser, env=os.environ):
237 def options(self, parser, env=os.environ):
238 Plugin.options(self, parser, env)
238 Plugin.options(self, parser, env)
239
239
240 def configure(self, options, config):
240 def configure(self, options, config):
241 Plugin.configure(self, options, config)
241 Plugin.configure(self, options, config)
242 # Override nose trying to disable plugin.
242 # Override nose trying to disable plugin.
243 self.enabled = True
243 self.enabled = True
244
244
245 def wantFile(self, filename):
245 def wantFile(self, filename):
246 """Return whether the given filename should be scanned for tests.
246 """Return whether the given filename should be scanned for tests.
247 """
247 """
248 if any(pat in filename for pat in self.exclude_patterns):
248 if any(pat in filename for pat in self.exclude_patterns):
249 return False
249 return False
250 return None
250 return None
251
251
252 def wantDirectory(self, directory):
252 def wantDirectory(self, directory):
253 """Return whether the given directory should be scanned for tests.
253 """Return whether the given directory should be scanned for tests.
254 """
254 """
255 if any(pat in directory for pat in self.exclude_patterns):
255 if any(pat in directory for pat in self.exclude_patterns):
256 return False
256 return False
257 return None
257 return None
258
258
259
259
260 class StreamCapturer(Thread):
260 class StreamCapturer(Thread):
261 daemon = True # Don't hang if main thread crashes
261 daemon = True # Don't hang if main thread crashes
262 started = False
262 started = False
263 def __init__(self, echo=False):
263 def __init__(self, echo=False):
264 super(StreamCapturer, self).__init__()
264 super(StreamCapturer, self).__init__()
265 self.echo = echo
265 self.echo = echo
266 self.streams = []
266 self.streams = []
267 self.buffer = BytesIO()
267 self.buffer = BytesIO()
268 self.readfd, self.writefd = os.pipe()
268 self.readfd, self.writefd = os.pipe()
269 self.buffer_lock = Lock()
269 self.buffer_lock = Lock()
270 self.stop = Event()
270 self.stop = Event()
271
271
272 def run(self):
272 def run(self):
273 self.started = True
273 self.started = True
274
274
275 while not self.stop.is_set():
275 while not self.stop.is_set():
276 chunk = os.read(self.readfd, 1024)
276 chunk = os.read(self.readfd, 1024)
277
277
278 with self.buffer_lock:
278 with self.buffer_lock:
279 self.buffer.write(chunk)
279 self.buffer.write(chunk)
280 if self.echo:
280 if self.echo:
281 sys.stdout.write(bytes_to_str(chunk))
281 sys.stdout.write(bytes_to_str(chunk))
282
282
283 os.close(self.readfd)
283 os.close(self.readfd)
284 os.close(self.writefd)
284 os.close(self.writefd)
285
285
286 def reset_buffer(self):
286 def reset_buffer(self):
287 with self.buffer_lock:
287 with self.buffer_lock:
288 self.buffer.truncate(0)
288 self.buffer.truncate(0)
289 self.buffer.seek(0)
289 self.buffer.seek(0)
290
290
291 def get_buffer(self):
291 def get_buffer(self):
292 with self.buffer_lock:
292 with self.buffer_lock:
293 return self.buffer.getvalue()
293 return self.buffer.getvalue()
294
294
295 def ensure_started(self):
295 def ensure_started(self):
296 if not self.started:
296 if not self.started:
297 self.start()
297 self.start()
298
298
299 def halt(self):
299 def halt(self):
300 """Safely stop the thread."""
300 """Safely stop the thread."""
301 if not self.started:
301 if not self.started:
302 return
302 return
303
303
304 self.stop.set()
304 self.stop.set()
305 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
305 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
306 self.join()
306 self.join()
307
307
308 class SubprocessStreamCapturePlugin(Plugin):
308 class SubprocessStreamCapturePlugin(Plugin):
309 name='subprocstreams'
309 name='subprocstreams'
310 def __init__(self):
310 def __init__(self):
311 Plugin.__init__(self)
311 Plugin.__init__(self)
312 self.stream_capturer = StreamCapturer()
312 self.stream_capturer = StreamCapturer()
313 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
313 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
314 # This is ugly, but distant parts of the test machinery need to be able
314 # This is ugly, but distant parts of the test machinery need to be able
315 # to redirect streams, so we make the object globally accessible.
315 # to redirect streams, so we make the object globally accessible.
316 nose.iptest_stdstreams_fileno = self.get_write_fileno
316 nose.iptest_stdstreams_fileno = self.get_write_fileno
317
317
318 def get_write_fileno(self):
318 def get_write_fileno(self):
319 if self.destination == 'capture':
319 if self.destination == 'capture':
320 self.stream_capturer.ensure_started()
320 self.stream_capturer.ensure_started()
321 return self.stream_capturer.writefd
321 return self.stream_capturer.writefd
322 elif self.destination == 'discard':
322 elif self.destination == 'discard':
323 return os.open(os.devnull, os.O_WRONLY)
323 return os.open(os.devnull, os.O_WRONLY)
324 else:
324 else:
325 return sys.__stdout__.fileno()
325 return sys.__stdout__.fileno()
326
326
327 def configure(self, options, config):
327 def configure(self, options, config):
328 Plugin.configure(self, options, config)
328 Plugin.configure(self, options, config)
329 # Override nose trying to disable plugin.
329 # Override nose trying to disable plugin.
330 if self.destination == 'capture':
330 if self.destination == 'capture':
331 self.enabled = True
331 self.enabled = True
332
332
333 def startTest(self, test):
333 def startTest(self, test):
334 # Reset log capture
334 # Reset log capture
335 self.stream_capturer.reset_buffer()
335 self.stream_capturer.reset_buffer()
336
336
337 def formatFailure(self, test, err):
337 def formatFailure(self, test, err):
338 # Show output
338 # Show output
339 ec, ev, tb = err
339 ec, ev, tb = err
340 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
340 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
341 if captured.strip():
341 if captured.strip():
342 ev = safe_str(ev)
342 ev = safe_str(ev)
343 out = [ev, '>> begin captured subprocess output <<',
343 out = [ev, '>> begin captured subprocess output <<',
344 captured,
344 captured,
345 '>> end captured subprocess output <<']
345 '>> end captured subprocess output <<']
346 return ec, '\n'.join(out), tb
346 return ec, '\n'.join(out), tb
347
347
348 return err
348 return err
349
349
350 formatError = formatFailure
350 formatError = formatFailure
351
351
352 def finalize(self, result):
352 def finalize(self, result):
353 self.stream_capturer.halt()
353 self.stream_capturer.halt()
354
354
355
355
356 def run_iptest():
356 def run_iptest():
357 """Run the IPython test suite using nose.
357 """Run the IPython test suite using nose.
358
358
359 This function is called when this script is **not** called with the form
359 This function is called when this script is **not** called with the form
360 `iptest all`. It simply calls nose with appropriate command line flags
360 `iptest all`. It simply calls nose with appropriate command line flags
361 and accepts all of the standard nose arguments.
361 and accepts all of the standard nose arguments.
362 """
362 """
363 # Apply our monkeypatch to Xunit
363 # Apply our monkeypatch to Xunit
364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
364 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
365 monkeypatch_xunit()
365 monkeypatch_xunit()
366
366
367 arg1 = sys.argv[1]
367 arg1 = sys.argv[1]
368 if arg1 in test_sections:
368 if arg1 in test_sections:
369 section = test_sections[arg1]
369 section = test_sections[arg1]
370 sys.argv[1:2] = section.includes
370 sys.argv[1:2] = section.includes
371 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
371 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
372 section = test_sections[arg1[8:]]
372 section = test_sections[arg1[8:]]
373 sys.argv[1:2] = section.includes
373 sys.argv[1:2] = section.includes
374 else:
374 else:
375 section = TestSection(arg1, includes=[arg1])
375 section = TestSection(arg1, includes=[arg1])
376
376
377
377
378 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
378 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
379 # We add --exe because of setuptools' imbecility (it
379 # We add --exe because of setuptools' imbecility (it
380 # blindly does chmod +x on ALL files). Nose does the
380 # blindly does chmod +x on ALL files). Nose does the
381 # right thing and it tries to avoid executables,
381 # right thing and it tries to avoid executables,
382 # setuptools unfortunately forces our hand here. This
382 # setuptools unfortunately forces our hand here. This
383 # has been discussed on the distutils list and the
383 # has been discussed on the distutils list and the
384 # setuptools devs refuse to fix this problem!
384 # setuptools devs refuse to fix this problem!
385 '--exe',
385 '--exe',
386 ]
386 ]
387 if '-a' not in argv and '-A' not in argv:
387 if '-a' not in argv and '-A' not in argv:
388 argv = argv + ['-a', '!crash']
388 argv = argv + ['-a', '!crash']
389
389
390 if nose.__version__ >= '0.11':
390 if nose.__version__ >= '0.11':
391 # I don't fully understand why we need this one, but depending on what
391 # I don't fully understand why we need this one, but depending on what
392 # directory the test suite is run from, if we don't give it, 0 tests
392 # directory the test suite is run from, if we don't give it, 0 tests
393 # get run. Specifically, if the test suite is run from the source dir
393 # get run. Specifically, if the test suite is run from the source dir
394 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
394 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
395 # even if the same call done in this directory works fine). It appears
395 # even if the same call done in this directory works fine). It appears
396 # that if the requested package is in the current dir, nose bails early
396 # that if the requested package is in the current dir, nose bails early
397 # by default. Since it's otherwise harmless, leave it in by default
397 # by default. Since it's otherwise harmless, leave it in by default
398 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
398 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
399 argv.append('--traverse-namespace')
399 argv.append('--traverse-namespace')
400
400
401 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
401 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
402 SubprocessStreamCapturePlugin() ]
402 SubprocessStreamCapturePlugin() ]
403
403
404 # we still have some vestigial doctests in core
404 # we still have some vestigial doctests in core
405 if (section.name.startswith(('core', 'IPython.core'))):
405 if (section.name.startswith(('core', 'IPython.core', 'IPython.utils'))):
406 plugins.append(IPythonDoctest())
406 plugins.append(IPythonDoctest())
407 argv.extend([
407 argv.extend([
408 '--with-ipdoctest',
408 '--with-ipdoctest',
409 '--ipdoctest-tests',
409 '--ipdoctest-tests',
410 '--ipdoctest-extension=txt',
410 '--ipdoctest-extension=txt',
411 ])
411 ])
412
412
413
413
414 # Use working directory set by parent process (see iptestcontroller)
414 # Use working directory set by parent process (see iptestcontroller)
415 if 'IPTEST_WORKING_DIR' in os.environ:
415 if 'IPTEST_WORKING_DIR' in os.environ:
416 os.chdir(os.environ['IPTEST_WORKING_DIR'])
416 os.chdir(os.environ['IPTEST_WORKING_DIR'])
417
417
418 # We need a global ipython running in this process, but the special
418 # We need a global ipython running in this process, but the special
419 # in-process group spawns its own IPython kernels, so for *that* group we
419 # in-process group spawns its own IPython kernels, so for *that* group we
420 # must avoid also opening the global one (otherwise there's a conflict of
420 # must avoid also opening the global one (otherwise there's a conflict of
421 # singletons). Ultimately the solution to this problem is to refactor our
421 # singletons). Ultimately the solution to this problem is to refactor our
422 # assumptions about what needs to be a singleton and what doesn't (app
422 # assumptions about what needs to be a singleton and what doesn't (app
423 # objects should, individual shells shouldn't). But for now, this
423 # objects should, individual shells shouldn't). But for now, this
424 # workaround allows the test suite for the inprocess module to complete.
424 # workaround allows the test suite for the inprocess module to complete.
425 if 'kernel.inprocess' not in section.name:
425 if 'kernel.inprocess' not in section.name:
426 from IPython.testing import globalipapp
426 from IPython.testing import globalipapp
427 globalipapp.start_ipython()
427 globalipapp.start_ipython()
428
428
429 # Now nose can run
429 # Now nose can run
430 TestProgram(argv=argv, addplugins=plugins)
430 TestProgram(argv=argv, addplugins=plugins)
431
431
432 if __name__ == '__main__':
432 if __name__ == '__main__':
433 run_iptest()
433 run_iptest()
General Comments 0
You need to be logged in to leave comments. Login now