##// END OF EJS Templates
relax help_all output check...
Min RK -
Show More
@@ -1,468 +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 325 MyStringIO = StringIO
326 326
327 327 _re_type = type(re.compile(r''))
328 328
329 329 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
330 330 -------
331 331 {2!s}
332 332 -------
333 333 """
334 334
335 335 class AssertPrints(object):
336 336 """Context manager for testing that code prints certain text.
337 337
338 338 Examples
339 339 --------
340 340 >>> with AssertPrints("abc", suppress=False):
341 341 ... print("abcd")
342 342 ... print("def")
343 343 ...
344 344 abcd
345 345 def
346 346 """
347 347 def __init__(self, s, channel='stdout', suppress=True):
348 348 self.s = s
349 349 if isinstance(self.s, (str, _re_type)):
350 350 self.s = [self.s]
351 351 self.channel = channel
352 352 self.suppress = suppress
353 353
354 354 def __enter__(self):
355 355 self.orig_stream = getattr(sys, self.channel)
356 356 self.buffer = MyStringIO()
357 357 self.tee = Tee(self.buffer, channel=self.channel)
358 358 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
359 359
360 360 def __exit__(self, etype, value, traceback):
361 361 try:
362 362 if value is not None:
363 363 # If an error was raised, don't check anything else
364 364 return False
365 365 self.tee.flush()
366 366 setattr(sys, self.channel, self.orig_stream)
367 367 printed = self.buffer.getvalue()
368 368 for s in self.s:
369 369 if isinstance(s, _re_type):
370 370 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
371 371 else:
372 372 assert s in printed, notprinted_msg.format(s, self.channel, printed)
373 373 return False
374 374 finally:
375 375 self.tee.close()
376 376
377 377 printed_msg = """Found {0!r} in printed output (on {1}):
378 378 -------
379 379 {2!s}
380 380 -------
381 381 """
382 382
383 383 class AssertNotPrints(AssertPrints):
384 384 """Context manager for checking that certain output *isn't* produced.
385 385
386 386 Counterpart of AssertPrints"""
387 387 def __exit__(self, etype, value, traceback):
388 388 try:
389 389 if value is not None:
390 390 # If an error was raised, don't check anything else
391 391 self.tee.close()
392 392 return False
393 393 self.tee.flush()
394 394 setattr(sys, self.channel, self.orig_stream)
395 395 printed = self.buffer.getvalue()
396 396 for s in self.s:
397 397 if isinstance(s, _re_type):
398 398 assert not s.search(printed),printed_msg.format(
399 399 s.pattern, self.channel, printed)
400 400 else:
401 401 assert s not in printed, printed_msg.format(
402 402 s, self.channel, printed)
403 403 return False
404 404 finally:
405 405 self.tee.close()
406 406
407 407 @contextmanager
408 408 def mute_warn():
409 409 from IPython.utils import warn
410 410 save_warn = warn.warn
411 411 warn.warn = lambda *a, **kw: None
412 412 try:
413 413 yield
414 414 finally:
415 415 warn.warn = save_warn
416 416
417 417 @contextmanager
418 418 def make_tempfile(name):
419 419 """ Create an empty, named, temporary file for the duration of the context.
420 420 """
421 421 f = open(name, 'w')
422 422 f.close()
423 423 try:
424 424 yield
425 425 finally:
426 426 os.unlink(name)
427 427
428 428 def fake_input(inputs):
429 429 """Temporarily replace the input() function to return the given values
430 430
431 431 Use as a context manager:
432 432
433 433 with fake_input(['result1', 'result2']):
434 434 ...
435 435
436 436 Values are returned in order. If input() is called again after the last value
437 437 was used, EOFError is raised.
438 438 """
439 439 it = iter(inputs)
440 440 def mock_input(prompt=''):
441 441 try:
442 442 return next(it)
443 443 except StopIteration:
444 444 raise EOFError('No more inputs given')
445 445
446 446 return patch('builtins.input', mock_input)
447 447
448 448 def help_output_test(subcommand=''):
449 449 """test that `ipython [subcommand] -h` works"""
450 450 cmd = get_ipython_cmd() + [subcommand, '-h']
451 451 out, err, rc = get_output_error_code(cmd)
452 452 nt.assert_equal(rc, 0, err)
453 453 nt.assert_not_in("Traceback", err)
454 454 nt.assert_in("Options", out)
455 455 nt.assert_in("--help-all", out)
456 456 return out, err
457 457
458 458
459 459 def help_all_output_test(subcommand=''):
460 460 """test that `ipython [subcommand] --help-all` works"""
461 461 cmd = get_ipython_cmd() + [subcommand, '--help-all']
462 462 out, err, rc = get_output_error_code(cmd)
463 463 nt.assert_equal(rc, 0, err)
464 464 nt.assert_not_in("Traceback", err)
465 465 nt.assert_in("Options", out)
466 nt.assert_in("Class parameters", out)
466 nt.assert_in("Class", out)
467 467 return out, err
468 468
General Comments 0
You need to be logged in to leave comments. Login now