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