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