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