##// END OF EJS Templates
Make our temp file mixin unittest-friendly....
Fernando Perez -
Show More
@@ -1,277 +1,277 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 return config
170 return config
171
171
172
172
173 def ipexec(fname, options=None):
173 def ipexec(fname, options=None):
174 """Utility to call 'ipython filename'.
174 """Utility to call 'ipython filename'.
175
175
176 Starts IPython witha minimal and safe configuration to make startup as fast
176 Starts IPython witha minimal and safe configuration to make startup as fast
177 as possible.
177 as possible.
178
178
179 Note that this starts IPython in a subprocess!
179 Note that this starts IPython in a subprocess!
180
180
181 Parameters
181 Parameters
182 ----------
182 ----------
183 fname : str
183 fname : str
184 Name of file to be executed (should have .py or .ipy extension).
184 Name of file to be executed (should have .py or .ipy extension).
185
185
186 options : optional, list
186 options : optional, list
187 Extra command-line flags to be passed to IPython.
187 Extra command-line flags to be passed to IPython.
188
188
189 Returns
189 Returns
190 -------
190 -------
191 (stdout, stderr) of ipython subprocess.
191 (stdout, stderr) of ipython subprocess.
192 """
192 """
193 if options is None: options = []
193 if options is None: options = []
194
194
195 # For these subprocess calls, eliminate all prompt printing so we only see
195 # For these subprocess calls, eliminate all prompt printing so we only see
196 # output from script execution
196 # output from script execution
197 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
197 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
198 cmdargs = ' '.join(default_argv() + prompt_opts + options)
198 cmdargs = ' '.join(default_argv() + prompt_opts + options)
199
199
200 _ip = get_ipython()
200 _ip = get_ipython()
201 test_dir = os.path.dirname(__file__)
201 test_dir = os.path.dirname(__file__)
202
202
203 ipython_cmd = find_cmd('ipython')
203 ipython_cmd = find_cmd('ipython')
204 # Absolute path for filename
204 # Absolute path for filename
205 full_fname = os.path.join(test_dir, fname)
205 full_fname = os.path.join(test_dir, fname)
206 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
206 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
207 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
207 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
208 return getoutputerror(full_cmd)
208 return getoutputerror(full_cmd)
209
209
210
210
211 def ipexec_validate(fname, expected_out, expected_err='',
211 def ipexec_validate(fname, expected_out, expected_err='',
212 options=None):
212 options=None):
213 """Utility to call 'ipython filename' and validate output/error.
213 """Utility to call 'ipython filename' and validate output/error.
214
214
215 This function raises an AssertionError if the validation fails.
215 This function raises an AssertionError if the validation fails.
216
216
217 Note that this starts IPython in a subprocess!
217 Note that this starts IPython in a subprocess!
218
218
219 Parameters
219 Parameters
220 ----------
220 ----------
221 fname : str
221 fname : str
222 Name of the file to be executed (should have .py or .ipy extension).
222 Name of the file to be executed (should have .py or .ipy extension).
223
223
224 expected_out : str
224 expected_out : str
225 Expected stdout of the process.
225 Expected stdout of the process.
226
226
227 expected_err : optional, str
227 expected_err : optional, str
228 Expected stderr of the process.
228 Expected stderr of the process.
229
229
230 options : optional, list
230 options : optional, list
231 Extra command-line flags to be passed to IPython.
231 Extra command-line flags to be passed to IPython.
232
232
233 Returns
233 Returns
234 -------
234 -------
235 None
235 None
236 """
236 """
237
237
238 import nose.tools as nt
238 import nose.tools as nt
239
239
240 out, err = ipexec(fname)
240 out, err = ipexec(fname)
241 #print 'OUT', out # dbg
241 #print 'OUT', out # dbg
242 #print 'ERR', err # dbg
242 #print 'ERR', err # dbg
243 # If there are any errors, we must check those befor stdout, as they may be
243 # If there are any errors, we must check those befor stdout, as they may be
244 # more informative than simply having an empty stdout.
244 # more informative than simply having an empty stdout.
245 if err:
245 if err:
246 if expected_err:
246 if expected_err:
247 nt.assert_equals(err.strip(), expected_err.strip())
247 nt.assert_equals(err.strip(), expected_err.strip())
248 else:
248 else:
249 raise ValueError('Running file %r produced error: %r' %
249 raise ValueError('Running file %r produced error: %r' %
250 (fname, err))
250 (fname, err))
251 # If no errors or output on stderr was expected, match stdout
251 # If no errors or output on stderr was expected, match stdout
252 nt.assert_equals(out.strip(), expected_out.strip())
252 nt.assert_equals(out.strip(), expected_out.strip())
253
253
254
254
255 class TempFileMixin(object):
255 class TempFileMixin(object):
256 """Utility class to create temporary Python/IPython files.
256 """Utility class to create temporary Python/IPython files.
257
257
258 Meant as a mixin class for test cases."""
258 Meant as a mixin class for test cases."""
259
259
260 def mktmp(self, src, ext='.py'):
260 def mktmp(self, src, ext='.py'):
261 """Make a valid python temp file."""
261 """Make a valid python temp file."""
262 fname, f = temp_pyfile(src, ext)
262 fname, f = temp_pyfile(src, ext)
263 self.tmpfile = f
263 self.tmpfile = f
264 self.fname = fname
264 self.fname = fname
265
265
266 def teardown(self):
266 def tearDown(self):
267 if hasattr(self, 'tmpfile'):
267 if hasattr(self, 'tmpfile'):
268 # If the tmpfile wasn't made because of skipped tests, like in
268 # If the tmpfile wasn't made because of skipped tests, like in
269 # win32, there's nothing to cleanup.
269 # win32, there's nothing to cleanup.
270 self.tmpfile.close()
270 self.tmpfile.close()
271 try:
271 try:
272 os.unlink(self.fname)
272 os.unlink(self.fname)
273 except:
273 except:
274 # On Windows, even though we close the file, we still can't
274 # On Windows, even though we close the file, we still can't
275 # delete it. I have no clue why
275 # delete it. I have no clue why
276 pass
276 pass
277
277
General Comments 0
You need to be logged in to leave comments. Login now