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