##// END OF EJS Templates
unpack out,err in ipexec...
MinRK -
Show More
@@ -1,398 +1,396 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools that do NOT depend on Twisted.
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
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
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
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
10 instead.
11
11
12
12
13 Authors
13 Authors
14 -------
14 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
16 """
17
17
18 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Copyright (C) 2009-2011 The IPython Development Team
21 # Copyright (C) 2009-2011 The IPython Development Team
22 #
22 #
23 # Distributed under the terms of the BSD License. The full license is in
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
24 # the file COPYING, distributed as part of this software.
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Imports
28 # Imports
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 import os
31 import os
32 import re
32 import re
33 import sys
33 import sys
34 import tempfile
34 import tempfile
35
35
36 from contextlib import contextmanager
36 from contextlib import contextmanager
37 from io import StringIO
37 from io import StringIO
38
38
39 try:
39 try:
40 # These tools are used by parts of the runtime, so we make the nose
40 # 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
41 # dependency optional at this point. Nose is a hard dependency to run the
42 # test suite, but NOT to use ipython itself.
42 # test suite, but NOT to use ipython itself.
43 import nose.tools as nt
43 import nose.tools as nt
44 has_nose = True
44 has_nose = True
45 except ImportError:
45 except ImportError:
46 has_nose = False
46 has_nose = False
47
47
48 from IPython.config.loader import Config
48 from IPython.config.loader import Config
49 from IPython.utils.process import find_cmd, getoutputerror
49 from IPython.utils.process import find_cmd, getoutputerror
50 from IPython.utils.text import list_strings, getdefaultencoding
50 from IPython.utils.text import list_strings, getdefaultencoding
51 from IPython.utils.io import temp_pyfile, Tee
51 from IPython.utils.io import temp_pyfile, Tee
52 from IPython.utils import py3compat
52 from IPython.utils import py3compat
53
53
54 from . import decorators as dec
54 from . import decorators as dec
55 from . import skipdoctest
55 from . import skipdoctest
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Globals
58 # Globals
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 # Make a bunch of nose.tools assert wrappers that can be used in test
61 # 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.
62 # generators. This will expose an assert* function for each one in nose.tools.
63
63
64 _tpl = """
64 _tpl = """
65 def %(name)s(*a,**kw):
65 def %(name)s(*a,**kw):
66 return nt.%(name)s(*a,**kw)
66 return nt.%(name)s(*a,**kw)
67 """
67 """
68
68
69 if has_nose:
69 if has_nose:
70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
71 exec _tpl % dict(name=_x)
71 exec _tpl % dict(name=_x)
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Functions and classes
74 # Functions and classes
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 # The docstring for full_path doctests differently on win32 (different path
77 # The docstring for full_path doctests differently on win32 (different path
78 # separator) so just skip the doctest there. The example remains informative.
78 # 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
79 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
80
80
81 @doctest_deco
81 @doctest_deco
82 def full_path(startPath,files):
82 def full_path(startPath,files):
83 """Make full paths for all the listed files, based on startPath.
83 """Make full paths for all the listed files, based on startPath.
84
84
85 Only the base part of startPath is kept, since this routine is typically
85 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
86 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.
87 is then prepended to all the listed files, forming the output list.
88
88
89 Parameters
89 Parameters
90 ----------
90 ----------
91 startPath : string
91 startPath : string
92 Initial path to use as the base for the results. This path is split
92 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.
93 using os.path.split() and only its first component is kept.
94
94
95 files : string or list
95 files : string or list
96 One or more files.
96 One or more files.
97
97
98 Examples
98 Examples
99 --------
99 --------
100
100
101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
102 ['/foo/a.txt', '/foo/b.txt']
102 ['/foo/a.txt', '/foo/b.txt']
103
103
104 >>> full_path('/foo',['a.txt','b.txt'])
104 >>> full_path('/foo',['a.txt','b.txt'])
105 ['/a.txt', '/b.txt']
105 ['/a.txt', '/b.txt']
106
106
107 If a single file is given, the output is still a list:
107 If a single file is given, the output is still a list:
108 >>> full_path('/foo','a.txt')
108 >>> full_path('/foo','a.txt')
109 ['/a.txt']
109 ['/a.txt']
110 """
110 """
111
111
112 files = list_strings(files)
112 files = list_strings(files)
113 base = os.path.split(startPath)[0]
113 base = os.path.split(startPath)[0]
114 return [ os.path.join(base,f) for f in files ]
114 return [ os.path.join(base,f) for f in files ]
115
115
116
116
117 def parse_test_output(txt):
117 def parse_test_output(txt):
118 """Parse the output of a test run and return errors, failures.
118 """Parse the output of a test run and return errors, failures.
119
119
120 Parameters
120 Parameters
121 ----------
121 ----------
122 txt : str
122 txt : str
123 Text output of a test run, assumed to contain a line of one of the
123 Text output of a test run, assumed to contain a line of one of the
124 following forms::
124 following forms::
125 'FAILED (errors=1)'
125 'FAILED (errors=1)'
126 'FAILED (failures=1)'
126 'FAILED (failures=1)'
127 'FAILED (errors=1, failures=1)'
127 'FAILED (errors=1, failures=1)'
128
128
129 Returns
129 Returns
130 -------
130 -------
131 nerr, nfail: number of errors and failures.
131 nerr, nfail: number of errors and failures.
132 """
132 """
133
133
134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
135 if err_m:
135 if err_m:
136 nerr = int(err_m.group(1))
136 nerr = int(err_m.group(1))
137 nfail = 0
137 nfail = 0
138 return nerr, nfail
138 return nerr, nfail
139
139
140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
141 if fail_m:
141 if fail_m:
142 nerr = 0
142 nerr = 0
143 nfail = int(fail_m.group(1))
143 nfail = int(fail_m.group(1))
144 return nerr, nfail
144 return nerr, nfail
145
145
146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
147 re.MULTILINE)
147 re.MULTILINE)
148 if both_m:
148 if both_m:
149 nerr = int(both_m.group(1))
149 nerr = int(both_m.group(1))
150 nfail = int(both_m.group(2))
150 nfail = int(both_m.group(2))
151 return nerr, nfail
151 return nerr, nfail
152
152
153 # If the input didn't match any of these forms, assume no error/failures
153 # If the input didn't match any of these forms, assume no error/failures
154 return 0, 0
154 return 0, 0
155
155
156
156
157 # So nose doesn't think this is a test
157 # So nose doesn't think this is a test
158 parse_test_output.__test__ = False
158 parse_test_output.__test__ = False
159
159
160
160
161 def default_argv():
161 def default_argv():
162 """Return a valid default argv for creating testing instances of ipython"""
162 """Return a valid default argv for creating testing instances of ipython"""
163
163
164 return ['--quick', # so no config file is loaded
164 return ['--quick', # so no config file is loaded
165 # Other defaults to minimize side effects on stdout
165 # Other defaults to minimize side effects on stdout
166 '--colors=NoColor', '--no-term-title','--no-banner',
166 '--colors=NoColor', '--no-term-title','--no-banner',
167 '--autocall=0']
167 '--autocall=0']
168
168
169
169
170 def default_config():
170 def default_config():
171 """Return a config object with good defaults for testing."""
171 """Return a config object with good defaults for testing."""
172 config = Config()
172 config = Config()
173 config.TerminalInteractiveShell.colors = 'NoColor'
173 config.TerminalInteractiveShell.colors = 'NoColor'
174 config.TerminalTerminalInteractiveShell.term_title = False,
174 config.TerminalTerminalInteractiveShell.term_title = False,
175 config.TerminalInteractiveShell.autocall = 0
175 config.TerminalInteractiveShell.autocall = 0
176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
177 config.HistoryManager.db_cache_size = 10000
177 config.HistoryManager.db_cache_size = 10000
178 return config
178 return config
179
179
180
180
181 def ipexec(fname, options=None):
181 def ipexec(fname, options=None):
182 """Utility to call 'ipython filename'.
182 """Utility to call 'ipython filename'.
183
183
184 Starts IPython witha minimal and safe configuration to make startup as fast
184 Starts IPython witha minimal and safe configuration to make startup as fast
185 as possible.
185 as possible.
186
186
187 Note that this starts IPython in a subprocess!
187 Note that this starts IPython in a subprocess!
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 fname : str
191 fname : str
192 Name of file to be executed (should have .py or .ipy extension).
192 Name of file to be executed (should have .py or .ipy extension).
193
193
194 options : optional, list
194 options : optional, list
195 Extra command-line flags to be passed to IPython.
195 Extra command-line flags to be passed to IPython.
196
196
197 Returns
197 Returns
198 -------
198 -------
199 (stdout, stderr) of ipython subprocess.
199 (stdout, stderr) of ipython subprocess.
200 """
200 """
201 if options is None: options = []
201 if options is None: options = []
202
202
203 # For these subprocess calls, eliminate all prompt printing so we only see
203 # For these subprocess calls, eliminate all prompt printing so we only see
204 # output from script execution
204 # output from script execution
205 prompt_opts = [ '--PromptManager.in_template=""',
205 prompt_opts = [ '--PromptManager.in_template=""',
206 '--PromptManager.in2_template=""',
206 '--PromptManager.in2_template=""',
207 '--PromptManager.out_template=""'
207 '--PromptManager.out_template=""'
208 ]
208 ]
209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210
210
211 _ip = get_ipython()
211 _ip = get_ipython()
212 test_dir = os.path.dirname(__file__)
212 test_dir = os.path.dirname(__file__)
213
213
214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
215 # Absolute path for filename
215 # Absolute path for filename
216 full_fname = os.path.join(test_dir, fname)
216 full_fname = os.path.join(test_dir, fname)
217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
219 out = getoutputerror(full_cmd)
219 out, err = getoutputerror(full_cmd)
220 # `import readline` causes 'ESC[?1034h' to be output sometimes,
220 # `import readline` causes 'ESC[?1034h' to be output sometimes,
221 # so strip that out before doing comparisons
221 # so strip that out before doing comparisons
222 if out:
222 if out:
223 out,err = out
224 out = re.sub(r'\x1b\[[^h]+h', '', out)
223 out = re.sub(r'\x1b\[[^h]+h', '', out)
225 out = out,err
224 return out, err
226 return out
227
225
228
226
229 def ipexec_validate(fname, expected_out, expected_err='',
227 def ipexec_validate(fname, expected_out, expected_err='',
230 options=None):
228 options=None):
231 """Utility to call 'ipython filename' and validate output/error.
229 """Utility to call 'ipython filename' and validate output/error.
232
230
233 This function raises an AssertionError if the validation fails.
231 This function raises an AssertionError if the validation fails.
234
232
235 Note that this starts IPython in a subprocess!
233 Note that this starts IPython in a subprocess!
236
234
237 Parameters
235 Parameters
238 ----------
236 ----------
239 fname : str
237 fname : str
240 Name of the file to be executed (should have .py or .ipy extension).
238 Name of the file to be executed (should have .py or .ipy extension).
241
239
242 expected_out : str
240 expected_out : str
243 Expected stdout of the process.
241 Expected stdout of the process.
244
242
245 expected_err : optional, str
243 expected_err : optional, str
246 Expected stderr of the process.
244 Expected stderr of the process.
247
245
248 options : optional, list
246 options : optional, list
249 Extra command-line flags to be passed to IPython.
247 Extra command-line flags to be passed to IPython.
250
248
251 Returns
249 Returns
252 -------
250 -------
253 None
251 None
254 """
252 """
255
253
256 import nose.tools as nt
254 import nose.tools as nt
257
255
258 out, err = ipexec(fname, options)
256 out, err = ipexec(fname, options)
259 #print 'OUT', out # dbg
257 #print 'OUT', out # dbg
260 #print 'ERR', err # dbg
258 #print 'ERR', err # dbg
261 # If there are any errors, we must check those befor stdout, as they may be
259 # If there are any errors, we must check those befor stdout, as they may be
262 # more informative than simply having an empty stdout.
260 # more informative than simply having an empty stdout.
263 if err:
261 if err:
264 if expected_err:
262 if expected_err:
265 nt.assert_equals(err.strip(), expected_err.strip())
263 nt.assert_equals(err.strip(), expected_err.strip())
266 else:
264 else:
267 raise ValueError('Running file %r produced error: %r' %
265 raise ValueError('Running file %r produced error: %r' %
268 (fname, err))
266 (fname, err))
269 # If no errors or output on stderr was expected, match stdout
267 # If no errors or output on stderr was expected, match stdout
270 nt.assert_equals(out.strip(), expected_out.strip())
268 nt.assert_equals(out.strip(), expected_out.strip())
271
269
272
270
273 class TempFileMixin(object):
271 class TempFileMixin(object):
274 """Utility class to create temporary Python/IPython files.
272 """Utility class to create temporary Python/IPython files.
275
273
276 Meant as a mixin class for test cases."""
274 Meant as a mixin class for test cases."""
277
275
278 def mktmp(self, src, ext='.py'):
276 def mktmp(self, src, ext='.py'):
279 """Make a valid python temp file."""
277 """Make a valid python temp file."""
280 fname, f = temp_pyfile(src, ext)
278 fname, f = temp_pyfile(src, ext)
281 self.tmpfile = f
279 self.tmpfile = f
282 self.fname = fname
280 self.fname = fname
283
281
284 def tearDown(self):
282 def tearDown(self):
285 if hasattr(self, 'tmpfile'):
283 if hasattr(self, 'tmpfile'):
286 # If the tmpfile wasn't made because of skipped tests, like in
284 # If the tmpfile wasn't made because of skipped tests, like in
287 # win32, there's nothing to cleanup.
285 # win32, there's nothing to cleanup.
288 self.tmpfile.close()
286 self.tmpfile.close()
289 try:
287 try:
290 os.unlink(self.fname)
288 os.unlink(self.fname)
291 except:
289 except:
292 # On Windows, even though we close the file, we still can't
290 # On Windows, even though we close the file, we still can't
293 # delete it. I have no clue why
291 # delete it. I have no clue why
294 pass
292 pass
295
293
296 pair_fail_msg = ("Testing {0}\n\n"
294 pair_fail_msg = ("Testing {0}\n\n"
297 "In:\n"
295 "In:\n"
298 " {1!r}\n"
296 " {1!r}\n"
299 "Expected:\n"
297 "Expected:\n"
300 " {2!r}\n"
298 " {2!r}\n"
301 "Got:\n"
299 "Got:\n"
302 " {3!r}\n")
300 " {3!r}\n")
303 def check_pairs(func, pairs):
301 def check_pairs(func, pairs):
304 """Utility function for the common case of checking a function with a
302 """Utility function for the common case of checking a function with a
305 sequence of input/output pairs.
303 sequence of input/output pairs.
306
304
307 Parameters
305 Parameters
308 ----------
306 ----------
309 func : callable
307 func : callable
310 The function to be tested. Should accept a single argument.
308 The function to be tested. Should accept a single argument.
311 pairs : iterable
309 pairs : iterable
312 A list of (input, expected_output) tuples.
310 A list of (input, expected_output) tuples.
313
311
314 Returns
312 Returns
315 -------
313 -------
316 None. Raises an AssertionError if any output does not match the expected
314 None. Raises an AssertionError if any output does not match the expected
317 value.
315 value.
318 """
316 """
319 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
317 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
320 for inp, expected in pairs:
318 for inp, expected in pairs:
321 out = func(inp)
319 out = func(inp)
322 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
320 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
323
321
324
322
325 if py3compat.PY3:
323 if py3compat.PY3:
326 MyStringIO = StringIO
324 MyStringIO = StringIO
327 else:
325 else:
328 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
326 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
329 # so we need a class that can handle both.
327 # so we need a class that can handle both.
330 class MyStringIO(StringIO):
328 class MyStringIO(StringIO):
331 def write(self, s):
329 def write(self, s):
332 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
330 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
333 super(MyStringIO, self).write(s)
331 super(MyStringIO, self).write(s)
334
332
335 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
333 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
336 {2!r}"""
334 {2!r}"""
337
335
338 class AssertPrints(object):
336 class AssertPrints(object):
339 """Context manager for testing that code prints certain text.
337 """Context manager for testing that code prints certain text.
340
338
341 Examples
339 Examples
342 --------
340 --------
343 >>> with AssertPrints("abc", suppress=False):
341 >>> with AssertPrints("abc", suppress=False):
344 ... print "abcd"
342 ... print "abcd"
345 ... print "def"
343 ... print "def"
346 ...
344 ...
347 abcd
345 abcd
348 def
346 def
349 """
347 """
350 def __init__(self, s, channel='stdout', suppress=True):
348 def __init__(self, s, channel='stdout', suppress=True):
351 self.s = s
349 self.s = s
352 self.channel = channel
350 self.channel = channel
353 self.suppress = suppress
351 self.suppress = suppress
354
352
355 def __enter__(self):
353 def __enter__(self):
356 self.orig_stream = getattr(sys, self.channel)
354 self.orig_stream = getattr(sys, self.channel)
357 self.buffer = MyStringIO()
355 self.buffer = MyStringIO()
358 self.tee = Tee(self.buffer, channel=self.channel)
356 self.tee = Tee(self.buffer, channel=self.channel)
359 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
357 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
360
358
361 def __exit__(self, etype, value, traceback):
359 def __exit__(self, etype, value, traceback):
362 self.tee.flush()
360 self.tee.flush()
363 setattr(sys, self.channel, self.orig_stream)
361 setattr(sys, self.channel, self.orig_stream)
364 printed = self.buffer.getvalue()
362 printed = self.buffer.getvalue()
365 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
363 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
366 return False
364 return False
367
365
368 class AssertNotPrints(AssertPrints):
366 class AssertNotPrints(AssertPrints):
369 """Context manager for checking that certain output *isn't* produced.
367 """Context manager for checking that certain output *isn't* produced.
370
368
371 Counterpart of AssertPrints"""
369 Counterpart of AssertPrints"""
372 def __exit__(self, etype, value, traceback):
370 def __exit__(self, etype, value, traceback):
373 self.tee.flush()
371 self.tee.flush()
374 setattr(sys, self.channel, self.orig_stream)
372 setattr(sys, self.channel, self.orig_stream)
375 printed = self.buffer.getvalue()
373 printed = self.buffer.getvalue()
376 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
374 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
377 return False
375 return False
378
376
379 @contextmanager
377 @contextmanager
380 def mute_warn():
378 def mute_warn():
381 from IPython.utils import warn
379 from IPython.utils import warn
382 save_warn = warn.warn
380 save_warn = warn.warn
383 warn.warn = lambda *a, **kw: None
381 warn.warn = lambda *a, **kw: None
384 try:
382 try:
385 yield
383 yield
386 finally:
384 finally:
387 warn.warn = save_warn
385 warn.warn = save_warn
388
386
389 @contextmanager
387 @contextmanager
390 def make_tempfile(name):
388 def make_tempfile(name):
391 """ Create an empty, named, temporary file for the duration of the context.
389 """ Create an empty, named, temporary file for the duration of the context.
392 """
390 """
393 f = open(name, 'w')
391 f = open(name, 'w')
394 f.close()
392 f.close()
395 try:
393 try:
396 yield
394 yield
397 finally:
395 finally:
398 os.unlink(name)
396 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now