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