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