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