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