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