##// END OF EJS Templates
Merge pull request #1472 from minrk/ipexec_esc...
Min RK -
r6237:3912ea7c merge
parent child Browse files
Show More
@@ -1,402 +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)
220 # `import readline` causes 'ESC[?1034h' to be the first output sometimes,
221 # so strip that off the front of the first line if it is found
219 out, err = getoutputerror(full_cmd)
220 # `import readline` causes 'ESC[?1034h' to be output sometimes,
221 # so strip that out before doing comparisons
222 222 if out:
223 first = out[0]
224 m = re.match(r'\x1b\[[^h]+h', first)
225 if m:
226 # strip initial readline escape
227 out = list(out)
228 out[0] = first[len(m.group()):]
229 out = tuple(out)
230 return out
223 out = re.sub(r'\x1b\[[^h]+h', '', out)
224 return out, err
231 225
232 226
233 227 def ipexec_validate(fname, expected_out, expected_err='',
234 228 options=None):
235 229 """Utility to call 'ipython filename' and validate output/error.
236 230
237 231 This function raises an AssertionError if the validation fails.
238 232
239 233 Note that this starts IPython in a subprocess!
240 234
241 235 Parameters
242 236 ----------
243 237 fname : str
244 238 Name of the file to be executed (should have .py or .ipy extension).
245 239
246 240 expected_out : str
247 241 Expected stdout of the process.
248 242
249 243 expected_err : optional, str
250 244 Expected stderr of the process.
251 245
252 246 options : optional, list
253 247 Extra command-line flags to be passed to IPython.
254 248
255 249 Returns
256 250 -------
257 251 None
258 252 """
259 253
260 254 import nose.tools as nt
261 255
262 256 out, err = ipexec(fname, options)
263 257 #print 'OUT', out # dbg
264 258 #print 'ERR', err # dbg
265 259 # If there are any errors, we must check those befor stdout, as they may be
266 260 # more informative than simply having an empty stdout.
267 261 if err:
268 262 if expected_err:
269 263 nt.assert_equals(err.strip(), expected_err.strip())
270 264 else:
271 265 raise ValueError('Running file %r produced error: %r' %
272 266 (fname, err))
273 267 # If no errors or output on stderr was expected, match stdout
274 268 nt.assert_equals(out.strip(), expected_out.strip())
275 269
276 270
277 271 class TempFileMixin(object):
278 272 """Utility class to create temporary Python/IPython files.
279 273
280 274 Meant as a mixin class for test cases."""
281 275
282 276 def mktmp(self, src, ext='.py'):
283 277 """Make a valid python temp file."""
284 278 fname, f = temp_pyfile(src, ext)
285 279 self.tmpfile = f
286 280 self.fname = fname
287 281
288 282 def tearDown(self):
289 283 if hasattr(self, 'tmpfile'):
290 284 # If the tmpfile wasn't made because of skipped tests, like in
291 285 # win32, there's nothing to cleanup.
292 286 self.tmpfile.close()
293 287 try:
294 288 os.unlink(self.fname)
295 289 except:
296 290 # On Windows, even though we close the file, we still can't
297 291 # delete it. I have no clue why
298 292 pass
299 293
300 294 pair_fail_msg = ("Testing {0}\n\n"
301 295 "In:\n"
302 296 " {1!r}\n"
303 297 "Expected:\n"
304 298 " {2!r}\n"
305 299 "Got:\n"
306 300 " {3!r}\n")
307 301 def check_pairs(func, pairs):
308 302 """Utility function for the common case of checking a function with a
309 303 sequence of input/output pairs.
310 304
311 305 Parameters
312 306 ----------
313 307 func : callable
314 308 The function to be tested. Should accept a single argument.
315 309 pairs : iterable
316 310 A list of (input, expected_output) tuples.
317 311
318 312 Returns
319 313 -------
320 314 None. Raises an AssertionError if any output does not match the expected
321 315 value.
322 316 """
323 317 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
324 318 for inp, expected in pairs:
325 319 out = func(inp)
326 320 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
327 321
328 322
329 323 if py3compat.PY3:
330 324 MyStringIO = StringIO
331 325 else:
332 326 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
333 327 # so we need a class that can handle both.
334 328 class MyStringIO(StringIO):
335 329 def write(self, s):
336 330 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
337 331 super(MyStringIO, self).write(s)
338 332
339 333 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
340 334 {2!r}"""
341 335
342 336 class AssertPrints(object):
343 337 """Context manager for testing that code prints certain text.
344 338
345 339 Examples
346 340 --------
347 341 >>> with AssertPrints("abc", suppress=False):
348 342 ... print "abcd"
349 343 ... print "def"
350 344 ...
351 345 abcd
352 346 def
353 347 """
354 348 def __init__(self, s, channel='stdout', suppress=True):
355 349 self.s = s
356 350 self.channel = channel
357 351 self.suppress = suppress
358 352
359 353 def __enter__(self):
360 354 self.orig_stream = getattr(sys, self.channel)
361 355 self.buffer = MyStringIO()
362 356 self.tee = Tee(self.buffer, channel=self.channel)
363 357 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
364 358
365 359 def __exit__(self, etype, value, traceback):
366 360 self.tee.flush()
367 361 setattr(sys, self.channel, self.orig_stream)
368 362 printed = self.buffer.getvalue()
369 363 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
370 364 return False
371 365
372 366 class AssertNotPrints(AssertPrints):
373 367 """Context manager for checking that certain output *isn't* produced.
374 368
375 369 Counterpart of AssertPrints"""
376 370 def __exit__(self, etype, value, traceback):
377 371 self.tee.flush()
378 372 setattr(sys, self.channel, self.orig_stream)
379 373 printed = self.buffer.getvalue()
380 374 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
381 375 return False
382 376
383 377 @contextmanager
384 378 def mute_warn():
385 379 from IPython.utils import warn
386 380 save_warn = warn.warn
387 381 warn.warn = lambda *a, **kw: None
388 382 try:
389 383 yield
390 384 finally:
391 385 warn.warn = save_warn
392 386
393 387 @contextmanager
394 388 def make_tempfile(name):
395 389 """ Create an empty, named, temporary file for the duration of the context.
396 390 """
397 391 f = open(name, 'w')
398 392 f.close()
399 393 try:
400 394 yield
401 395 finally:
402 396 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now