##// END OF EJS Templates
Minimize stdout side effects in testing instances.
Fernando Perez -
Show More
@@ -1,315 +1,315 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 #*****************************************************************************
18 #*****************************************************************************
19 # Copyright (C) 2009 The IPython Development Team
19 # Copyright (C) 2009 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #*****************************************************************************
23 #*****************************************************************************
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Required modules and packages
26 # Required modules and packages
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 from __future__ import absolute_import
28 from __future__ import absolute_import
29
29
30 import os
30 import os
31 import re
31 import re
32 import sys
32 import sys
33 import tempfile
33 import tempfile
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.utils import genutils, platutils
44 from IPython.utils import genutils, platutils
45
45
46 from . import decorators as dec
46 from . import decorators as dec
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Globals
49 # Globals
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 # Make a bunch of nose.tools assert wrappers that can be used in test
52 # Make a bunch of nose.tools assert wrappers that can be used in test
53 # generators. This will expose an assert* function for each one in nose.tools.
53 # generators. This will expose an assert* function for each one in nose.tools.
54
54
55 _tpl = """
55 _tpl = """
56 def %(name)s(*a,**kw):
56 def %(name)s(*a,**kw):
57 return nt.%(name)s(*a,**kw)
57 return nt.%(name)s(*a,**kw)
58 """
58 """
59
59
60 if has_nose:
60 if has_nose:
61 for _x in [a for a in dir(nt) if a.startswith('assert')]:
61 for _x in [a for a in dir(nt) if a.startswith('assert')]:
62 exec _tpl % dict(name=_x)
62 exec _tpl % dict(name=_x)
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Functions and classes
65 # Functions and classes
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 # The docstring for full_path doctests differently on win32 (different path
68 # The docstring for full_path doctests differently on win32 (different path
69 # separator) so just skip the doctest there. The example remains informative.
69 # separator) so just skip the doctest there. The example remains informative.
70 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
70 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
71
71
72 @doctest_deco
72 @doctest_deco
73 def full_path(startPath,files):
73 def full_path(startPath,files):
74 """Make full paths for all the listed files, based on startPath.
74 """Make full paths for all the listed files, based on startPath.
75
75
76 Only the base part of startPath is kept, since this routine is typically
76 Only the base part of startPath is kept, since this routine is typically
77 used with a script's __file__ variable as startPath. The base of startPath
77 used with a script's __file__ variable as startPath. The base of startPath
78 is then prepended to all the listed files, forming the output list.
78 is then prepended to all the listed files, forming the output list.
79
79
80 Parameters
80 Parameters
81 ----------
81 ----------
82 startPath : string
82 startPath : string
83 Initial path to use as the base for the results. This path is split
83 Initial path to use as the base for the results. This path is split
84 using os.path.split() and only its first component is kept.
84 using os.path.split() and only its first component is kept.
85
85
86 files : string or list
86 files : string or list
87 One or more files.
87 One or more files.
88
88
89 Examples
89 Examples
90 --------
90 --------
91
91
92 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
92 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
93 ['/foo/a.txt', '/foo/b.txt']
93 ['/foo/a.txt', '/foo/b.txt']
94
94
95 >>> full_path('/foo',['a.txt','b.txt'])
95 >>> full_path('/foo',['a.txt','b.txt'])
96 ['/a.txt', '/b.txt']
96 ['/a.txt', '/b.txt']
97
97
98 If a single file is given, the output is still a list:
98 If a single file is given, the output is still a list:
99 >>> full_path('/foo','a.txt')
99 >>> full_path('/foo','a.txt')
100 ['/a.txt']
100 ['/a.txt']
101 """
101 """
102
102
103 files = genutils.list_strings(files)
103 files = genutils.list_strings(files)
104 base = os.path.split(startPath)[0]
104 base = os.path.split(startPath)[0]
105 return [ os.path.join(base,f) for f in files ]
105 return [ os.path.join(base,f) for f in files ]
106
106
107
107
108 def parse_test_output(txt):
108 def parse_test_output(txt):
109 """Parse the output of a test run and return errors, failures.
109 """Parse the output of a test run and return errors, failures.
110
110
111 Parameters
111 Parameters
112 ----------
112 ----------
113 txt : str
113 txt : str
114 Text output of a test run, assumed to contain a line of one of the
114 Text output of a test run, assumed to contain a line of one of the
115 following forms::
115 following forms::
116 'FAILED (errors=1)'
116 'FAILED (errors=1)'
117 'FAILED (failures=1)'
117 'FAILED (failures=1)'
118 'FAILED (errors=1, failures=1)'
118 'FAILED (errors=1, failures=1)'
119
119
120 Returns
120 Returns
121 -------
121 -------
122 nerr, nfail: number of errors and failures.
122 nerr, nfail: number of errors and failures.
123 """
123 """
124
124
125 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
125 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
126 if err_m:
126 if err_m:
127 nerr = int(err_m.group(1))
127 nerr = int(err_m.group(1))
128 nfail = 0
128 nfail = 0
129 return nerr, nfail
129 return nerr, nfail
130
130
131 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
131 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
132 if fail_m:
132 if fail_m:
133 nerr = 0
133 nerr = 0
134 nfail = int(fail_m.group(1))
134 nfail = int(fail_m.group(1))
135 return nerr, nfail
135 return nerr, nfail
136
136
137 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
137 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
138 re.MULTILINE)
138 re.MULTILINE)
139 if both_m:
139 if both_m:
140 nerr = int(both_m.group(1))
140 nerr = int(both_m.group(1))
141 nfail = int(both_m.group(2))
141 nfail = int(both_m.group(2))
142 return nerr, nfail
142 return nerr, nfail
143
143
144 # If the input didn't match any of these forms, assume no error/failures
144 # If the input didn't match any of these forms, assume no error/failures
145 return 0, 0
145 return 0, 0
146
146
147
147
148 # So nose doesn't think this is a test
148 # So nose doesn't think this is a test
149 parse_test_output.__test__ = False
149 parse_test_output.__test__ = False
150
150
151
151
152 def cmd2argv(cmd):
152 def cmd2argv(cmd):
153 r"""Take the path of a command and return a list (argv-style).
153 r"""Take the path of a command and return a list (argv-style).
154
154
155 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
155 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
156 .com or .bat, and ['python', cmd] otherwise.
156 .com or .bat, and ['python', cmd] otherwise.
157
157
158 This is mostly a Windows utility, to deal with the fact that the scripts in
158 This is mostly a Windows utility, to deal with the fact that the scripts in
159 Windows get wrapped in .exe entry points, so we have to call them
159 Windows get wrapped in .exe entry points, so we have to call them
160 differently.
160 differently.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 cmd : string
164 cmd : string
165 The path of the command.
165 The path of the command.
166
166
167 Returns
167 Returns
168 -------
168 -------
169 argv-style list.
169 argv-style list.
170
170
171 Examples
171 Examples
172 --------
172 --------
173 In [2]: cmd2argv('/usr/bin/ipython')
173 In [2]: cmd2argv('/usr/bin/ipython')
174 Out[2]: ['python', '/usr/bin/ipython']
174 Out[2]: ['python', '/usr/bin/ipython']
175
175
176 In [3]: cmd2argv(r'C:\Python26\Scripts\ipython.exe')
176 In [3]: cmd2argv(r'C:\Python26\Scripts\ipython.exe')
177 Out[3]: ['C:\\Python26\\Scripts\\ipython.exe']
177 Out[3]: ['C:\\Python26\\Scripts\\ipython.exe']
178 """
178 """
179 ext = os.path.splitext(cmd)[1]
179 ext = os.path.splitext(cmd)[1]
180 if ext in ['.exe', '.com', '.bat']:
180 if ext in ['.exe', '.com', '.bat']:
181 return [cmd]
181 return [cmd]
182 else:
182 else:
183 return ['python', cmd]
183 return ['python', cmd]
184
184
185
185
186 def temp_pyfile(src, ext='.py'):
186 def temp_pyfile(src, ext='.py'):
187 """Make a temporary python file, return filename and filehandle.
187 """Make a temporary python file, return filename and filehandle.
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 src : string or list of strings (no need for ending newlines if list)
191 src : string or list of strings (no need for ending newlines if list)
192 Source code to be written to the file.
192 Source code to be written to the file.
193
193
194 ext : optional, string
194 ext : optional, string
195 Extension for the generated file.
195 Extension for the generated file.
196
196
197 Returns
197 Returns
198 -------
198 -------
199 (filename, open filehandle)
199 (filename, open filehandle)
200 It is the caller's responsibility to close the open file and unlink it.
200 It is the caller's responsibility to close the open file and unlink it.
201 """
201 """
202 fname = tempfile.mkstemp(ext)[1]
202 fname = tempfile.mkstemp(ext)[1]
203 f = open(fname,'w')
203 f = open(fname,'w')
204 f.write(src)
204 f.write(src)
205 f.flush()
205 f.flush()
206 return fname, f
206 return fname, f
207
207
208
208
209 def default_argv():
209 def default_argv():
210 """Return a valid default argv for creating testing instances of ipython"""
210 """Return a valid default argv for creating testing instances of ipython"""
211
211
212 # Get the install directory for the user configuration and tell ipython to
212 return ['--quick', # so no config file is loaded
213 # use the default profile from there.
213 # Other defaults to minimize side effects on stdout
214 from IPython.config import default
214 '--colors=NoColor', '--no-term-title','--no-banner',
215 ipcdir = os.path.dirname(default.__file__)
215 '--autocall=0']
216 ipconf = os.path.join(ipcdir,'ipython_config.py')
217 return ['--colors=NoColor', '--no-term-title','--no-banner',
218 '--config-file="%s"' % ipconf, '--autocall=0',
219 '--prompt-out=""']
220
216
221
217
222 def ipexec(fname, options=None):
218 def ipexec(fname, options=None):
223 """Utility to call 'ipython filename'.
219 """Utility to call 'ipython filename'.
224
220
225 Starts IPython witha minimal and safe configuration to make startup as fast
221 Starts IPython witha minimal and safe configuration to make startup as fast
226 as possible.
222 as possible.
227
223
228 Note that this starts IPython in a subprocess!
224 Note that this starts IPython in a subprocess!
229
225
230 Parameters
226 Parameters
231 ----------
227 ----------
232 fname : str
228 fname : str
233 Name of file to be executed (should have .py or .ipy extension).
229 Name of file to be executed (should have .py or .ipy extension).
234
230
235 options : optional, list
231 options : optional, list
236 Extra command-line flags to be passed to IPython.
232 Extra command-line flags to be passed to IPython.
237
233
238 Returns
234 Returns
239 -------
235 -------
240 (stdout, stderr) of ipython subprocess.
236 (stdout, stderr) of ipython subprocess.
241 """
237 """
242 if options is None: options = []
238 if options is None: options = []
243 cmdargs = ' '.join(default_argv() + options)
239
240 # For these subprocess calls, eliminate all prompt printing so we only see
241 # output from script execution
242 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
243 cmdargs = ' '.join(default_argv() + prompt_opts + options)
244
244
245 _ip = get_ipython()
245 _ip = get_ipython()
246 test_dir = os.path.dirname(__file__)
246 test_dir = os.path.dirname(__file__)
247 # Find the ipython script from the package we're using, so that the test
247 # Find the ipython script from the package we're using, so that the test
248 # suite can be run from the source tree without an installed IPython
248 # suite can be run from the source tree without an installed IPython
249 ipython_package_dir = genutils.get_ipython_package_dir()
249 ipython_package_dir = genutils.get_ipython_package_dir()
250 ipython_script = os.path.join(ipython_package_dir,'scripts','ipython')
250 ipython_script = os.path.join(ipython_package_dir,'scripts','ipython')
251 ipython_cmd = 'python "%s"' % ipython_script
251 ipython_cmd = 'python "%s"' % ipython_script
252 # Absolute path for filename
252 # Absolute path for filename
253 full_fname = os.path.join(test_dir, fname)
253 full_fname = os.path.join(test_dir, fname)
254 full_cmd = '%s %s "%s"' % (ipython_cmd, cmdargs, full_fname)
254 full_cmd = '%s %s "%s"' % (ipython_cmd, cmdargs, full_fname)
255 return genutils.getoutputerror(full_cmd)
255 return genutils.getoutputerror(full_cmd)
256
256
257
257
258 def ipexec_validate(fname, expected_out, expected_err=None,
258 def ipexec_validate(fname, expected_out, expected_err=None,
259 options=None):
259 options=None):
260 """Utility to call 'ipython filename' and validate output/error.
260 """Utility to call 'ipython filename' and validate output/error.
261
261
262 This function raises an AssertionError if the validation fails.
262 This function raises an AssertionError if the validation fails.
263
263
264 Note that this starts IPython in a subprocess!
264 Note that this starts IPython in a subprocess!
265
265
266 Parameters
266 Parameters
267 ----------
267 ----------
268 fname : str
268 fname : str
269 Name of the file to be executed (should have .py or .ipy extension).
269 Name of the file to be executed (should have .py or .ipy extension).
270
270
271 expected_out : str
271 expected_out : str
272 Expected stdout of the process.
272 Expected stdout of the process.
273
273
274 expected_err : optional, str
274 expected_err : optional, str
275 Expected stderr of the process.
275 Expected stderr of the process.
276
276
277 options : optional, list
277 options : optional, list
278 Extra command-line flags to be passed to IPython.
278 Extra command-line flags to be passed to IPython.
279
279
280 Returns
280 Returns
281 -------
281 -------
282 None
282 None
283 """
283 """
284
284
285 import nose.tools as nt
285 import nose.tools as nt
286
286
287 out, err = ipexec(fname)
287 out, err = ipexec(fname)
288 nt.assert_equals(out.strip(), expected_out.strip())
288 nt.assert_equals(out.strip(), expected_out.strip())
289 if expected_err:
289 if expected_err:
290 nt.assert_equals(err.strip(), expected_err.strip())
290 nt.assert_equals(err.strip(), expected_err.strip())
291
291
292
292
293 class TempFileMixin(object):
293 class TempFileMixin(object):
294 """Utility class to create temporary Python/IPython files.
294 """Utility class to create temporary Python/IPython files.
295
295
296 Meant as a mixin class for test cases."""
296 Meant as a mixin class for test cases."""
297
297
298 def mktmp(self, src, ext='.py'):
298 def mktmp(self, src, ext='.py'):
299 """Make a valid python temp file."""
299 """Make a valid python temp file."""
300 fname, f = temp_pyfile(src, ext)
300 fname, f = temp_pyfile(src, ext)
301 self.tmpfile = f
301 self.tmpfile = f
302 self.fname = fname
302 self.fname = fname
303
303
304 def teardown(self):
304 def teardown(self):
305 if hasattr(self, 'tmpfile'):
305 if hasattr(self, 'tmpfile'):
306 # If the tmpfile wasn't made because of skipped tests, like in
306 # If the tmpfile wasn't made because of skipped tests, like in
307 # win32, there's nothing to cleanup.
307 # win32, there's nothing to cleanup.
308 self.tmpfile.close()
308 self.tmpfile.close()
309 try:
309 try:
310 os.unlink(self.fname)
310 os.unlink(self.fname)
311 except:
311 except:
312 # On Windows, even though we close the file, we still can't
312 # On Windows, even though we close the file, we still can't
313 # delete it. I have no clue why
313 # delete it. I have no clue why
314 pass
314 pass
315
315
General Comments 0
You need to be logged in to leave comments. Login now