##// END OF EJS Templates
unpack out,err in ipexec...
MinRK -
Show More
@@ -1,398 +1,396 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 from __future__ import absolute_import
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Copyright (C) 2009-2011 The IPython Development Team
22 22 #
23 23 # Distributed under the terms of the BSD License. The full license is in
24 24 # the file COPYING, distributed as part of this software.
25 25 #-----------------------------------------------------------------------------
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Imports
29 29 #-----------------------------------------------------------------------------
30 30
31 31 import os
32 32 import re
33 33 import sys
34 34 import tempfile
35 35
36 36 from contextlib import contextmanager
37 37 from io import StringIO
38 38
39 39 try:
40 40 # These tools are used by parts of the runtime, so we make the nose
41 41 # dependency optional at this point. Nose is a hard dependency to run the
42 42 # test suite, but NOT to use ipython itself.
43 43 import nose.tools as nt
44 44 has_nose = True
45 45 except ImportError:
46 46 has_nose = False
47 47
48 48 from IPython.config.loader import Config
49 49 from IPython.utils.process import find_cmd, getoutputerror
50 50 from IPython.utils.text import list_strings, getdefaultencoding
51 51 from IPython.utils.io import temp_pyfile, Tee
52 52 from IPython.utils import py3compat
53 53
54 54 from . import decorators as dec
55 55 from . import skipdoctest
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Globals
59 59 #-----------------------------------------------------------------------------
60 60
61 61 # Make a bunch of nose.tools assert wrappers that can be used in test
62 62 # generators. This will expose an assert* function for each one in nose.tools.
63 63
64 64 _tpl = """
65 65 def %(name)s(*a,**kw):
66 66 return nt.%(name)s(*a,**kw)
67 67 """
68 68
69 69 if has_nose:
70 70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
71 71 exec _tpl % dict(name=_x)
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Functions and classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77 # The docstring for full_path doctests differently on win32 (different path
78 78 # separator) so just skip the doctest there. The example remains informative.
79 79 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
80 80
81 81 @doctest_deco
82 82 def full_path(startPath,files):
83 83 """Make full paths for all the listed files, based on startPath.
84 84
85 85 Only the base part of startPath is kept, since this routine is typically
86 86 used with a script's __file__ variable as startPath. The base of startPath
87 87 is then prepended to all the listed files, forming the output list.
88 88
89 89 Parameters
90 90 ----------
91 91 startPath : string
92 92 Initial path to use as the base for the results. This path is split
93 93 using os.path.split() and only its first component is kept.
94 94
95 95 files : string or list
96 96 One or more files.
97 97
98 98 Examples
99 99 --------
100 100
101 101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
102 102 ['/foo/a.txt', '/foo/b.txt']
103 103
104 104 >>> full_path('/foo',['a.txt','b.txt'])
105 105 ['/a.txt', '/b.txt']
106 106
107 107 If a single file is given, the output is still a list:
108 108 >>> full_path('/foo','a.txt')
109 109 ['/a.txt']
110 110 """
111 111
112 112 files = list_strings(files)
113 113 base = os.path.split(startPath)[0]
114 114 return [ os.path.join(base,f) for f in files ]
115 115
116 116
117 117 def parse_test_output(txt):
118 118 """Parse the output of a test run and return errors, failures.
119 119
120 120 Parameters
121 121 ----------
122 122 txt : str
123 123 Text output of a test run, assumed to contain a line of one of the
124 124 following forms::
125 125 'FAILED (errors=1)'
126 126 'FAILED (failures=1)'
127 127 'FAILED (errors=1, failures=1)'
128 128
129 129 Returns
130 130 -------
131 131 nerr, nfail: number of errors and failures.
132 132 """
133 133
134 134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
135 135 if err_m:
136 136 nerr = int(err_m.group(1))
137 137 nfail = 0
138 138 return nerr, nfail
139 139
140 140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
141 141 if fail_m:
142 142 nerr = 0
143 143 nfail = int(fail_m.group(1))
144 144 return nerr, nfail
145 145
146 146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
147 147 re.MULTILINE)
148 148 if both_m:
149 149 nerr = int(both_m.group(1))
150 150 nfail = int(both_m.group(2))
151 151 return nerr, nfail
152 152
153 153 # If the input didn't match any of these forms, assume no error/failures
154 154 return 0, 0
155 155
156 156
157 157 # So nose doesn't think this is a test
158 158 parse_test_output.__test__ = False
159 159
160 160
161 161 def default_argv():
162 162 """Return a valid default argv for creating testing instances of ipython"""
163 163
164 164 return ['--quick', # so no config file is loaded
165 165 # Other defaults to minimize side effects on stdout
166 166 '--colors=NoColor', '--no-term-title','--no-banner',
167 167 '--autocall=0']
168 168
169 169
170 170 def default_config():
171 171 """Return a config object with good defaults for testing."""
172 172 config = Config()
173 173 config.TerminalInteractiveShell.colors = 'NoColor'
174 174 config.TerminalTerminalInteractiveShell.term_title = False,
175 175 config.TerminalInteractiveShell.autocall = 0
176 176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
177 177 config.HistoryManager.db_cache_size = 10000
178 178 return config
179 179
180 180
181 181 def ipexec(fname, options=None):
182 182 """Utility to call 'ipython filename'.
183 183
184 184 Starts IPython witha minimal and safe configuration to make startup as fast
185 185 as possible.
186 186
187 187 Note that this starts IPython in a subprocess!
188 188
189 189 Parameters
190 190 ----------
191 191 fname : str
192 192 Name of file to be executed (should have .py or .ipy extension).
193 193
194 194 options : optional, list
195 195 Extra command-line flags to be passed to IPython.
196 196
197 197 Returns
198 198 -------
199 199 (stdout, stderr) of ipython subprocess.
200 200 """
201 201 if options is None: options = []
202 202
203 203 # For these subprocess calls, eliminate all prompt printing so we only see
204 204 # output from script execution
205 205 prompt_opts = [ '--PromptManager.in_template=""',
206 206 '--PromptManager.in2_template=""',
207 207 '--PromptManager.out_template=""'
208 208 ]
209 209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210 210
211 211 _ip = get_ipython()
212 212 test_dir = os.path.dirname(__file__)
213 213
214 214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
215 215 # Absolute path for filename
216 216 full_fname = os.path.join(test_dir, fname)
217 217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
218 218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
219 out = getoutputerror(full_cmd)
219 out, err = getoutputerror(full_cmd)
220 220 # `import readline` causes 'ESC[?1034h' to be output sometimes,
221 221 # so strip that out before doing comparisons
222 222 if out:
223 out,err = out
224 223 out = re.sub(r'\x1b\[[^h]+h', '', out)
225 out = out,err
226 return out
224 return out, err
227 225
228 226
229 227 def ipexec_validate(fname, expected_out, expected_err='',
230 228 options=None):
231 229 """Utility to call 'ipython filename' and validate output/error.
232 230
233 231 This function raises an AssertionError if the validation fails.
234 232
235 233 Note that this starts IPython in a subprocess!
236 234
237 235 Parameters
238 236 ----------
239 237 fname : str
240 238 Name of the file to be executed (should have .py or .ipy extension).
241 239
242 240 expected_out : str
243 241 Expected stdout of the process.
244 242
245 243 expected_err : optional, str
246 244 Expected stderr of the process.
247 245
248 246 options : optional, list
249 247 Extra command-line flags to be passed to IPython.
250 248
251 249 Returns
252 250 -------
253 251 None
254 252 """
255 253
256 254 import nose.tools as nt
257 255
258 256 out, err = ipexec(fname, options)
259 257 #print 'OUT', out # dbg
260 258 #print 'ERR', err # dbg
261 259 # If there are any errors, we must check those befor stdout, as they may be
262 260 # more informative than simply having an empty stdout.
263 261 if err:
264 262 if expected_err:
265 263 nt.assert_equals(err.strip(), expected_err.strip())
266 264 else:
267 265 raise ValueError('Running file %r produced error: %r' %
268 266 (fname, err))
269 267 # If no errors or output on stderr was expected, match stdout
270 268 nt.assert_equals(out.strip(), expected_out.strip())
271 269
272 270
273 271 class TempFileMixin(object):
274 272 """Utility class to create temporary Python/IPython files.
275 273
276 274 Meant as a mixin class for test cases."""
277 275
278 276 def mktmp(self, src, ext='.py'):
279 277 """Make a valid python temp file."""
280 278 fname, f = temp_pyfile(src, ext)
281 279 self.tmpfile = f
282 280 self.fname = fname
283 281
284 282 def tearDown(self):
285 283 if hasattr(self, 'tmpfile'):
286 284 # If the tmpfile wasn't made because of skipped tests, like in
287 285 # win32, there's nothing to cleanup.
288 286 self.tmpfile.close()
289 287 try:
290 288 os.unlink(self.fname)
291 289 except:
292 290 # On Windows, even though we close the file, we still can't
293 291 # delete it. I have no clue why
294 292 pass
295 293
296 294 pair_fail_msg = ("Testing {0}\n\n"
297 295 "In:\n"
298 296 " {1!r}\n"
299 297 "Expected:\n"
300 298 " {2!r}\n"
301 299 "Got:\n"
302 300 " {3!r}\n")
303 301 def check_pairs(func, pairs):
304 302 """Utility function for the common case of checking a function with a
305 303 sequence of input/output pairs.
306 304
307 305 Parameters
308 306 ----------
309 307 func : callable
310 308 The function to be tested. Should accept a single argument.
311 309 pairs : iterable
312 310 A list of (input, expected_output) tuples.
313 311
314 312 Returns
315 313 -------
316 314 None. Raises an AssertionError if any output does not match the expected
317 315 value.
318 316 """
319 317 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
320 318 for inp, expected in pairs:
321 319 out = func(inp)
322 320 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
323 321
324 322
325 323 if py3compat.PY3:
326 324 MyStringIO = StringIO
327 325 else:
328 326 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
329 327 # so we need a class that can handle both.
330 328 class MyStringIO(StringIO):
331 329 def write(self, s):
332 330 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
333 331 super(MyStringIO, self).write(s)
334 332
335 333 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
336 334 {2!r}"""
337 335
338 336 class AssertPrints(object):
339 337 """Context manager for testing that code prints certain text.
340 338
341 339 Examples
342 340 --------
343 341 >>> with AssertPrints("abc", suppress=False):
344 342 ... print "abcd"
345 343 ... print "def"
346 344 ...
347 345 abcd
348 346 def
349 347 """
350 348 def __init__(self, s, channel='stdout', suppress=True):
351 349 self.s = s
352 350 self.channel = channel
353 351 self.suppress = suppress
354 352
355 353 def __enter__(self):
356 354 self.orig_stream = getattr(sys, self.channel)
357 355 self.buffer = MyStringIO()
358 356 self.tee = Tee(self.buffer, channel=self.channel)
359 357 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
360 358
361 359 def __exit__(self, etype, value, traceback):
362 360 self.tee.flush()
363 361 setattr(sys, self.channel, self.orig_stream)
364 362 printed = self.buffer.getvalue()
365 363 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
366 364 return False
367 365
368 366 class AssertNotPrints(AssertPrints):
369 367 """Context manager for checking that certain output *isn't* produced.
370 368
371 369 Counterpart of AssertPrints"""
372 370 def __exit__(self, etype, value, traceback):
373 371 self.tee.flush()
374 372 setattr(sys, self.channel, self.orig_stream)
375 373 printed = self.buffer.getvalue()
376 374 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
377 375 return False
378 376
379 377 @contextmanager
380 378 def mute_warn():
381 379 from IPython.utils import warn
382 380 save_warn = warn.warn
383 381 warn.warn = lambda *a, **kw: None
384 382 try:
385 383 yield
386 384 finally:
387 385 warn.warn = save_warn
388 386
389 387 @contextmanager
390 388 def make_tempfile(name):
391 389 """ Create an empty, named, temporary file for the duration of the context.
392 390 """
393 391 f = open(name, 'w')
394 392 f.close()
395 393 try:
396 394 yield
397 395 finally:
398 396 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now