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