diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index fab2bb2..1d7ae51 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -108,8 +108,33 @@ class IndentationErrorTest(unittest.TestCase): with tt.AssertPrints("zoon()", suppress=False): ip.magic('run %s' % fname) +se_file_1 = """1 +2 +7/ +""" + +se_file_2 = """7/ +""" + class SyntaxErrorTest(unittest.TestCase): def test_syntaxerror_without_lineno(self): with tt.AssertNotPrints("TypeError"): with tt.AssertPrints("line unknown"): ip.run_cell("raise SyntaxError()") + + def test_changing_py_file(self): + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, 'w') as f: + f.write(se_file_1) + + with tt.AssertPrints(["7/", "SyntaxError"]): + ip.magic("run " + fname) + + # Modify the file + with open(fname, 'w') as f: + f.write(se_file_2) + + # The SyntaxError should point to the correct line + with tt.AssertPrints(["7/", "SyntaxError"]): + ip.magic("run " + fname) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 4261339..846d1a1 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -1193,6 +1193,20 @@ class SyntaxTB(ListTB): self.last_syntax_error = value ListTB.__call__(self,etype,value,elist) + def structured_traceback(self, etype, value, elist, tb_offset=None, + context=5): + # If the source file has been edited, the line in the syntax error can + # be wrong (retrieved from an outdated cache). This replaces it with + # the current value. + if isinstance(value.filename, py3compat.string_types) \ + and isinstance(value.lineno, int): + linecache.checkcache(value.filename) + newtext = ulinecache.getline(value.filename, value.lineno) + if newtext: + value.text = newtext + return super(SyntaxTB, self).structured_traceback(etype, value, elist, + tb_offset=tb_offset, context=context) + def clear_err_state(self): """Return the current error state and clear it""" e = self.last_syntax_error diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 0bd3f7a..4732e1c 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -18,7 +18,6 @@ from __future__ import absolute_import # Imports #----------------------------------------------------------------------------- -import inspect import os import re import sys @@ -350,6 +349,8 @@ class AssertPrints(object): """ def __init__(self, s, channel='stdout', suppress=True): self.s = s + if isinstance(self.s, py3compat.string_types): + self.s = [self.s] self.channel = channel self.suppress = suppress @@ -363,7 +364,8 @@ class AssertPrints(object): self.tee.flush() setattr(sys, self.channel, self.orig_stream) printed = self.buffer.getvalue() - assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed) + for s in self.s: + assert s in printed, notprinted_msg.format(s, self.channel, printed) return False printed_msg = """Found {0!r} in printed output (on {1}): @@ -380,7 +382,8 @@ class AssertNotPrints(AssertPrints): self.tee.flush() setattr(sys, self.channel, self.orig_stream) printed = self.buffer.getvalue() - assert self.s not in printed, printed_msg.format(self.s, self.channel, printed) + for s in self.s: + assert s not in printed, printed_msg.format(s, self.channel, printed) return False @contextmanager