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