##// END OF EJS Templates
Use argument lists for testing command help output....
Thomas Kluyver -
Show More
@@ -1,446 +1,446 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 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
156 156 config.HistoryManager.db_cache_size = 10000
157 157 return config
158 158
159 159
160 160 def get_ipython_cmd(as_string=False):
161 161 """
162 162 Return appropriate IPython command line name. By default, this will return
163 163 a list that can be used with subprocess.Popen, for example, but passing
164 164 `as_string=True` allows for returning the IPython command as a string.
165 165
166 166 Parameters
167 167 ----------
168 168 as_string: bool
169 169 Flag to allow to return the command as a string.
170 170 """
171 171 ipython_cmd = [sys.executable, "-m", "IPython"]
172 172
173 173 if as_string:
174 174 ipython_cmd = " ".join(ipython_cmd)
175 175
176 176 return ipython_cmd
177 177
178 178 def ipexec(fname, options=None):
179 179 """Utility to call 'ipython filename'.
180 180
181 181 Starts IPython with a minimal and safe configuration to make startup as fast
182 182 as possible.
183 183
184 184 Note that this starts IPython in a subprocess!
185 185
186 186 Parameters
187 187 ----------
188 188 fname : str
189 189 Name of file to be executed (should have .py or .ipy extension).
190 190
191 191 options : optional, list
192 192 Extra command-line flags to be passed to IPython.
193 193
194 194 Returns
195 195 -------
196 196 (stdout, stderr) of ipython subprocess.
197 197 """
198 198 if options is None: options = []
199 199
200 200 # For these subprocess calls, eliminate all prompt printing so we only see
201 201 # output from script execution
202 202 prompt_opts = [ '--PromptManager.in_template=""',
203 203 '--PromptManager.in2_template=""',
204 204 '--PromptManager.out_template=""'
205 205 ]
206 206 cmdargs = default_argv() + prompt_opts + options
207 207
208 208 test_dir = os.path.dirname(__file__)
209 209
210 210 ipython_cmd = get_ipython_cmd()
211 211 # Absolute path for filename
212 212 full_fname = os.path.join(test_dir, fname)
213 213 full_cmd = ipython_cmd + cmdargs + [full_fname]
214 214 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
215 215 out, err = p.communicate()
216 216 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
217 217 # `import readline` causes 'ESC[?1034h' to be output sometimes,
218 218 # so strip that out before doing comparisons
219 219 if out:
220 220 out = re.sub(r'\x1b\[[^h]+h', '', out)
221 221 return out, err
222 222
223 223
224 224 def ipexec_validate(fname, expected_out, expected_err='',
225 225 options=None):
226 226 """Utility to call 'ipython filename' and validate output/error.
227 227
228 228 This function raises an AssertionError if the validation fails.
229 229
230 230 Note that this starts IPython in a subprocess!
231 231
232 232 Parameters
233 233 ----------
234 234 fname : str
235 235 Name of the file to be executed (should have .py or .ipy extension).
236 236
237 237 expected_out : str
238 238 Expected stdout of the process.
239 239
240 240 expected_err : optional, str
241 241 Expected stderr of the process.
242 242
243 243 options : optional, list
244 244 Extra command-line flags to be passed to IPython.
245 245
246 246 Returns
247 247 -------
248 248 None
249 249 """
250 250
251 251 import nose.tools as nt
252 252
253 253 out, err = ipexec(fname, options)
254 254 #print 'OUT', out # dbg
255 255 #print 'ERR', err # dbg
256 256 # If there are any errors, we must check those befor stdout, as they may be
257 257 # more informative than simply having an empty stdout.
258 258 if err:
259 259 if expected_err:
260 260 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
261 261 else:
262 262 raise ValueError('Running file %r produced error: %r' %
263 263 (fname, err))
264 264 # If no errors or output on stderr was expected, match stdout
265 265 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
266 266
267 267
268 268 class TempFileMixin(object):
269 269 """Utility class to create temporary Python/IPython files.
270 270
271 271 Meant as a mixin class for test cases."""
272 272
273 273 def mktmp(self, src, ext='.py'):
274 274 """Make a valid python temp file."""
275 275 fname, f = temp_pyfile(src, ext)
276 276 self.tmpfile = f
277 277 self.fname = fname
278 278
279 279 def tearDown(self):
280 280 if hasattr(self, 'tmpfile'):
281 281 # If the tmpfile wasn't made because of skipped tests, like in
282 282 # win32, there's nothing to cleanup.
283 283 self.tmpfile.close()
284 284 try:
285 285 os.unlink(self.fname)
286 286 except:
287 287 # On Windows, even though we close the file, we still can't
288 288 # delete it. I have no clue why
289 289 pass
290 290
291 291 pair_fail_msg = ("Testing {0}\n\n"
292 292 "In:\n"
293 293 " {1!r}\n"
294 294 "Expected:\n"
295 295 " {2!r}\n"
296 296 "Got:\n"
297 297 " {3!r}\n")
298 298 def check_pairs(func, pairs):
299 299 """Utility function for the common case of checking a function with a
300 300 sequence of input/output pairs.
301 301
302 302 Parameters
303 303 ----------
304 304 func : callable
305 305 The function to be tested. Should accept a single argument.
306 306 pairs : iterable
307 307 A list of (input, expected_output) tuples.
308 308
309 309 Returns
310 310 -------
311 311 None. Raises an AssertionError if any output does not match the expected
312 312 value.
313 313 """
314 314 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
315 315 for inp, expected in pairs:
316 316 out = func(inp)
317 317 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
318 318
319 319
320 320 if py3compat.PY3:
321 321 MyStringIO = StringIO
322 322 else:
323 323 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
324 324 # so we need a class that can handle both.
325 325 class MyStringIO(StringIO):
326 326 def write(self, s):
327 327 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
328 328 super(MyStringIO, self).write(s)
329 329
330 330 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
331 331 -------
332 332 {2!s}
333 333 -------
334 334 """
335 335
336 336 class AssertPrints(object):
337 337 """Context manager for testing that code prints certain text.
338 338
339 339 Examples
340 340 --------
341 341 >>> with AssertPrints("abc", suppress=False):
342 342 ... print("abcd")
343 343 ... print("def")
344 344 ...
345 345 abcd
346 346 def
347 347 """
348 348 def __init__(self, s, channel='stdout', suppress=True):
349 349 self.s = s
350 350 if isinstance(self.s, py3compat.string_types):
351 351 self.s = [self.s]
352 352 self.channel = channel
353 353 self.suppress = suppress
354 354
355 355 def __enter__(self):
356 356 self.orig_stream = getattr(sys, self.channel)
357 357 self.buffer = MyStringIO()
358 358 self.tee = Tee(self.buffer, channel=self.channel)
359 359 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
360 360
361 361 def __exit__(self, etype, value, traceback):
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 assert s in printed, notprinted_msg.format(s, self.channel, printed)
370 370 return False
371 371
372 372 printed_msg = """Found {0!r} in printed output (on {1}):
373 373 -------
374 374 {2!s}
375 375 -------
376 376 """
377 377
378 378 class AssertNotPrints(AssertPrints):
379 379 """Context manager for checking that certain output *isn't* produced.
380 380
381 381 Counterpart of AssertPrints"""
382 382 def __exit__(self, etype, value, traceback):
383 383 if value is not None:
384 384 # If an error was raised, don't check anything else
385 385 return False
386 386 self.tee.flush()
387 387 setattr(sys, self.channel, self.orig_stream)
388 388 printed = self.buffer.getvalue()
389 389 for s in self.s:
390 390 assert s not in printed, printed_msg.format(s, self.channel, printed)
391 391 return False
392 392
393 393 @contextmanager
394 394 def mute_warn():
395 395 from IPython.utils import warn
396 396 save_warn = warn.warn
397 397 warn.warn = lambda *a, **kw: None
398 398 try:
399 399 yield
400 400 finally:
401 401 warn.warn = save_warn
402 402
403 403 @contextmanager
404 404 def make_tempfile(name):
405 405 """ Create an empty, named, temporary file for the duration of the context.
406 406 """
407 407 f = open(name, 'w')
408 408 f.close()
409 409 try:
410 410 yield
411 411 finally:
412 412 os.unlink(name)
413 413
414 414
415 415 @contextmanager
416 416 def monkeypatch(obj, name, attr):
417 417 """
418 418 Context manager to replace attribute named `name` in `obj` with `attr`.
419 419 """
420 420 orig = getattr(obj, name)
421 421 setattr(obj, name, attr)
422 422 yield
423 423 setattr(obj, name, orig)
424 424
425 425
426 426 def help_output_test(subcommand=''):
427 427 """test that `ipython [subcommand] -h` works"""
428 cmd = ' '.join(get_ipython_cmd() + [subcommand, '-h'])
428 cmd = get_ipython_cmd() + [subcommand, '-h']
429 429 out, err, rc = get_output_error_code(cmd)
430 430 nt.assert_equal(rc, 0, err)
431 431 nt.assert_not_in("Traceback", err)
432 432 nt.assert_in("Options", out)
433 433 nt.assert_in("--help-all", out)
434 434 return out, err
435 435
436 436
437 437 def help_all_output_test(subcommand=''):
438 438 """test that `ipython [subcommand] --help-all` works"""
439 cmd = ' '.join(get_ipython_cmd() + [subcommand, '--help-all'])
439 cmd = get_ipython_cmd() + [subcommand, '--help-all']
440 440 out, err, rc = get_output_error_code(cmd)
441 441 nt.assert_equal(rc, 0, err)
442 442 nt.assert_not_in("Traceback", err)
443 443 nt.assert_in("Options", out)
444 444 nt.assert_in("Class parameters", out)
445 445 return out, err
446 446
General Comments 0
You need to be logged in to leave comments. Login now