##// END OF EJS Templates
Add debugging print for test failure on Windows
Thomas Kluyver -
Show More
@@ -1,490 +1,495 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 220 env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
221 for k, v in env.items():
222 # Debug a bizarre failure we've seen on Windows:
223 # TypeError: environment can only contain strings
224 if not isinstance(v, str):
225 print(v)
221 226 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
222 227 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
223 228 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
224 229 # `import readline` causes 'ESC[?1034h' to be output sometimes,
225 230 # so strip that out before doing comparisons
226 231 if out:
227 232 out = re.sub(r'\x1b\[[^h]+h', '', out)
228 233 return out, err
229 234
230 235
231 236 def ipexec_validate(fname, expected_out, expected_err='',
232 237 options=None, commands=()):
233 238 """Utility to call 'ipython filename' and validate output/error.
234 239
235 240 This function raises an AssertionError if the validation fails.
236 241
237 242 Note that this starts IPython in a subprocess!
238 243
239 244 Parameters
240 245 ----------
241 246 fname : str
242 247 Name of the file to be executed (should have .py or .ipy extension).
243 248
244 249 expected_out : str
245 250 Expected stdout of the process.
246 251
247 252 expected_err : optional, str
248 253 Expected stderr of the process.
249 254
250 255 options : optional, list
251 256 Extra command-line flags to be passed to IPython.
252 257
253 258 Returns
254 259 -------
255 260 None
256 261 """
257 262
258 263 import nose.tools as nt
259 264
260 265 out, err = ipexec(fname, options, commands)
261 266 #print 'OUT', out # dbg
262 267 #print 'ERR', err # dbg
263 268 # If there are any errors, we must check those befor stdout, as they may be
264 269 # more informative than simply having an empty stdout.
265 270 if err:
266 271 if expected_err:
267 272 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
268 273 else:
269 274 raise ValueError('Running file %r produced error: %r' %
270 275 (fname, err))
271 276 # If no errors or output on stderr was expected, match stdout
272 277 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
273 278
274 279
275 280 class TempFileMixin(object):
276 281 """Utility class to create temporary Python/IPython files.
277 282
278 283 Meant as a mixin class for test cases."""
279 284
280 285 def mktmp(self, src, ext='.py'):
281 286 """Make a valid python temp file."""
282 287 fname, f = temp_pyfile(src, ext)
283 288 self.tmpfile = f
284 289 self.fname = fname
285 290
286 291 def tearDown(self):
287 292 if hasattr(self, 'tmpfile'):
288 293 # If the tmpfile wasn't made because of skipped tests, like in
289 294 # win32, there's nothing to cleanup.
290 295 self.tmpfile.close()
291 296 try:
292 297 os.unlink(self.fname)
293 298 except:
294 299 # On Windows, even though we close the file, we still can't
295 300 # delete it. I have no clue why
296 301 pass
297 302
298 303 pair_fail_msg = ("Testing {0}\n\n"
299 304 "In:\n"
300 305 " {1!r}\n"
301 306 "Expected:\n"
302 307 " {2!r}\n"
303 308 "Got:\n"
304 309 " {3!r}\n")
305 310 def check_pairs(func, pairs):
306 311 """Utility function for the common case of checking a function with a
307 312 sequence of input/output pairs.
308 313
309 314 Parameters
310 315 ----------
311 316 func : callable
312 317 The function to be tested. Should accept a single argument.
313 318 pairs : iterable
314 319 A list of (input, expected_output) tuples.
315 320
316 321 Returns
317 322 -------
318 323 None. Raises an AssertionError if any output does not match the expected
319 324 value.
320 325 """
321 326 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
322 327 for inp, expected in pairs:
323 328 out = func(inp)
324 329 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
325 330
326 331
327 332 if py3compat.PY3:
328 333 MyStringIO = StringIO
329 334 else:
330 335 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
331 336 # so we need a class that can handle both.
332 337 class MyStringIO(StringIO):
333 338 def write(self, s):
334 339 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
335 340 super(MyStringIO, self).write(s)
336 341
337 342 _re_type = type(re.compile(r''))
338 343
339 344 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
340 345 -------
341 346 {2!s}
342 347 -------
343 348 """
344 349
345 350 class AssertPrints(object):
346 351 """Context manager for testing that code prints certain text.
347 352
348 353 Examples
349 354 --------
350 355 >>> with AssertPrints("abc", suppress=False):
351 356 ... print("abcd")
352 357 ... print("def")
353 358 ...
354 359 abcd
355 360 def
356 361 """
357 362 def __init__(self, s, channel='stdout', suppress=True):
358 363 self.s = s
359 364 if isinstance(self.s, (py3compat.string_types, _re_type)):
360 365 self.s = [self.s]
361 366 self.channel = channel
362 367 self.suppress = suppress
363 368
364 369 def __enter__(self):
365 370 self.orig_stream = getattr(sys, self.channel)
366 371 self.buffer = MyStringIO()
367 372 self.tee = Tee(self.buffer, channel=self.channel)
368 373 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
369 374
370 375 def __exit__(self, etype, value, traceback):
371 376 try:
372 377 if value is not None:
373 378 # If an error was raised, don't check anything else
374 379 return False
375 380 self.tee.flush()
376 381 setattr(sys, self.channel, self.orig_stream)
377 382 printed = self.buffer.getvalue()
378 383 for s in self.s:
379 384 if isinstance(s, _re_type):
380 385 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
381 386 else:
382 387 assert s in printed, notprinted_msg.format(s, self.channel, printed)
383 388 return False
384 389 finally:
385 390 self.tee.close()
386 391
387 392 printed_msg = """Found {0!r} in printed output (on {1}):
388 393 -------
389 394 {2!s}
390 395 -------
391 396 """
392 397
393 398 class AssertNotPrints(AssertPrints):
394 399 """Context manager for checking that certain output *isn't* produced.
395 400
396 401 Counterpart of AssertPrints"""
397 402 def __exit__(self, etype, value, traceback):
398 403 try:
399 404 if value is not None:
400 405 # If an error was raised, don't check anything else
401 406 self.tee.close()
402 407 return False
403 408 self.tee.flush()
404 409 setattr(sys, self.channel, self.orig_stream)
405 410 printed = self.buffer.getvalue()
406 411 for s in self.s:
407 412 if isinstance(s, _re_type):
408 413 assert not s.search(printed),printed_msg.format(
409 414 s.pattern, self.channel, printed)
410 415 else:
411 416 assert s not in printed, printed_msg.format(
412 417 s, self.channel, printed)
413 418 return False
414 419 finally:
415 420 self.tee.close()
416 421
417 422 @contextmanager
418 423 def mute_warn():
419 424 from IPython.utils import warn
420 425 save_warn = warn.warn
421 426 warn.warn = lambda *a, **kw: None
422 427 try:
423 428 yield
424 429 finally:
425 430 warn.warn = save_warn
426 431
427 432 @contextmanager
428 433 def make_tempfile(name):
429 434 """ Create an empty, named, temporary file for the duration of the context.
430 435 """
431 436 f = open(name, 'w')
432 437 f.close()
433 438 try:
434 439 yield
435 440 finally:
436 441 os.unlink(name)
437 442
438 443
439 444 @contextmanager
440 445 def monkeypatch(obj, name, attr):
441 446 """
442 447 Context manager to replace attribute named `name` in `obj` with `attr`.
443 448 """
444 449 orig = getattr(obj, name)
445 450 setattr(obj, name, attr)
446 451 yield
447 452 setattr(obj, name, orig)
448 453
449 454
450 455 def help_output_test(subcommand=''):
451 456 """test that `ipython [subcommand] -h` works"""
452 457 cmd = get_ipython_cmd() + [subcommand, '-h']
453 458 out, err, rc = get_output_error_code(cmd)
454 459 nt.assert_equal(rc, 0, err)
455 460 nt.assert_not_in("Traceback", err)
456 461 nt.assert_in("Options", out)
457 462 nt.assert_in("--help-all", out)
458 463 return out, err
459 464
460 465
461 466 def help_all_output_test(subcommand=''):
462 467 """test that `ipython [subcommand] --help-all` works"""
463 468 cmd = get_ipython_cmd() + [subcommand, '--help-all']
464 469 out, err, rc = get_output_error_code(cmd)
465 470 nt.assert_equal(rc, 0, err)
466 471 nt.assert_not_in("Traceback", err)
467 472 nt.assert_in("Options", out)
468 473 nt.assert_in("Class parameters", out)
469 474 return out, err
470 475
471 476 def assert_big_text_equal(a, b, chunk_size=80):
472 477 """assert that large strings are equal
473 478
474 479 Zooms in on first chunk that differs,
475 480 to give better info than vanilla assertEqual for large text blobs.
476 481 """
477 482 for i in range(0, len(a), chunk_size):
478 483 chunk_a = a[i:i + chunk_size]
479 484 chunk_b = b[i:i + chunk_size]
480 485 nt.assert_equal(chunk_a, chunk_b, "[offset: %i]\n%r != \n%r" % (
481 486 i, chunk_a, chunk_b))
482 487
483 488 if len(a) > len(b):
484 489 nt.fail("Length doesn't match (%i > %i). Extra text:\n%r" % (
485 490 len(a), len(b), a[len(b):]
486 491 ))
487 492 elif len(a) < len(b):
488 493 nt.fail("Length doesn't match (%i < %i). Extra text:\n%r" % (
489 494 len(a), len(b), b[len(a):]
490 495 ))
General Comments 0
You need to be logged in to leave comments. Login now