##// END OF EJS Templates
strip leading 'ESC[?1034h' in tests caused by `import readline`...
MinRK -
Show More
@@ -1,321 +1,332 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 The IPython Development Team
21 # Copyright (C) 2009 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
34
35 from contextlib import contextmanager
35 from contextlib import contextmanager
36
36
37 try:
37 try:
38 # These tools are used by parts of the runtime, so we make the nose
38 # These tools are used by parts of the runtime, so we make the nose
39 # dependency optional at this point. Nose is a hard dependency to run the
39 # dependency optional at this point. Nose is a hard dependency to run the
40 # test suite, but NOT to use ipython itself.
40 # test suite, but NOT to use ipython itself.
41 import nose.tools as nt
41 import nose.tools as nt
42 has_nose = True
42 has_nose = True
43 except ImportError:
43 except ImportError:
44 has_nose = False
44 has_nose = False
45
45
46 from IPython.config.loader import Config
46 from IPython.config.loader import Config
47 from IPython.utils.process import find_cmd, getoutputerror
47 from IPython.utils.process import find_cmd, getoutputerror
48 from IPython.utils.text import list_strings
48 from IPython.utils.text import list_strings
49 from IPython.utils.io import temp_pyfile
49 from IPython.utils.io import temp_pyfile
50
50
51 from . import decorators as dec
51 from . import decorators as dec
52 from . import skipdoctest
52 from . import skipdoctest
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Globals
55 # Globals
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 # Make a bunch of nose.tools assert wrappers that can be used in test
58 # Make a bunch of nose.tools assert wrappers that can be used in test
59 # generators. This will expose an assert* function for each one in nose.tools.
59 # generators. This will expose an assert* function for each one in nose.tools.
60
60
61 _tpl = """
61 _tpl = """
62 def %(name)s(*a,**kw):
62 def %(name)s(*a,**kw):
63 return nt.%(name)s(*a,**kw)
63 return nt.%(name)s(*a,**kw)
64 """
64 """
65
65
66 if has_nose:
66 if has_nose:
67 for _x in [a for a in dir(nt) if a.startswith('assert')]:
67 for _x in [a for a in dir(nt) if a.startswith('assert')]:
68 exec _tpl % dict(name=_x)
68 exec _tpl % dict(name=_x)
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Functions and classes
71 # Functions and classes
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 # The docstring for full_path doctests differently on win32 (different path
74 # The docstring for full_path doctests differently on win32 (different path
75 # separator) so just skip the doctest there. The example remains informative.
75 # separator) so just skip the doctest there. The example remains informative.
76 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
76 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
77
77
78 @doctest_deco
78 @doctest_deco
79 def full_path(startPath,files):
79 def full_path(startPath,files):
80 """Make full paths for all the listed files, based on startPath.
80 """Make full paths for all the listed files, based on startPath.
81
81
82 Only the base part of startPath is kept, since this routine is typically
82 Only the base part of startPath is kept, since this routine is typically
83 used with a script's __file__ variable as startPath. The base of startPath
83 used with a script's __file__ variable as startPath. The base of startPath
84 is then prepended to all the listed files, forming the output list.
84 is then prepended to all the listed files, forming the output list.
85
85
86 Parameters
86 Parameters
87 ----------
87 ----------
88 startPath : string
88 startPath : string
89 Initial path to use as the base for the results. This path is split
89 Initial path to use as the base for the results. This path is split
90 using os.path.split() and only its first component is kept.
90 using os.path.split() and only its first component is kept.
91
91
92 files : string or list
92 files : string or list
93 One or more files.
93 One or more files.
94
94
95 Examples
95 Examples
96 --------
96 --------
97
97
98 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
98 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
99 ['/foo/a.txt', '/foo/b.txt']
99 ['/foo/a.txt', '/foo/b.txt']
100
100
101 >>> full_path('/foo',['a.txt','b.txt'])
101 >>> full_path('/foo',['a.txt','b.txt'])
102 ['/a.txt', '/b.txt']
102 ['/a.txt', '/b.txt']
103
103
104 If a single file is given, the output is still a list:
104 If a single file is given, the output is still a list:
105 >>> full_path('/foo','a.txt')
105 >>> full_path('/foo','a.txt')
106 ['/a.txt']
106 ['/a.txt']
107 """
107 """
108
108
109 files = list_strings(files)
109 files = list_strings(files)
110 base = os.path.split(startPath)[0]
110 base = os.path.split(startPath)[0]
111 return [ os.path.join(base,f) for f in files ]
111 return [ os.path.join(base,f) for f in files ]
112
112
113
113
114 def parse_test_output(txt):
114 def parse_test_output(txt):
115 """Parse the output of a test run and return errors, failures.
115 """Parse the output of a test run and return errors, failures.
116
116
117 Parameters
117 Parameters
118 ----------
118 ----------
119 txt : str
119 txt : str
120 Text output of a test run, assumed to contain a line of one of the
120 Text output of a test run, assumed to contain a line of one of the
121 following forms::
121 following forms::
122 'FAILED (errors=1)'
122 'FAILED (errors=1)'
123 'FAILED (failures=1)'
123 'FAILED (failures=1)'
124 'FAILED (errors=1, failures=1)'
124 'FAILED (errors=1, failures=1)'
125
125
126 Returns
126 Returns
127 -------
127 -------
128 nerr, nfail: number of errors and failures.
128 nerr, nfail: number of errors and failures.
129 """
129 """
130
130
131 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
131 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
132 if err_m:
132 if err_m:
133 nerr = int(err_m.group(1))
133 nerr = int(err_m.group(1))
134 nfail = 0
134 nfail = 0
135 return nerr, nfail
135 return nerr, nfail
136
136
137 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
137 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
138 if fail_m:
138 if fail_m:
139 nerr = 0
139 nerr = 0
140 nfail = int(fail_m.group(1))
140 nfail = int(fail_m.group(1))
141 return nerr, nfail
141 return nerr, nfail
142
142
143 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
143 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
144 re.MULTILINE)
144 re.MULTILINE)
145 if both_m:
145 if both_m:
146 nerr = int(both_m.group(1))
146 nerr = int(both_m.group(1))
147 nfail = int(both_m.group(2))
147 nfail = int(both_m.group(2))
148 return nerr, nfail
148 return nerr, nfail
149
149
150 # If the input didn't match any of these forms, assume no error/failures
150 # If the input didn't match any of these forms, assume no error/failures
151 return 0, 0
151 return 0, 0
152
152
153
153
154 # So nose doesn't think this is a test
154 # So nose doesn't think this is a test
155 parse_test_output.__test__ = False
155 parse_test_output.__test__ = False
156
156
157
157
158 def default_argv():
158 def default_argv():
159 """Return a valid default argv for creating testing instances of ipython"""
159 """Return a valid default argv for creating testing instances of ipython"""
160
160
161 return ['--quick', # so no config file is loaded
161 return ['--quick', # so no config file is loaded
162 # Other defaults to minimize side effects on stdout
162 # Other defaults to minimize side effects on stdout
163 '--colors=NoColor', '--no-term-title','--no-banner',
163 '--colors=NoColor', '--no-term-title','--no-banner',
164 '--autocall=0']
164 '--autocall=0']
165
165
166
166
167 def default_config():
167 def default_config():
168 """Return a config object with good defaults for testing."""
168 """Return a config object with good defaults for testing."""
169 config = Config()
169 config = Config()
170 config.TerminalInteractiveShell.colors = 'NoColor'
170 config.TerminalInteractiveShell.colors = 'NoColor'
171 config.TerminalTerminalInteractiveShell.term_title = False,
171 config.TerminalTerminalInteractiveShell.term_title = False,
172 config.TerminalInteractiveShell.autocall = 0
172 config.TerminalInteractiveShell.autocall = 0
173 config.HistoryManager.hist_file = u'test_hist.sqlite'
173 config.HistoryManager.hist_file = u'test_hist.sqlite'
174 config.HistoryManager.db_cache_size = 10000
174 config.HistoryManager.db_cache_size = 10000
175 return config
175 return config
176
176
177
177
178 def ipexec(fname, options=None):
178 def ipexec(fname, options=None):
179 """Utility to call 'ipython filename'.
179 """Utility to call 'ipython filename'.
180
180
181 Starts IPython witha minimal and safe configuration to make startup as fast
181 Starts IPython witha minimal and safe configuration to make startup as fast
182 as possible.
182 as possible.
183
183
184 Note that this starts IPython in a subprocess!
184 Note that this starts IPython in a subprocess!
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 fname : str
188 fname : str
189 Name of file to be executed (should have .py or .ipy extension).
189 Name of file to be executed (should have .py or .ipy extension).
190
190
191 options : optional, list
191 options : optional, list
192 Extra command-line flags to be passed to IPython.
192 Extra command-line flags to be passed to IPython.
193
193
194 Returns
194 Returns
195 -------
195 -------
196 (stdout, stderr) of ipython subprocess.
196 (stdout, stderr) of ipython subprocess.
197 """
197 """
198 if options is None: options = []
198 if options is None: options = []
199
199
200 # For these subprocess calls, eliminate all prompt printing so we only see
200 # For these subprocess calls, eliminate all prompt printing so we only see
201 # output from script execution
201 # output from script execution
202 prompt_opts = [ '--InteractiveShell.prompt_in1=""',
202 prompt_opts = [ '--InteractiveShell.prompt_in1=""',
203 '--InteractiveShell.prompt_in2=""',
203 '--InteractiveShell.prompt_in2=""',
204 '--InteractiveShell.prompt_out=""'
204 '--InteractiveShell.prompt_out=""'
205 ]
205 ]
206 cmdargs = ' '.join(default_argv() + prompt_opts + options)
206 cmdargs = ' '.join(default_argv() + prompt_opts + options)
207
207
208 _ip = get_ipython()
208 _ip = get_ipython()
209 test_dir = os.path.dirname(__file__)
209 test_dir = os.path.dirname(__file__)
210
210
211 ipython_cmd = find_cmd('ipython')
211 ipython_cmd = find_cmd('ipython')
212 # Absolute path for filename
212 # Absolute path for filename
213 full_fname = os.path.join(test_dir, fname)
213 full_fname = os.path.join(test_dir, fname)
214 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
214 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
215 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
215 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
216 return getoutputerror(full_cmd)
216 out = getoutputerror(full_cmd)
217 # `import readline` causes 'ESC[?1034h' to be the first output sometimes,
218 # so strip that off the front of the first line if it is found
219 if out:
220 first = out[0]
221 m = re.match(r'\x1b\[[^h]+h', first)
222 if m:
223 # strip initial readline escape
224 out = list(out)
225 out[0] = first[len(m.group()):]
226 out = tuple(out)
227 return out
217
228
218
229
219 def ipexec_validate(fname, expected_out, expected_err='',
230 def ipexec_validate(fname, expected_out, expected_err='',
220 options=None):
231 options=None):
221 """Utility to call 'ipython filename' and validate output/error.
232 """Utility to call 'ipython filename' and validate output/error.
222
233
223 This function raises an AssertionError if the validation fails.
234 This function raises an AssertionError if the validation fails.
224
235
225 Note that this starts IPython in a subprocess!
236 Note that this starts IPython in a subprocess!
226
237
227 Parameters
238 Parameters
228 ----------
239 ----------
229 fname : str
240 fname : str
230 Name of the file to be executed (should have .py or .ipy extension).
241 Name of the file to be executed (should have .py or .ipy extension).
231
242
232 expected_out : str
243 expected_out : str
233 Expected stdout of the process.
244 Expected stdout of the process.
234
245
235 expected_err : optional, str
246 expected_err : optional, str
236 Expected stderr of the process.
247 Expected stderr of the process.
237
248
238 options : optional, list
249 options : optional, list
239 Extra command-line flags to be passed to IPython.
250 Extra command-line flags to be passed to IPython.
240
251
241 Returns
252 Returns
242 -------
253 -------
243 None
254 None
244 """
255 """
245
256
246 import nose.tools as nt
257 import nose.tools as nt
247
258
248 out, err = ipexec(fname)
259 out, err = ipexec(fname)
249 #print 'OUT', out # dbg
260 #print 'OUT', out # dbg
250 #print 'ERR', err # dbg
261 #print 'ERR', err # dbg
251 # If there are any errors, we must check those befor stdout, as they may be
262 # If there are any errors, we must check those befor stdout, as they may be
252 # more informative than simply having an empty stdout.
263 # more informative than simply having an empty stdout.
253 if err:
264 if err:
254 if expected_err:
265 if expected_err:
255 nt.assert_equals(err.strip(), expected_err.strip())
266 nt.assert_equals(err.strip(), expected_err.strip())
256 else:
267 else:
257 raise ValueError('Running file %r produced error: %r' %
268 raise ValueError('Running file %r produced error: %r' %
258 (fname, err))
269 (fname, err))
259 # If no errors or output on stderr was expected, match stdout
270 # If no errors or output on stderr was expected, match stdout
260 nt.assert_equals(out.strip(), expected_out.strip())
271 nt.assert_equals(out.strip(), expected_out.strip())
261
272
262
273
263 class TempFileMixin(object):
274 class TempFileMixin(object):
264 """Utility class to create temporary Python/IPython files.
275 """Utility class to create temporary Python/IPython files.
265
276
266 Meant as a mixin class for test cases."""
277 Meant as a mixin class for test cases."""
267
278
268 def mktmp(self, src, ext='.py'):
279 def mktmp(self, src, ext='.py'):
269 """Make a valid python temp file."""
280 """Make a valid python temp file."""
270 fname, f = temp_pyfile(src, ext)
281 fname, f = temp_pyfile(src, ext)
271 self.tmpfile = f
282 self.tmpfile = f
272 self.fname = fname
283 self.fname = fname
273
284
274 def tearDown(self):
285 def tearDown(self):
275 if hasattr(self, 'tmpfile'):
286 if hasattr(self, 'tmpfile'):
276 # If the tmpfile wasn't made because of skipped tests, like in
287 # If the tmpfile wasn't made because of skipped tests, like in
277 # win32, there's nothing to cleanup.
288 # win32, there's nothing to cleanup.
278 self.tmpfile.close()
289 self.tmpfile.close()
279 try:
290 try:
280 os.unlink(self.fname)
291 os.unlink(self.fname)
281 except:
292 except:
282 # On Windows, even though we close the file, we still can't
293 # On Windows, even though we close the file, we still can't
283 # delete it. I have no clue why
294 # delete it. I have no clue why
284 pass
295 pass
285
296
286 pair_fail_msg = ("Testing function {0}\n\n"
297 pair_fail_msg = ("Testing function {0}\n\n"
287 "In:\n"
298 "In:\n"
288 " {1!r}\n"
299 " {1!r}\n"
289 "Expected:\n"
300 "Expected:\n"
290 " {2!r}\n"
301 " {2!r}\n"
291 "Got:\n"
302 "Got:\n"
292 " {3!r}\n")
303 " {3!r}\n")
293 def check_pairs(func, pairs):
304 def check_pairs(func, pairs):
294 """Utility function for the common case of checking a function with a
305 """Utility function for the common case of checking a function with a
295 sequence of input/output pairs.
306 sequence of input/output pairs.
296
307
297 Parameters
308 Parameters
298 ----------
309 ----------
299 func : callable
310 func : callable
300 The function to be tested. Should accept a single argument.
311 The function to be tested. Should accept a single argument.
301 pairs : iterable
312 pairs : iterable
302 A list of (input, expected_output) tuples.
313 A list of (input, expected_output) tuples.
303
314
304 Returns
315 Returns
305 -------
316 -------
306 None. Raises an AssertionError if any output does not match the expected
317 None. Raises an AssertionError if any output does not match the expected
307 value.
318 value.
308 """
319 """
309 for inp, expected in pairs:
320 for inp, expected in pairs:
310 out = func(inp)
321 out = func(inp)
311 assert out == expected, pair_fail_msg.format(func.func_name, inp, expected, out)
322 assert out == expected, pair_fail_msg.format(func.func_name, inp, expected, out)
312
323
313 @contextmanager
324 @contextmanager
314 def mute_warn():
325 def mute_warn():
315 from IPython.utils import warn
326 from IPython.utils import warn
316 save_warn = warn.warn
327 save_warn = warn.warn
317 warn.warn = lambda *a, **kw: None
328 warn.warn = lambda *a, **kw: None
318 try:
329 try:
319 yield
330 yield
320 finally:
331 finally:
321 warn.warn = save_warn No newline at end of file
332 warn.warn = save_warn
General Comments 0
You need to be logged in to leave comments. Login now