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