##// END OF EJS Templates
suppress warnings in ipexec tests...
Min RK -
Show More
@@ -1,495 +1,498 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8 from __future__ import absolute_import
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2009 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 22 import re
23 23 import sys
24 24 import tempfile
25 25
26 26 from contextlib import contextmanager
27 27 from io import StringIO
28 28 from subprocess import Popen, PIPE
29 29
30 30 try:
31 31 # These tools are used by parts of the runtime, so we make the nose
32 32 # dependency optional at this point. Nose is a hard dependency to run the
33 33 # test suite, but NOT to use ipython itself.
34 34 import nose.tools as nt
35 35 has_nose = True
36 36 except ImportError:
37 37 has_nose = False
38 38
39 39 from IPython.config.loader import Config
40 40 from IPython.utils.process import get_output_error_code
41 41 from IPython.utils.text import list_strings
42 42 from IPython.utils.io import temp_pyfile, Tee
43 43 from IPython.utils import py3compat
44 44 from IPython.utils.encoding import DEFAULT_ENCODING
45 45
46 46 from . import decorators as dec
47 47 from . import skipdoctest
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Functions and classes
51 51 #-----------------------------------------------------------------------------
52 52
53 53 # The docstring for full_path doctests differently on win32 (different path
54 54 # separator) so just skip the doctest there. The example remains informative.
55 55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
56 56
57 57 @doctest_deco
58 58 def full_path(startPath,files):
59 59 """Make full paths for all the listed files, based on startPath.
60 60
61 61 Only the base part of startPath is kept, since this routine is typically
62 62 used with a script's ``__file__`` variable as startPath. The base of startPath
63 63 is then prepended to all the listed files, forming the output list.
64 64
65 65 Parameters
66 66 ----------
67 67 startPath : string
68 68 Initial path to use as the base for the results. This path is split
69 69 using os.path.split() and only its first component is kept.
70 70
71 71 files : string or list
72 72 One or more files.
73 73
74 74 Examples
75 75 --------
76 76
77 77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 78 ['/foo/a.txt', '/foo/b.txt']
79 79
80 80 >>> full_path('/foo',['a.txt','b.txt'])
81 81 ['/a.txt', '/b.txt']
82 82
83 83 If a single file is given, the output is still a list::
84 84
85 85 >>> full_path('/foo','a.txt')
86 86 ['/a.txt']
87 87 """
88 88
89 89 files = list_strings(files)
90 90 base = os.path.split(startPath)[0]
91 91 return [ os.path.join(base,f) for f in files ]
92 92
93 93
94 94 def parse_test_output(txt):
95 95 """Parse the output of a test run and return errors, failures.
96 96
97 97 Parameters
98 98 ----------
99 99 txt : str
100 100 Text output of a test run, assumed to contain a line of one of the
101 101 following forms::
102 102
103 103 'FAILED (errors=1)'
104 104 'FAILED (failures=1)'
105 105 'FAILED (errors=1, failures=1)'
106 106
107 107 Returns
108 108 -------
109 109 nerr, nfail
110 110 number of errors and failures.
111 111 """
112 112
113 113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
114 114 if err_m:
115 115 nerr = int(err_m.group(1))
116 116 nfail = 0
117 117 return nerr, nfail
118 118
119 119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
120 120 if fail_m:
121 121 nerr = 0
122 122 nfail = int(fail_m.group(1))
123 123 return nerr, nfail
124 124
125 125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
126 126 re.MULTILINE)
127 127 if both_m:
128 128 nerr = int(both_m.group(1))
129 129 nfail = int(both_m.group(2))
130 130 return nerr, nfail
131 131
132 132 # If the input didn't match any of these forms, assume no error/failures
133 133 return 0, 0
134 134
135 135
136 136 # So nose doesn't think this is a test
137 137 parse_test_output.__test__ = False
138 138
139 139
140 140 def default_argv():
141 141 """Return a valid default argv for creating testing instances of ipython"""
142 142
143 143 return ['--quick', # so no config file is loaded
144 144 # Other defaults to minimize side effects on stdout
145 145 '--colors=NoColor', '--no-term-title','--no-banner',
146 146 '--autocall=0']
147 147
148 148
149 149 def default_config():
150 150 """Return a config object with good defaults for testing."""
151 151 config = Config()
152 152 config.TerminalInteractiveShell.colors = 'NoColor'
153 153 config.TerminalTerminalInteractiveShell.term_title = False,
154 154 config.TerminalInteractiveShell.autocall = 0
155 155 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
156 156 config.HistoryManager.hist_file = f.name
157 157 f.close()
158 158 config.HistoryManager.db_cache_size = 10000
159 159 return config
160 160
161 161
162 162 def get_ipython_cmd(as_string=False):
163 163 """
164 164 Return appropriate IPython command line name. By default, this will return
165 165 a list that can be used with subprocess.Popen, for example, but passing
166 166 `as_string=True` allows for returning the IPython command as a string.
167 167
168 168 Parameters
169 169 ----------
170 170 as_string: bool
171 171 Flag to allow to return the command as a string.
172 172 """
173 173 ipython_cmd = [sys.executable, "-m", "IPython"]
174 174
175 175 if as_string:
176 176 ipython_cmd = " ".join(ipython_cmd)
177 177
178 178 return ipython_cmd
179 179
180 180 def ipexec(fname, options=None, commands=()):
181 181 """Utility to call 'ipython filename'.
182 182
183 183 Starts IPython with a minimal and safe configuration to make startup as fast
184 184 as possible.
185 185
186 186 Note that this starts IPython in a subprocess!
187 187
188 188 Parameters
189 189 ----------
190 190 fname : str
191 191 Name of file to be executed (should have .py or .ipy extension).
192 192
193 193 options : optional, list
194 194 Extra command-line flags to be passed to IPython.
195 195
196 196 commands : optional, list
197 197 Commands to send in on stdin
198 198
199 199 Returns
200 200 -------
201 201 (stdout, stderr) of ipython subprocess.
202 202 """
203 203 if options is None: options = []
204 204
205 205 # For these subprocess calls, eliminate all prompt printing so we only see
206 206 # output from script execution
207 207 prompt_opts = [ '--PromptManager.in_template=""',
208 208 '--PromptManager.in2_template=""',
209 209 '--PromptManager.out_template=""'
210 210 ]
211 211 cmdargs = default_argv() + prompt_opts + options
212 212
213 213 test_dir = os.path.dirname(__file__)
214 214
215 215 ipython_cmd = get_ipython_cmd()
216 216 # Absolute path for filename
217 217 full_fname = os.path.join(test_dir, fname)
218 218 full_cmd = ipython_cmd + cmdargs + [full_fname]
219 219 env = os.environ.copy()
220 env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
220 # FIXME: ignore all warnings in ipexec while we have shims
221 # should we keep suppressing warnings here, even after removing shims?
222 env['PYTHONWARNINGS'] = 'ignore'
223 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
221 224 for k, v in env.items():
222 225 # Debug a bizarre failure we've seen on Windows:
223 226 # TypeError: environment can only contain strings
224 227 if not isinstance(v, str):
225 228 print(k, v)
226 229 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
227 230 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
228 231 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
229 232 # `import readline` causes 'ESC[?1034h' to be output sometimes,
230 233 # so strip that out before doing comparisons
231 234 if out:
232 235 out = re.sub(r'\x1b\[[^h]+h', '', out)
233 236 return out, err
234 237
235 238
236 239 def ipexec_validate(fname, expected_out, expected_err='',
237 240 options=None, commands=()):
238 241 """Utility to call 'ipython filename' and validate output/error.
239 242
240 243 This function raises an AssertionError if the validation fails.
241 244
242 245 Note that this starts IPython in a subprocess!
243 246
244 247 Parameters
245 248 ----------
246 249 fname : str
247 250 Name of the file to be executed (should have .py or .ipy extension).
248 251
249 252 expected_out : str
250 253 Expected stdout of the process.
251 254
252 255 expected_err : optional, str
253 256 Expected stderr of the process.
254 257
255 258 options : optional, list
256 259 Extra command-line flags to be passed to IPython.
257 260
258 261 Returns
259 262 -------
260 263 None
261 264 """
262 265
263 266 import nose.tools as nt
264 267
265 268 out, err = ipexec(fname, options, commands)
266 269 #print 'OUT', out # dbg
267 270 #print 'ERR', err # dbg
268 271 # If there are any errors, we must check those befor stdout, as they may be
269 272 # more informative than simply having an empty stdout.
270 273 if err:
271 274 if expected_err:
272 275 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
273 276 else:
274 277 raise ValueError('Running file %r produced error: %r' %
275 278 (fname, err))
276 279 # If no errors or output on stderr was expected, match stdout
277 280 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
278 281
279 282
280 283 class TempFileMixin(object):
281 284 """Utility class to create temporary Python/IPython files.
282 285
283 286 Meant as a mixin class for test cases."""
284 287
285 288 def mktmp(self, src, ext='.py'):
286 289 """Make a valid python temp file."""
287 290 fname, f = temp_pyfile(src, ext)
288 291 self.tmpfile = f
289 292 self.fname = fname
290 293
291 294 def tearDown(self):
292 295 if hasattr(self, 'tmpfile'):
293 296 # If the tmpfile wasn't made because of skipped tests, like in
294 297 # win32, there's nothing to cleanup.
295 298 self.tmpfile.close()
296 299 try:
297 300 os.unlink(self.fname)
298 301 except:
299 302 # On Windows, even though we close the file, we still can't
300 303 # delete it. I have no clue why
301 304 pass
302 305
303 306 pair_fail_msg = ("Testing {0}\n\n"
304 307 "In:\n"
305 308 " {1!r}\n"
306 309 "Expected:\n"
307 310 " {2!r}\n"
308 311 "Got:\n"
309 312 " {3!r}\n")
310 313 def check_pairs(func, pairs):
311 314 """Utility function for the common case of checking a function with a
312 315 sequence of input/output pairs.
313 316
314 317 Parameters
315 318 ----------
316 319 func : callable
317 320 The function to be tested. Should accept a single argument.
318 321 pairs : iterable
319 322 A list of (input, expected_output) tuples.
320 323
321 324 Returns
322 325 -------
323 326 None. Raises an AssertionError if any output does not match the expected
324 327 value.
325 328 """
326 329 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
327 330 for inp, expected in pairs:
328 331 out = func(inp)
329 332 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
330 333
331 334
332 335 if py3compat.PY3:
333 336 MyStringIO = StringIO
334 337 else:
335 338 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
336 339 # so we need a class that can handle both.
337 340 class MyStringIO(StringIO):
338 341 def write(self, s):
339 342 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
340 343 super(MyStringIO, self).write(s)
341 344
342 345 _re_type = type(re.compile(r''))
343 346
344 347 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
345 348 -------
346 349 {2!s}
347 350 -------
348 351 """
349 352
350 353 class AssertPrints(object):
351 354 """Context manager for testing that code prints certain text.
352 355
353 356 Examples
354 357 --------
355 358 >>> with AssertPrints("abc", suppress=False):
356 359 ... print("abcd")
357 360 ... print("def")
358 361 ...
359 362 abcd
360 363 def
361 364 """
362 365 def __init__(self, s, channel='stdout', suppress=True):
363 366 self.s = s
364 367 if isinstance(self.s, (py3compat.string_types, _re_type)):
365 368 self.s = [self.s]
366 369 self.channel = channel
367 370 self.suppress = suppress
368 371
369 372 def __enter__(self):
370 373 self.orig_stream = getattr(sys, self.channel)
371 374 self.buffer = MyStringIO()
372 375 self.tee = Tee(self.buffer, channel=self.channel)
373 376 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
374 377
375 378 def __exit__(self, etype, value, traceback):
376 379 try:
377 380 if value is not None:
378 381 # If an error was raised, don't check anything else
379 382 return False
380 383 self.tee.flush()
381 384 setattr(sys, self.channel, self.orig_stream)
382 385 printed = self.buffer.getvalue()
383 386 for s in self.s:
384 387 if isinstance(s, _re_type):
385 388 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
386 389 else:
387 390 assert s in printed, notprinted_msg.format(s, self.channel, printed)
388 391 return False
389 392 finally:
390 393 self.tee.close()
391 394
392 395 printed_msg = """Found {0!r} in printed output (on {1}):
393 396 -------
394 397 {2!s}
395 398 -------
396 399 """
397 400
398 401 class AssertNotPrints(AssertPrints):
399 402 """Context manager for checking that certain output *isn't* produced.
400 403
401 404 Counterpart of AssertPrints"""
402 405 def __exit__(self, etype, value, traceback):
403 406 try:
404 407 if value is not None:
405 408 # If an error was raised, don't check anything else
406 409 self.tee.close()
407 410 return False
408 411 self.tee.flush()
409 412 setattr(sys, self.channel, self.orig_stream)
410 413 printed = self.buffer.getvalue()
411 414 for s in self.s:
412 415 if isinstance(s, _re_type):
413 416 assert not s.search(printed),printed_msg.format(
414 417 s.pattern, self.channel, printed)
415 418 else:
416 419 assert s not in printed, printed_msg.format(
417 420 s, self.channel, printed)
418 421 return False
419 422 finally:
420 423 self.tee.close()
421 424
422 425 @contextmanager
423 426 def mute_warn():
424 427 from IPython.utils import warn
425 428 save_warn = warn.warn
426 429 warn.warn = lambda *a, **kw: None
427 430 try:
428 431 yield
429 432 finally:
430 433 warn.warn = save_warn
431 434
432 435 @contextmanager
433 436 def make_tempfile(name):
434 437 """ Create an empty, named, temporary file for the duration of the context.
435 438 """
436 439 f = open(name, 'w')
437 440 f.close()
438 441 try:
439 442 yield
440 443 finally:
441 444 os.unlink(name)
442 445
443 446
444 447 @contextmanager
445 448 def monkeypatch(obj, name, attr):
446 449 """
447 450 Context manager to replace attribute named `name` in `obj` with `attr`.
448 451 """
449 452 orig = getattr(obj, name)
450 453 setattr(obj, name, attr)
451 454 yield
452 455 setattr(obj, name, orig)
453 456
454 457
455 458 def help_output_test(subcommand=''):
456 459 """test that `ipython [subcommand] -h` works"""
457 460 cmd = get_ipython_cmd() + [subcommand, '-h']
458 461 out, err, rc = get_output_error_code(cmd)
459 462 nt.assert_equal(rc, 0, err)
460 463 nt.assert_not_in("Traceback", err)
461 464 nt.assert_in("Options", out)
462 465 nt.assert_in("--help-all", out)
463 466 return out, err
464 467
465 468
466 469 def help_all_output_test(subcommand=''):
467 470 """test that `ipython [subcommand] --help-all` works"""
468 471 cmd = get_ipython_cmd() + [subcommand, '--help-all']
469 472 out, err, rc = get_output_error_code(cmd)
470 473 nt.assert_equal(rc, 0, err)
471 474 nt.assert_not_in("Traceback", err)
472 475 nt.assert_in("Options", out)
473 476 nt.assert_in("Class parameters", out)
474 477 return out, err
475 478
476 479 def assert_big_text_equal(a, b, chunk_size=80):
477 480 """assert that large strings are equal
478 481
479 482 Zooms in on first chunk that differs,
480 483 to give better info than vanilla assertEqual for large text blobs.
481 484 """
482 485 for i in range(0, len(a), chunk_size):
483 486 chunk_a = a[i:i + chunk_size]
484 487 chunk_b = b[i:i + chunk_size]
485 488 nt.assert_equal(chunk_a, chunk_b, "[offset: %i]\n%r != \n%r" % (
486 489 i, chunk_a, chunk_b))
487 490
488 491 if len(a) > len(b):
489 492 nt.fail("Length doesn't match (%i > %i). Extra text:\n%r" % (
490 493 len(a), len(b), a[len(b):]
491 494 ))
492 495 elif len(a) < len(b):
493 496 nt.fail("Length doesn't match (%i < %i). Extra text:\n%r" % (
494 497 len(a), len(b), b[len(a):]
495 498 ))
General Comments 0
You need to be logged in to leave comments. Login now