From 4bd8010e606cbffabdc2b291780e2caa3d2c539d 2010-07-20 18:26:36 From: Fernando Perez Date: 2010-07-20 18:26:36 Subject: [PATCH] Fix bug where traceback wasn't being correctly stored in remote tasks. Reported by Chris Filo Gorgolewski on the list: http://mail.scipy.org/pipermail/ipython-user/2010-July/006993.html Note regarding the tests: it turns out that chaining too much info into the same deferred produces rather weird side-effects. So I broke up the test into several independent ones with separate deferreds. This fix was done in the 0.10.1 branch as separate commits over time, rebased here into a single, clean one. --- diff --git a/IPython/kernel/engineservice.py b/IPython/kernel/engineservice.py index 68604cf..3083770 100644 --- a/IPython/kernel/engineservice.py +++ b/IPython/kernel/engineservice.py @@ -390,7 +390,7 @@ class EngineService(object, service.Service): et,ev,tb = self.shell.format_traceback(et,ev,tb,msg) # Add another attribute ev._ipython_engine_info = msg - f = failure.Failure(ev,et,None) + f = failure.Failure(ev,et,tb) d.errback(f) else: d.callback(result) diff --git a/IPython/kernel/tests/tasktest.py b/IPython/kernel/tests/tasktest.py old mode 100644 new mode 100755 index bddc8aa..d8392a8 --- a/IPython/kernel/tests/tasktest.py +++ b/IPython/kernel/tests/tasktest.py @@ -185,3 +185,63 @@ class ITaskControllerTestCase(TaskTestBase): d.addCallback(lambda _: self.tc.get_task_result(0, block=True)) d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException)) return d + + def get_traceback_frames(self, result): + """Execute a failing string as a task and return stack frame strings. + + This lets us check that the returned exceptions contain as many stack + frames as the user expects from his code. + + Parameters + ---------- + d : deferred + + src : string + Code to be executed, should fail.""" + # This gets Twisted's short-format traceback and picks the info for + # frames that actually belong to user code. + return result.failure.getBriefTraceback().split('\n:')[1:] + + + def check_traceback(self, cmd, nframes, exception=IOError): + """Ensure that we have a traceback object in task failures.""" + + self.addEngine(1) + t1 = task.StringTask(cmd) + d = self.tc.run(t1) + d.addCallback(self.tc.get_task_result, block=True) + # Sanity check, that the right exception is raised + d.addCallback(lambda r: self.assertRaises(exception, r.raise_exception)) + # Rerun the same task, this time we check for the traceback to have the + # right number of frames + d.addCallback(lambda r: self.tc.run(t1)) + d.addCallback(self.tc.get_task_result, block=True) + d.addCallback(self.get_traceback_frames) + d.addCallback(lambda frames: self.assertEquals(len(frames), nframes)) + return d + + # Check traceback structure with 2 and 4 frame-deep stacks + def test_traceback(self): + cmd = """ +def fail(): + raise IOError('failure test') + +result = fail() +""" + return self.check_traceback(cmd, 2) + + + def test_traceback2(self): + cmd = """ +def boom(): + raise IOError('failure test') + +def crash(): + boom() + +def fail(): + crash() + +result = fail() +""" + return self.check_traceback(cmd, 4)