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