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