##// END OF EJS Templates
remove a few more obsolete twisted notes
MinRK -
Show More
@@ -1,523 +1,520 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 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 # Stdlib
28 # Stdlib
29 import os
29 import os
30 import os.path as path
30 import os.path as path
31 import signal
31 import signal
32 import sys
32 import sys
33 import subprocess
33 import subprocess
34 import tempfile
34 import tempfile
35 import time
35 import time
36 import warnings
36 import warnings
37
37
38 # Note: monkeypatch!
38 # Note: monkeypatch!
39 # We need to monkeypatch a small problem in nose itself first, before importing
39 # We need to monkeypatch a small problem in nose itself first, before importing
40 # it for actual use. This should get into nose upstream, but its release cycle
40 # it for actual use. This should get into nose upstream, but its release cycle
41 # is slow and we need it for our parametric tests to work correctly.
41 # is slow and we need it for our parametric tests to work correctly.
42 from IPython.testing import nosepatch
42 from IPython.testing import nosepatch
43 # Now, proceed to import nose itself
43 # Now, proceed to import nose itself
44 import nose.plugins.builtin
44 import nose.plugins.builtin
45 from nose.plugins.xunit import Xunit
45 from nose.plugins.xunit import Xunit
46 from nose import SkipTest
46 from nose import SkipTest
47 from nose.core import TestProgram
47 from nose.core import TestProgram
48
48
49 # Our own imports
49 # Our own imports
50 from IPython.utils.importstring import import_item
50 from IPython.utils.importstring import import_item
51 from IPython.utils.path import get_ipython_module_path
51 from IPython.utils.path import get_ipython_module_path
52 from IPython.utils.process import find_cmd, pycmd2argv
52 from IPython.utils.process import find_cmd, pycmd2argv
53 from IPython.utils.sysinfo import sys_info
53 from IPython.utils.sysinfo import sys_info
54
54
55 from IPython.testing import globalipapp
55 from IPython.testing import globalipapp
56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57 from IPython.external.decorators import KnownFailure, knownfailureif
57 from IPython.external.decorators import KnownFailure, knownfailureif
58
58
59 pjoin = path.join
59 pjoin = path.join
60
60
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Globals
63 # Globals
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Warnings control
68 # Warnings control
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 # Twisted generates annoying warnings with Python 2.6, as will do other code
71 # Twisted generates annoying warnings with Python 2.6, as will do other code
72 # that imports 'sets' as of today
72 # that imports 'sets' as of today
73 warnings.filterwarnings('ignore', 'the sets module is deprecated',
73 warnings.filterwarnings('ignore', 'the sets module is deprecated',
74 DeprecationWarning )
74 DeprecationWarning )
75
75
76 # This one also comes from Twisted
76 # This one also comes from Twisted
77 warnings.filterwarnings('ignore', 'the sha module is deprecated',
77 warnings.filterwarnings('ignore', 'the sha module is deprecated',
78 DeprecationWarning)
78 DeprecationWarning)
79
79
80 # Wx on Fedora11 spits these out
80 # Wx on Fedora11 spits these out
81 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
81 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
82 UserWarning)
82 UserWarning)
83
83
84 # ------------------------------------------------------------------------------
84 # ------------------------------------------------------------------------------
85 # Monkeypatch Xunit to count known failures as skipped.
85 # Monkeypatch Xunit to count known failures as skipped.
86 # ------------------------------------------------------------------------------
86 # ------------------------------------------------------------------------------
87 def monkeypatch_xunit():
87 def monkeypatch_xunit():
88 try:
88 try:
89 knownfailureif(True)(lambda: None)()
89 knownfailureif(True)(lambda: None)()
90 except Exception as e:
90 except Exception as e:
91 KnownFailureTest = type(e)
91 KnownFailureTest = type(e)
92
92
93 def addError(self, test, err, capt=None):
93 def addError(self, test, err, capt=None):
94 if issubclass(err[0], KnownFailureTest):
94 if issubclass(err[0], KnownFailureTest):
95 err = (SkipTest,) + err[1:]
95 err = (SkipTest,) + err[1:]
96 return self.orig_addError(test, err, capt)
96 return self.orig_addError(test, err, capt)
97
97
98 Xunit.orig_addError = Xunit.addError
98 Xunit.orig_addError = Xunit.addError
99 Xunit.addError = addError
99 Xunit.addError = addError
100
100
101 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
102 # Logic for skipping doctests
102 # Logic for skipping doctests
103 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
104 def extract_version(mod):
104 def extract_version(mod):
105 return mod.__version__
105 return mod.__version__
106
106
107 def test_for(item, min_version=None, callback=extract_version):
107 def test_for(item, min_version=None, callback=extract_version):
108 """Test to see if item is importable, and optionally check against a minimum
108 """Test to see if item is importable, and optionally check against a minimum
109 version.
109 version.
110
110
111 If min_version is given, the default behavior is to check against the
111 If min_version is given, the default behavior is to check against the
112 `__version__` attribute of the item, but specifying `callback` allows you to
112 `__version__` attribute of the item, but specifying `callback` allows you to
113 extract the value you are interested in. e.g::
113 extract the value you are interested in. e.g::
114
114
115 In [1]: import sys
115 In [1]: import sys
116
116
117 In [2]: from IPython.testing.iptest import test_for
117 In [2]: from IPython.testing.iptest import test_for
118
118
119 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
119 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
120 Out[3]: True
120 Out[3]: True
121
121
122 """
122 """
123 try:
123 try:
124 check = import_item(item)
124 check = import_item(item)
125 except (ImportError, RuntimeError):
125 except (ImportError, RuntimeError):
126 # GTK reports Runtime error if it can't be initialized even if it's
126 # GTK reports Runtime error if it can't be initialized even if it's
127 # importable.
127 # importable.
128 return False
128 return False
129 else:
129 else:
130 if min_version:
130 if min_version:
131 if callback:
131 if callback:
132 # extra processing step to get version to compare
132 # extra processing step to get version to compare
133 check = callback(check)
133 check = callback(check)
134
134
135 return check >= min_version
135 return check >= min_version
136 else:
136 else:
137 return True
137 return True
138
138
139 # Global dict where we can store information on what we have and what we don't
139 # Global dict where we can store information on what we have and what we don't
140 # have available at test run time
140 # have available at test run time
141 have = {}
141 have = {}
142
142
143 have['curses'] = test_for('_curses')
143 have['curses'] = test_for('_curses')
144 have['matplotlib'] = test_for('matplotlib')
144 have['matplotlib'] = test_for('matplotlib')
145 have['pexpect'] = test_for('IPython.external.pexpect')
145 have['pexpect'] = test_for('IPython.external.pexpect')
146 have['pymongo'] = test_for('pymongo')
146 have['pymongo'] = test_for('pymongo')
147 have['wx'] = test_for('wx')
147 have['wx'] = test_for('wx')
148 have['wx.aui'] = test_for('wx.aui')
148 have['wx.aui'] = test_for('wx.aui')
149 have['qt'] = test_for('IPython.external.qt')
149 have['qt'] = test_for('IPython.external.qt')
150 have['sqlite3'] = test_for('sqlite3')
150 have['sqlite3'] = test_for('sqlite3')
151
151
152 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
152 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
153
153
154 if os.name == 'nt':
154 if os.name == 'nt':
155 min_zmq = (2,1,7)
155 min_zmq = (2,1,7)
156 else:
156 else:
157 min_zmq = (2,1,4)
157 min_zmq = (2,1,4)
158
158
159 def version_tuple(mod):
159 def version_tuple(mod):
160 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
160 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
161 # turn 'dev' into 999, because Python3 rejects str-int comparisons
161 # turn 'dev' into 999, because Python3 rejects str-int comparisons
162 vs = mod.__version__.replace('dev', '.999')
162 vs = mod.__version__.replace('dev', '.999')
163 tup = tuple([int(v) for v in vs.split('.') ])
163 tup = tuple([int(v) for v in vs.split('.') ])
164 return tup
164 return tup
165
165
166 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
166 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
167
167
168 #-----------------------------------------------------------------------------
168 #-----------------------------------------------------------------------------
169 # Functions and classes
169 # Functions and classes
170 #-----------------------------------------------------------------------------
170 #-----------------------------------------------------------------------------
171
171
172 def report():
172 def report():
173 """Return a string with a summary report of test-related variables."""
173 """Return a string with a summary report of test-related variables."""
174
174
175 out = [ sys_info(), '\n']
175 out = [ sys_info(), '\n']
176
176
177 avail = []
177 avail = []
178 not_avail = []
178 not_avail = []
179
179
180 for k, is_avail in have.items():
180 for k, is_avail in have.items():
181 if is_avail:
181 if is_avail:
182 avail.append(k)
182 avail.append(k)
183 else:
183 else:
184 not_avail.append(k)
184 not_avail.append(k)
185
185
186 if avail:
186 if avail:
187 out.append('\nTools and libraries available at test time:\n')
187 out.append('\nTools and libraries available at test time:\n')
188 avail.sort()
188 avail.sort()
189 out.append(' ' + ' '.join(avail)+'\n')
189 out.append(' ' + ' '.join(avail)+'\n')
190
190
191 if not_avail:
191 if not_avail:
192 out.append('\nTools and libraries NOT available at test time:\n')
192 out.append('\nTools and libraries NOT available at test time:\n')
193 not_avail.sort()
193 not_avail.sort()
194 out.append(' ' + ' '.join(not_avail)+'\n')
194 out.append(' ' + ' '.join(not_avail)+'\n')
195
195
196 return ''.join(out)
196 return ''.join(out)
197
197
198
198
199 def make_exclude():
199 def make_exclude():
200 """Make patterns of modules and packages to exclude from testing.
200 """Make patterns of modules and packages to exclude from testing.
201
201
202 For the IPythonDoctest plugin, we need to exclude certain patterns that
202 For the IPythonDoctest plugin, we need to exclude certain patterns that
203 cause testing problems. We should strive to minimize the number of
203 cause testing problems. We should strive to minimize the number of
204 skipped modules, since this means untested code.
204 skipped modules, since this means untested code.
205
205
206 These modules and packages will NOT get scanned by nose at all for tests.
206 These modules and packages will NOT get scanned by nose at all for tests.
207 """
207 """
208 # Simple utility to make IPython paths more readably, we need a lot of
208 # Simple utility to make IPython paths more readably, we need a lot of
209 # these below
209 # these below
210 ipjoin = lambda *paths: pjoin('IPython', *paths)
210 ipjoin = lambda *paths: pjoin('IPython', *paths)
211
211
212 exclusions = [ipjoin('external'),
212 exclusions = [ipjoin('external'),
213 pjoin('IPython_doctest_plugin'),
213 pjoin('IPython_doctest_plugin'),
214 ipjoin('quarantine'),
214 ipjoin('quarantine'),
215 ipjoin('deathrow'),
215 ipjoin('deathrow'),
216 ipjoin('testing', 'attic'),
216 ipjoin('testing', 'attic'),
217 # This guy is probably attic material
217 # This guy is probably attic material
218 ipjoin('testing', 'mkdoctests'),
218 ipjoin('testing', 'mkdoctests'),
219 # Testing inputhook will need a lot of thought, to figure out
219 # Testing inputhook will need a lot of thought, to figure out
220 # how to have tests that don't lock up with the gui event
220 # how to have tests that don't lock up with the gui event
221 # loops in the picture
221 # loops in the picture
222 ipjoin('lib', 'inputhook'),
222 ipjoin('lib', 'inputhook'),
223 # Config files aren't really importable stand-alone
223 # Config files aren't really importable stand-alone
224 ipjoin('config', 'default'),
224 ipjoin('config', 'default'),
225 ipjoin('config', 'profile'),
225 ipjoin('config', 'profile'),
226 ]
226 ]
227 if not have['sqlite3']:
227 if not have['sqlite3']:
228 exclusions.append(ipjoin('core', 'tests', 'test_history'))
228 exclusions.append(ipjoin('core', 'tests', 'test_history'))
229 exclusions.append(ipjoin('core', 'history'))
229 exclusions.append(ipjoin('core', 'history'))
230 if not have['wx']:
230 if not have['wx']:
231 exclusions.append(ipjoin('lib', 'inputhookwx'))
231 exclusions.append(ipjoin('lib', 'inputhookwx'))
232
232
233 # We do this unconditionally, so that the test suite doesn't import
233 # We do this unconditionally, so that the test suite doesn't import
234 # gtk, changing the default encoding and masking some unicode bugs.
234 # gtk, changing the default encoding and masking some unicode bugs.
235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
236
236
237 # These have to be skipped on win32 because the use echo, rm, cd, etc.
237 # These have to be skipped on win32 because the use echo, rm, cd, etc.
238 # See ticket https://github.com/ipython/ipython/issues/87
238 # See ticket https://github.com/ipython/ipython/issues/87
239 if sys.platform == 'win32':
239 if sys.platform == 'win32':
240 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
240 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
241 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
241 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
242
242
243 if not have['pexpect']:
243 if not have['pexpect']:
244 exclusions.extend([ipjoin('scripts', 'irunner'),
244 exclusions.extend([ipjoin('scripts', 'irunner'),
245 ipjoin('lib', 'irunner'),
245 ipjoin('lib', 'irunner'),
246 ipjoin('lib', 'tests', 'test_irunner'),
246 ipjoin('lib', 'tests', 'test_irunner'),
247 ipjoin('frontend', 'terminal', 'console'),
247 ipjoin('frontend', 'terminal', 'console'),
248 ])
248 ])
249
249
250 if not have['zmq']:
250 if not have['zmq']:
251 exclusions.append(ipjoin('zmq'))
251 exclusions.append(ipjoin('zmq'))
252 exclusions.append(ipjoin('frontend', 'qt'))
252 exclusions.append(ipjoin('frontend', 'qt'))
253 exclusions.append(ipjoin('frontend', 'html'))
253 exclusions.append(ipjoin('frontend', 'html'))
254 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
254 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
255 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
255 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
256 exclusions.append(ipjoin('parallel'))
256 exclusions.append(ipjoin('parallel'))
257 elif not have['qt']:
257 elif not have['qt']:
258 exclusions.append(ipjoin('frontend', 'qt'))
258 exclusions.append(ipjoin('frontend', 'qt'))
259
259
260 if not have['pymongo']:
260 if not have['pymongo']:
261 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
261 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
262 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
262 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
263
263
264 if not have['matplotlib']:
264 if not have['matplotlib']:
265 exclusions.extend([ipjoin('core', 'pylabtools'),
265 exclusions.extend([ipjoin('core', 'pylabtools'),
266 ipjoin('core', 'tests', 'test_pylabtools')])
266 ipjoin('core', 'tests', 'test_pylabtools')])
267
267
268 if not have['tornado']:
268 if not have['tornado']:
269 exclusions.append(ipjoin('frontend', 'html'))
269 exclusions.append(ipjoin('frontend', 'html'))
270
270
271 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
271 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
272 if sys.platform == 'win32':
272 if sys.platform == 'win32':
273 exclusions = [s.replace('\\','\\\\') for s in exclusions]
273 exclusions = [s.replace('\\','\\\\') for s in exclusions]
274
274
275 return exclusions
275 return exclusions
276
276
277
277
278 class IPTester(object):
278 class IPTester(object):
279 """Call that calls iptest or trial in a subprocess.
279 """Call that calls iptest or trial in a subprocess.
280 """
280 """
281 #: string, name of test runner that will be called
281 #: string, name of test runner that will be called
282 runner = None
282 runner = None
283 #: list, parameters for test runner
283 #: list, parameters for test runner
284 params = None
284 params = None
285 #: list, arguments of system call to be made to call test runner
285 #: list, arguments of system call to be made to call test runner
286 call_args = None
286 call_args = None
287 #: list, process ids of subprocesses we start (for cleanup)
287 #: list, process ids of subprocesses we start (for cleanup)
288 pids = None
288 pids = None
289 #: str, coverage xml output file
289 #: str, coverage xml output file
290 coverage_xml = None
290 coverage_xml = None
291
291
292 def __init__(self, runner='iptest', params=None):
292 def __init__(self, runner='iptest', params=None):
293 """Create new test runner."""
293 """Create new test runner."""
294 p = os.path
294 p = os.path
295 if runner == 'iptest':
295 if runner == 'iptest':
296 iptest_app = get_ipython_module_path('IPython.testing.iptest')
296 iptest_app = get_ipython_module_path('IPython.testing.iptest')
297 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
297 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
298 else:
298 else:
299 raise Exception('Not a valid test runner: %s' % repr(runner))
299 raise Exception('Not a valid test runner: %s' % repr(runner))
300 if params is None:
300 if params is None:
301 params = []
301 params = []
302 if isinstance(params, str):
302 if isinstance(params, str):
303 params = [params]
303 params = [params]
304 self.params = params
304 self.params = params
305
305
306 # Assemble call
306 # Assemble call
307 self.call_args = self.runner+self.params
307 self.call_args = self.runner+self.params
308
308
309 # Find the section we're testing (IPython.foo)
309 # Find the section we're testing (IPython.foo)
310 for sect in self.params:
310 for sect in self.params:
311 if sect.startswith('IPython'): break
311 if sect.startswith('IPython'): break
312 else:
312 else:
313 raise ValueError("Section not found", self.params)
313 raise ValueError("Section not found", self.params)
314
314
315 if '--with-xunit' in self.call_args:
315 if '--with-xunit' in self.call_args:
316 self.call_args.append('--xunit-file=%s' % path.abspath(sect+'.xunit.xml'))
316 self.call_args.append('--xunit-file=%s' % path.abspath(sect+'.xunit.xml'))
317
317
318 if '--with-xml-coverage' in self.call_args:
318 if '--with-xml-coverage' in self.call_args:
319 self.coverage_xml = path.abspath(sect+".coverage.xml")
319 self.coverage_xml = path.abspath(sect+".coverage.xml")
320 self.call_args.remove('--with-xml-coverage')
320 self.call_args.remove('--with-xml-coverage')
321 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
321 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
322
322
323 # Store pids of anything we start to clean up on deletion, if possible
323 # Store pids of anything we start to clean up on deletion, if possible
324 # (on posix only, since win32 has no os.kill)
324 # (on posix only, since win32 has no os.kill)
325 self.pids = []
325 self.pids = []
326
326
327 if sys.platform == 'win32':
327 if sys.platform == 'win32':
328 def _run_cmd(self):
328 def _run_cmd(self):
329 # On Windows, use os.system instead of subprocess.call, because I
329 # On Windows, use os.system instead of subprocess.call, because I
330 # was having problems with subprocess and I just don't know enough
330 # was having problems with subprocess and I just don't know enough
331 # about win32 to debug this reliably. Os.system may be the 'old
331 # about win32 to debug this reliably. Os.system may be the 'old
332 # fashioned' way to do it, but it works just fine. If someone
332 # fashioned' way to do it, but it works just fine. If someone
333 # later can clean this up that's fine, as long as the tests run
333 # later can clean this up that's fine, as long as the tests run
334 # reliably in win32.
334 # reliably in win32.
335 # What types of problems are you having. They may be related to
335 # What types of problems are you having. They may be related to
336 # running Python in unboffered mode. BG.
336 # running Python in unboffered mode. BG.
337 return os.system(' '.join(self.call_args))
337 return os.system(' '.join(self.call_args))
338 else:
338 else:
339 def _run_cmd(self):
339 def _run_cmd(self):
340 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
340 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
341 subp = subprocess.Popen(self.call_args)
341 subp = subprocess.Popen(self.call_args)
342 self.pids.append(subp.pid)
342 self.pids.append(subp.pid)
343 # If this fails, the pid will be left in self.pids and cleaned up
343 # If this fails, the pid will be left in self.pids and cleaned up
344 # later, but if the wait call succeeds, then we can clear the
344 # later, but if the wait call succeeds, then we can clear the
345 # stored pid.
345 # stored pid.
346 retcode = subp.wait()
346 retcode = subp.wait()
347 self.pids.pop()
347 self.pids.pop()
348 return retcode
348 return retcode
349
349
350 def run(self):
350 def run(self):
351 """Run the stored commands"""
351 """Run the stored commands"""
352 try:
352 try:
353 retcode = self._run_cmd()
353 retcode = self._run_cmd()
354 except:
354 except:
355 import traceback
355 import traceback
356 traceback.print_exc()
356 traceback.print_exc()
357 return 1 # signal failure
357 return 1 # signal failure
358
358
359 if self.coverage_xml:
359 if self.coverage_xml:
360 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
360 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
361 return retcode
361 return retcode
362
362
363 def __del__(self):
363 def __del__(self):
364 """Cleanup on exit by killing any leftover processes."""
364 """Cleanup on exit by killing any leftover processes."""
365
365
366 if not hasattr(os, 'kill'):
366 if not hasattr(os, 'kill'):
367 return
367 return
368
368
369 for pid in self.pids:
369 for pid in self.pids:
370 try:
370 try:
371 print 'Cleaning stale PID:', pid
371 print 'Cleaning stale PID:', pid
372 os.kill(pid, signal.SIGKILL)
372 os.kill(pid, signal.SIGKILL)
373 except OSError:
373 except OSError:
374 # This is just a best effort, if we fail or the process was
374 # This is just a best effort, if we fail or the process was
375 # really gone, ignore it.
375 # really gone, ignore it.
376 pass
376 pass
377
377
378
378
379 def make_runners():
379 def make_runners():
380 """Define the top-level packages that need to be tested.
380 """Define the top-level packages that need to be tested.
381 """
381 """
382
382
383 # Packages to be tested via nose, that only depend on the stdlib
383 # Packages to be tested via nose, that only depend on the stdlib
384 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
384 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
385 'scripts', 'testing', 'utils', 'nbformat' ]
385 'scripts', 'testing', 'utils', 'nbformat' ]
386
386
387 if have['zmq']:
387 if have['zmq']:
388 nose_pkg_names.append('parallel')
388 nose_pkg_names.append('parallel')
389
389
390 # For debugging this code, only load quick stuff
390 # For debugging this code, only load quick stuff
391 #nose_pkg_names = ['core', 'extensions'] # dbg
391 #nose_pkg_names = ['core', 'extensions'] # dbg
392
392
393 # Make fully qualified package names prepending 'IPython.' to our name lists
393 # Make fully qualified package names prepending 'IPython.' to our name lists
394 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
394 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
395
395
396 # Make runners
396 # Make runners
397 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
397 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
398
398
399 return runners
399 return runners
400
400
401
401
402 def run_iptest():
402 def run_iptest():
403 """Run the IPython test suite using nose.
403 """Run the IPython test suite using nose.
404
404
405 This function is called when this script is **not** called with the form
405 This function is called when this script is **not** called with the form
406 `iptest all`. It simply calls nose with appropriate command line flags
406 `iptest all`. It simply calls nose with appropriate command line flags
407 and accepts all of the standard nose arguments.
407 and accepts all of the standard nose arguments.
408 """
408 """
409 # Apply our monkeypatch to Xunit
409 # Apply our monkeypatch to Xunit
410 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
410 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
411 monkeypatch_xunit()
411 monkeypatch_xunit()
412
412
413 warnings.filterwarnings('ignore',
413 warnings.filterwarnings('ignore',
414 'This will be removed soon. Use IPython.testing.util instead')
414 'This will be removed soon. Use IPython.testing.util instead')
415
415
416 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
416 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
417
417
418 # Loading ipdoctest causes problems with Twisted, but
419 # our test suite runner now separates things and runs
420 # all Twisted tests with trial.
421 '--with-ipdoctest',
418 '--with-ipdoctest',
422 '--ipdoctest-tests','--ipdoctest-extension=txt',
419 '--ipdoctest-tests','--ipdoctest-extension=txt',
423
420
424 # We add --exe because of setuptools' imbecility (it
421 # We add --exe because of setuptools' imbecility (it
425 # blindly does chmod +x on ALL files). Nose does the
422 # blindly does chmod +x on ALL files). Nose does the
426 # right thing and it tries to avoid executables,
423 # right thing and it tries to avoid executables,
427 # setuptools unfortunately forces our hand here. This
424 # setuptools unfortunately forces our hand here. This
428 # has been discussed on the distutils list and the
425 # has been discussed on the distutils list and the
429 # setuptools devs refuse to fix this problem!
426 # setuptools devs refuse to fix this problem!
430 '--exe',
427 '--exe',
431 ]
428 ]
432
429
433 if nose.__version__ >= '0.11':
430 if nose.__version__ >= '0.11':
434 # I don't fully understand why we need this one, but depending on what
431 # I don't fully understand why we need this one, but depending on what
435 # directory the test suite is run from, if we don't give it, 0 tests
432 # directory the test suite is run from, if we don't give it, 0 tests
436 # get run. Specifically, if the test suite is run from the source dir
433 # get run. Specifically, if the test suite is run from the source dir
437 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
434 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
438 # even if the same call done in this directory works fine). It appears
435 # even if the same call done in this directory works fine). It appears
439 # that if the requested package is in the current dir, nose bails early
436 # that if the requested package is in the current dir, nose bails early
440 # by default. Since it's otherwise harmless, leave it in by default
437 # by default. Since it's otherwise harmless, leave it in by default
441 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
438 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
442 argv.append('--traverse-namespace')
439 argv.append('--traverse-namespace')
443
440
444 # use our plugin for doctesting. It will remove the standard doctest plugin
441 # use our plugin for doctesting. It will remove the standard doctest plugin
445 # if it finds it enabled
442 # if it finds it enabled
446 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
443 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
447 # We need a global ipython running in this process
444 # We need a global ipython running in this process
448 globalipapp.start_ipython()
445 globalipapp.start_ipython()
449 # Now nose can run
446 # Now nose can run
450 TestProgram(argv=argv, addplugins=plugins)
447 TestProgram(argv=argv, addplugins=plugins)
451
448
452
449
453 def run_iptestall():
450 def run_iptestall():
454 """Run the entire IPython test suite by calling nose and trial.
451 """Run the entire IPython test suite by calling nose and trial.
455
452
456 This function constructs :class:`IPTester` instances for all IPython
453 This function constructs :class:`IPTester` instances for all IPython
457 modules and package and then runs each of them. This causes the modules
454 modules and package and then runs each of them. This causes the modules
458 and packages of IPython to be tested each in their own subprocess using
455 and packages of IPython to be tested each in their own subprocess using
459 nose or twisted.trial appropriately.
456 nose.
460 """
457 """
461
458
462 runners = make_runners()
459 runners = make_runners()
463
460
464 # Run the test runners in a temporary dir so we can nuke it when finished
461 # Run the test runners in a temporary dir so we can nuke it when finished
465 # to clean up any junk files left over by accident. This also makes it
462 # to clean up any junk files left over by accident. This also makes it
466 # robust against being run in non-writeable directories by mistake, as the
463 # robust against being run in non-writeable directories by mistake, as the
467 # temp dir will always be user-writeable.
464 # temp dir will always be user-writeable.
468 curdir = os.getcwdu()
465 curdir = os.getcwdu()
469 testdir = tempfile.gettempdir()
466 testdir = tempfile.gettempdir()
470 os.chdir(testdir)
467 os.chdir(testdir)
471
468
472 # Run all test runners, tracking execution time
469 # Run all test runners, tracking execution time
473 failed = []
470 failed = []
474 t_start = time.time()
471 t_start = time.time()
475 try:
472 try:
476 for (name, runner) in runners:
473 for (name, runner) in runners:
477 print '*'*70
474 print '*'*70
478 print 'IPython test group:',name
475 print 'IPython test group:',name
479 res = runner.run()
476 res = runner.run()
480 if res:
477 if res:
481 failed.append( (name, runner) )
478 failed.append( (name, runner) )
482 finally:
479 finally:
483 os.chdir(curdir)
480 os.chdir(curdir)
484 t_end = time.time()
481 t_end = time.time()
485 t_tests = t_end - t_start
482 t_tests = t_end - t_start
486 nrunners = len(runners)
483 nrunners = len(runners)
487 nfail = len(failed)
484 nfail = len(failed)
488 # summarize results
485 # summarize results
489 print
486 print
490 print '*'*70
487 print '*'*70
491 print 'Test suite completed for system with the following information:'
488 print 'Test suite completed for system with the following information:'
492 print report()
489 print report()
493 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
490 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
494 print
491 print
495 print 'Status:'
492 print 'Status:'
496 if not failed:
493 if not failed:
497 print 'OK'
494 print 'OK'
498 else:
495 else:
499 # If anything went wrong, point out what command to rerun manually to
496 # If anything went wrong, point out what command to rerun manually to
500 # see the actual errors and individual summary
497 # see the actual errors and individual summary
501 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
498 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
502 for name, failed_runner in failed:
499 for name, failed_runner in failed:
503 print '-'*40
500 print '-'*40
504 print 'Runner failed:',name
501 print 'Runner failed:',name
505 print 'You may wish to rerun this one individually, with:'
502 print 'You may wish to rerun this one individually, with:'
506 print ' '.join(failed_runner.call_args)
503 print ' '.join(failed_runner.call_args)
507 print
504 print
508 # Ensure that our exit code indicates failure
505 # Ensure that our exit code indicates failure
509 sys.exit(1)
506 sys.exit(1)
510
507
511
508
512 def main():
509 def main():
513 for arg in sys.argv[1:]:
510 for arg in sys.argv[1:]:
514 if arg.startswith('IPython'):
511 if arg.startswith('IPython'):
515 # This is in-process
512 # This is in-process
516 run_iptest()
513 run_iptest()
517 else:
514 else:
518 # This starts subprocesses
515 # This starts subprocesses
519 run_iptestall()
516 run_iptestall()
520
517
521
518
522 if __name__ == '__main__':
519 if __name__ == '__main__':
523 main()
520 main()
@@ -1,396 +1,391 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools.
2
2
3 In particular, this module exposes a set of top-level assert* functions that
3 In particular, this module exposes a set of top-level assert* functions that
4 can be used in place of nose.tools.assert* in method generators (the ones in
4 can be used in place of nose.tools.assert* in method generators (the ones in
5 nose can not, at least as of nose 0.10.4).
5 nose can not, at least as of nose 0.10.4).
6
6
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
11
12
7
13 Authors
8 Authors
14 -------
9 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
10 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
11 """
17
12
18 from __future__ import absolute_import
13 from __future__ import absolute_import
19
14
20 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
21 # Copyright (C) 2009-2011 The IPython Development Team
16 # Copyright (C) 2009-2011 The IPython Development Team
22 #
17 #
23 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
25 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
26
21
27 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
28 # Imports
23 # Imports
29 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
30
25
31 import os
26 import os
32 import re
27 import re
33 import sys
28 import sys
34 import tempfile
29 import tempfile
35
30
36 from contextlib import contextmanager
31 from contextlib import contextmanager
37 from io import StringIO
32 from io import StringIO
38
33
39 try:
34 try:
40 # These tools are used by parts of the runtime, so we make the nose
35 # These tools are used by parts of the runtime, so we make the nose
41 # dependency optional at this point. Nose is a hard dependency to run the
36 # dependency optional at this point. Nose is a hard dependency to run the
42 # test suite, but NOT to use ipython itself.
37 # test suite, but NOT to use ipython itself.
43 import nose.tools as nt
38 import nose.tools as nt
44 has_nose = True
39 has_nose = True
45 except ImportError:
40 except ImportError:
46 has_nose = False
41 has_nose = False
47
42
48 from IPython.config.loader import Config
43 from IPython.config.loader import Config
49 from IPython.utils.process import find_cmd, getoutputerror
44 from IPython.utils.process import find_cmd, getoutputerror
50 from IPython.utils.text import list_strings, getdefaultencoding
45 from IPython.utils.text import list_strings, getdefaultencoding
51 from IPython.utils.io import temp_pyfile, Tee
46 from IPython.utils.io import temp_pyfile, Tee
52 from IPython.utils import py3compat
47 from IPython.utils import py3compat
53
48
54 from . import decorators as dec
49 from . import decorators as dec
55 from . import skipdoctest
50 from . import skipdoctest
56
51
57 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
58 # Globals
53 # Globals
59 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
60
55
61 # Make a bunch of nose.tools assert wrappers that can be used in test
56 # Make a bunch of nose.tools assert wrappers that can be used in test
62 # generators. This will expose an assert* function for each one in nose.tools.
57 # generators. This will expose an assert* function for each one in nose.tools.
63
58
64 _tpl = """
59 _tpl = """
65 def %(name)s(*a,**kw):
60 def %(name)s(*a,**kw):
66 return nt.%(name)s(*a,**kw)
61 return nt.%(name)s(*a,**kw)
67 """
62 """
68
63
69 if has_nose:
64 if has_nose:
70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
65 for _x in [a for a in dir(nt) if a.startswith('assert')]:
71 exec _tpl % dict(name=_x)
66 exec _tpl % dict(name=_x)
72
67
73 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
74 # Functions and classes
69 # Functions and classes
75 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
76
71
77 # The docstring for full_path doctests differently on win32 (different path
72 # The docstring for full_path doctests differently on win32 (different path
78 # separator) so just skip the doctest there. The example remains informative.
73 # separator) so just skip the doctest there. The example remains informative.
79 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
74 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
80
75
81 @doctest_deco
76 @doctest_deco
82 def full_path(startPath,files):
77 def full_path(startPath,files):
83 """Make full paths for all the listed files, based on startPath.
78 """Make full paths for all the listed files, based on startPath.
84
79
85 Only the base part of startPath is kept, since this routine is typically
80 Only the base part of startPath is kept, since this routine is typically
86 used with a script's __file__ variable as startPath. The base of startPath
81 used with a script's __file__ variable as startPath. The base of startPath
87 is then prepended to all the listed files, forming the output list.
82 is then prepended to all the listed files, forming the output list.
88
83
89 Parameters
84 Parameters
90 ----------
85 ----------
91 startPath : string
86 startPath : string
92 Initial path to use as the base for the results. This path is split
87 Initial path to use as the base for the results. This path is split
93 using os.path.split() and only its first component is kept.
88 using os.path.split() and only its first component is kept.
94
89
95 files : string or list
90 files : string or list
96 One or more files.
91 One or more files.
97
92
98 Examples
93 Examples
99 --------
94 --------
100
95
101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
96 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
102 ['/foo/a.txt', '/foo/b.txt']
97 ['/foo/a.txt', '/foo/b.txt']
103
98
104 >>> full_path('/foo',['a.txt','b.txt'])
99 >>> full_path('/foo',['a.txt','b.txt'])
105 ['/a.txt', '/b.txt']
100 ['/a.txt', '/b.txt']
106
101
107 If a single file is given, the output is still a list:
102 If a single file is given, the output is still a list:
108 >>> full_path('/foo','a.txt')
103 >>> full_path('/foo','a.txt')
109 ['/a.txt']
104 ['/a.txt']
110 """
105 """
111
106
112 files = list_strings(files)
107 files = list_strings(files)
113 base = os.path.split(startPath)[0]
108 base = os.path.split(startPath)[0]
114 return [ os.path.join(base,f) for f in files ]
109 return [ os.path.join(base,f) for f in files ]
115
110
116
111
117 def parse_test_output(txt):
112 def parse_test_output(txt):
118 """Parse the output of a test run and return errors, failures.
113 """Parse the output of a test run and return errors, failures.
119
114
120 Parameters
115 Parameters
121 ----------
116 ----------
122 txt : str
117 txt : str
123 Text output of a test run, assumed to contain a line of one of the
118 Text output of a test run, assumed to contain a line of one of the
124 following forms::
119 following forms::
125 'FAILED (errors=1)'
120 'FAILED (errors=1)'
126 'FAILED (failures=1)'
121 'FAILED (failures=1)'
127 'FAILED (errors=1, failures=1)'
122 'FAILED (errors=1, failures=1)'
128
123
129 Returns
124 Returns
130 -------
125 -------
131 nerr, nfail: number of errors and failures.
126 nerr, nfail: number of errors and failures.
132 """
127 """
133
128
134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
129 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
135 if err_m:
130 if err_m:
136 nerr = int(err_m.group(1))
131 nerr = int(err_m.group(1))
137 nfail = 0
132 nfail = 0
138 return nerr, nfail
133 return nerr, nfail
139
134
140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
135 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
141 if fail_m:
136 if fail_m:
142 nerr = 0
137 nerr = 0
143 nfail = int(fail_m.group(1))
138 nfail = int(fail_m.group(1))
144 return nerr, nfail
139 return nerr, nfail
145
140
146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
141 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
147 re.MULTILINE)
142 re.MULTILINE)
148 if both_m:
143 if both_m:
149 nerr = int(both_m.group(1))
144 nerr = int(both_m.group(1))
150 nfail = int(both_m.group(2))
145 nfail = int(both_m.group(2))
151 return nerr, nfail
146 return nerr, nfail
152
147
153 # If the input didn't match any of these forms, assume no error/failures
148 # If the input didn't match any of these forms, assume no error/failures
154 return 0, 0
149 return 0, 0
155
150
156
151
157 # So nose doesn't think this is a test
152 # So nose doesn't think this is a test
158 parse_test_output.__test__ = False
153 parse_test_output.__test__ = False
159
154
160
155
161 def default_argv():
156 def default_argv():
162 """Return a valid default argv for creating testing instances of ipython"""
157 """Return a valid default argv for creating testing instances of ipython"""
163
158
164 return ['--quick', # so no config file is loaded
159 return ['--quick', # so no config file is loaded
165 # Other defaults to minimize side effects on stdout
160 # Other defaults to minimize side effects on stdout
166 '--colors=NoColor', '--no-term-title','--no-banner',
161 '--colors=NoColor', '--no-term-title','--no-banner',
167 '--autocall=0']
162 '--autocall=0']
168
163
169
164
170 def default_config():
165 def default_config():
171 """Return a config object with good defaults for testing."""
166 """Return a config object with good defaults for testing."""
172 config = Config()
167 config = Config()
173 config.TerminalInteractiveShell.colors = 'NoColor'
168 config.TerminalInteractiveShell.colors = 'NoColor'
174 config.TerminalTerminalInteractiveShell.term_title = False,
169 config.TerminalTerminalInteractiveShell.term_title = False,
175 config.TerminalInteractiveShell.autocall = 0
170 config.TerminalInteractiveShell.autocall = 0
176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
171 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
177 config.HistoryManager.db_cache_size = 10000
172 config.HistoryManager.db_cache_size = 10000
178 return config
173 return config
179
174
180
175
181 def ipexec(fname, options=None):
176 def ipexec(fname, options=None):
182 """Utility to call 'ipython filename'.
177 """Utility to call 'ipython filename'.
183
178
184 Starts IPython witha minimal and safe configuration to make startup as fast
179 Starts IPython witha minimal and safe configuration to make startup as fast
185 as possible.
180 as possible.
186
181
187 Note that this starts IPython in a subprocess!
182 Note that this starts IPython in a subprocess!
188
183
189 Parameters
184 Parameters
190 ----------
185 ----------
191 fname : str
186 fname : str
192 Name of file to be executed (should have .py or .ipy extension).
187 Name of file to be executed (should have .py or .ipy extension).
193
188
194 options : optional, list
189 options : optional, list
195 Extra command-line flags to be passed to IPython.
190 Extra command-line flags to be passed to IPython.
196
191
197 Returns
192 Returns
198 -------
193 -------
199 (stdout, stderr) of ipython subprocess.
194 (stdout, stderr) of ipython subprocess.
200 """
195 """
201 if options is None: options = []
196 if options is None: options = []
202
197
203 # For these subprocess calls, eliminate all prompt printing so we only see
198 # For these subprocess calls, eliminate all prompt printing so we only see
204 # output from script execution
199 # output from script execution
205 prompt_opts = [ '--PromptManager.in_template=""',
200 prompt_opts = [ '--PromptManager.in_template=""',
206 '--PromptManager.in2_template=""',
201 '--PromptManager.in2_template=""',
207 '--PromptManager.out_template=""'
202 '--PromptManager.out_template=""'
208 ]
203 ]
209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
204 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210
205
211 _ip = get_ipython()
206 _ip = get_ipython()
212 test_dir = os.path.dirname(__file__)
207 test_dir = os.path.dirname(__file__)
213
208
214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
209 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
215 # Absolute path for filename
210 # Absolute path for filename
216 full_fname = os.path.join(test_dir, fname)
211 full_fname = os.path.join(test_dir, fname)
217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
212 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
213 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
219 out, err = getoutputerror(full_cmd)
214 out, err = getoutputerror(full_cmd)
220 # `import readline` causes 'ESC[?1034h' to be output sometimes,
215 # `import readline` causes 'ESC[?1034h' to be output sometimes,
221 # so strip that out before doing comparisons
216 # so strip that out before doing comparisons
222 if out:
217 if out:
223 out = re.sub(r'\x1b\[[^h]+h', '', out)
218 out = re.sub(r'\x1b\[[^h]+h', '', out)
224 return out, err
219 return out, err
225
220
226
221
227 def ipexec_validate(fname, expected_out, expected_err='',
222 def ipexec_validate(fname, expected_out, expected_err='',
228 options=None):
223 options=None):
229 """Utility to call 'ipython filename' and validate output/error.
224 """Utility to call 'ipython filename' and validate output/error.
230
225
231 This function raises an AssertionError if the validation fails.
226 This function raises an AssertionError if the validation fails.
232
227
233 Note that this starts IPython in a subprocess!
228 Note that this starts IPython in a subprocess!
234
229
235 Parameters
230 Parameters
236 ----------
231 ----------
237 fname : str
232 fname : str
238 Name of the file to be executed (should have .py or .ipy extension).
233 Name of the file to be executed (should have .py or .ipy extension).
239
234
240 expected_out : str
235 expected_out : str
241 Expected stdout of the process.
236 Expected stdout of the process.
242
237
243 expected_err : optional, str
238 expected_err : optional, str
244 Expected stderr of the process.
239 Expected stderr of the process.
245
240
246 options : optional, list
241 options : optional, list
247 Extra command-line flags to be passed to IPython.
242 Extra command-line flags to be passed to IPython.
248
243
249 Returns
244 Returns
250 -------
245 -------
251 None
246 None
252 """
247 """
253
248
254 import nose.tools as nt
249 import nose.tools as nt
255
250
256 out, err = ipexec(fname, options)
251 out, err = ipexec(fname, options)
257 #print 'OUT', out # dbg
252 #print 'OUT', out # dbg
258 #print 'ERR', err # dbg
253 #print 'ERR', err # dbg
259 # If there are any errors, we must check those befor stdout, as they may be
254 # If there are any errors, we must check those befor stdout, as they may be
260 # more informative than simply having an empty stdout.
255 # more informative than simply having an empty stdout.
261 if err:
256 if err:
262 if expected_err:
257 if expected_err:
263 nt.assert_equals(err.strip(), expected_err.strip())
258 nt.assert_equals(err.strip(), expected_err.strip())
264 else:
259 else:
265 raise ValueError('Running file %r produced error: %r' %
260 raise ValueError('Running file %r produced error: %r' %
266 (fname, err))
261 (fname, err))
267 # If no errors or output on stderr was expected, match stdout
262 # If no errors or output on stderr was expected, match stdout
268 nt.assert_equals(out.strip(), expected_out.strip())
263 nt.assert_equals(out.strip(), expected_out.strip())
269
264
270
265
271 class TempFileMixin(object):
266 class TempFileMixin(object):
272 """Utility class to create temporary Python/IPython files.
267 """Utility class to create temporary Python/IPython files.
273
268
274 Meant as a mixin class for test cases."""
269 Meant as a mixin class for test cases."""
275
270
276 def mktmp(self, src, ext='.py'):
271 def mktmp(self, src, ext='.py'):
277 """Make a valid python temp file."""
272 """Make a valid python temp file."""
278 fname, f = temp_pyfile(src, ext)
273 fname, f = temp_pyfile(src, ext)
279 self.tmpfile = f
274 self.tmpfile = f
280 self.fname = fname
275 self.fname = fname
281
276
282 def tearDown(self):
277 def tearDown(self):
283 if hasattr(self, 'tmpfile'):
278 if hasattr(self, 'tmpfile'):
284 # If the tmpfile wasn't made because of skipped tests, like in
279 # If the tmpfile wasn't made because of skipped tests, like in
285 # win32, there's nothing to cleanup.
280 # win32, there's nothing to cleanup.
286 self.tmpfile.close()
281 self.tmpfile.close()
287 try:
282 try:
288 os.unlink(self.fname)
283 os.unlink(self.fname)
289 except:
284 except:
290 # On Windows, even though we close the file, we still can't
285 # On Windows, even though we close the file, we still can't
291 # delete it. I have no clue why
286 # delete it. I have no clue why
292 pass
287 pass
293
288
294 pair_fail_msg = ("Testing {0}\n\n"
289 pair_fail_msg = ("Testing {0}\n\n"
295 "In:\n"
290 "In:\n"
296 " {1!r}\n"
291 " {1!r}\n"
297 "Expected:\n"
292 "Expected:\n"
298 " {2!r}\n"
293 " {2!r}\n"
299 "Got:\n"
294 "Got:\n"
300 " {3!r}\n")
295 " {3!r}\n")
301 def check_pairs(func, pairs):
296 def check_pairs(func, pairs):
302 """Utility function for the common case of checking a function with a
297 """Utility function for the common case of checking a function with a
303 sequence of input/output pairs.
298 sequence of input/output pairs.
304
299
305 Parameters
300 Parameters
306 ----------
301 ----------
307 func : callable
302 func : callable
308 The function to be tested. Should accept a single argument.
303 The function to be tested. Should accept a single argument.
309 pairs : iterable
304 pairs : iterable
310 A list of (input, expected_output) tuples.
305 A list of (input, expected_output) tuples.
311
306
312 Returns
307 Returns
313 -------
308 -------
314 None. Raises an AssertionError if any output does not match the expected
309 None. Raises an AssertionError if any output does not match the expected
315 value.
310 value.
316 """
311 """
317 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
312 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
318 for inp, expected in pairs:
313 for inp, expected in pairs:
319 out = func(inp)
314 out = func(inp)
320 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
315 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
321
316
322
317
323 if py3compat.PY3:
318 if py3compat.PY3:
324 MyStringIO = StringIO
319 MyStringIO = StringIO
325 else:
320 else:
326 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
321 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
327 # so we need a class that can handle both.
322 # so we need a class that can handle both.
328 class MyStringIO(StringIO):
323 class MyStringIO(StringIO):
329 def write(self, s):
324 def write(self, s):
330 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
325 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
331 super(MyStringIO, self).write(s)
326 super(MyStringIO, self).write(s)
332
327
333 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
328 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
334 {2!r}"""
329 {2!r}"""
335
330
336 class AssertPrints(object):
331 class AssertPrints(object):
337 """Context manager for testing that code prints certain text.
332 """Context manager for testing that code prints certain text.
338
333
339 Examples
334 Examples
340 --------
335 --------
341 >>> with AssertPrints("abc", suppress=False):
336 >>> with AssertPrints("abc", suppress=False):
342 ... print "abcd"
337 ... print "abcd"
343 ... print "def"
338 ... print "def"
344 ...
339 ...
345 abcd
340 abcd
346 def
341 def
347 """
342 """
348 def __init__(self, s, channel='stdout', suppress=True):
343 def __init__(self, s, channel='stdout', suppress=True):
349 self.s = s
344 self.s = s
350 self.channel = channel
345 self.channel = channel
351 self.suppress = suppress
346 self.suppress = suppress
352
347
353 def __enter__(self):
348 def __enter__(self):
354 self.orig_stream = getattr(sys, self.channel)
349 self.orig_stream = getattr(sys, self.channel)
355 self.buffer = MyStringIO()
350 self.buffer = MyStringIO()
356 self.tee = Tee(self.buffer, channel=self.channel)
351 self.tee = Tee(self.buffer, channel=self.channel)
357 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
352 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
358
353
359 def __exit__(self, etype, value, traceback):
354 def __exit__(self, etype, value, traceback):
360 self.tee.flush()
355 self.tee.flush()
361 setattr(sys, self.channel, self.orig_stream)
356 setattr(sys, self.channel, self.orig_stream)
362 printed = self.buffer.getvalue()
357 printed = self.buffer.getvalue()
363 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
358 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
364 return False
359 return False
365
360
366 class AssertNotPrints(AssertPrints):
361 class AssertNotPrints(AssertPrints):
367 """Context manager for checking that certain output *isn't* produced.
362 """Context manager for checking that certain output *isn't* produced.
368
363
369 Counterpart of AssertPrints"""
364 Counterpart of AssertPrints"""
370 def __exit__(self, etype, value, traceback):
365 def __exit__(self, etype, value, traceback):
371 self.tee.flush()
366 self.tee.flush()
372 setattr(sys, self.channel, self.orig_stream)
367 setattr(sys, self.channel, self.orig_stream)
373 printed = self.buffer.getvalue()
368 printed = self.buffer.getvalue()
374 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
369 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
375 return False
370 return False
376
371
377 @contextmanager
372 @contextmanager
378 def mute_warn():
373 def mute_warn():
379 from IPython.utils import warn
374 from IPython.utils import warn
380 save_warn = warn.warn
375 save_warn = warn.warn
381 warn.warn = lambda *a, **kw: None
376 warn.warn = lambda *a, **kw: None
382 try:
377 try:
383 yield
378 yield
384 finally:
379 finally:
385 warn.warn = save_warn
380 warn.warn = save_warn
386
381
387 @contextmanager
382 @contextmanager
388 def make_tempfile(name):
383 def make_tempfile(name):
389 """ Create an empty, named, temporary file for the duration of the context.
384 """ Create an empty, named, temporary file for the duration of the context.
390 """
385 """
391 f = open(name, 'w')
386 f = open(name, 'w')
392 f.close()
387 f.close()
393 try:
388 try:
394 yield
389 yield
395 finally:
390 finally:
396 os.unlink(name)
391 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now