From 529df40f45906fd5534c8c3f97008d5bd0a1107a 2013-02-01 04:33:25 From: MinRK Date: 2013-02-01 04:33:25 Subject: [PATCH] truncate potentially long CompositeErrors If you have a typo in a task on 100 engines, the local exception will be a bit ridiculous. This defaults the truncation to 4 tracebacks (could be even fewer, or even one?), and you can set the tb_limit attribute of CompositeError to adjust this. Prompted by [this SO question](http://stackoverflow.com/questions/14635935/silence-or-condence-ipython-parallel-exceptions). --- diff --git a/IPython/parallel/error.py b/IPython/parallel/error.py index b289a58..c7f0170 100644 --- a/IPython/parallel/error.py +++ b/IPython/parallel/error.py @@ -232,6 +232,8 @@ class TaskRejectError(KernelError): class CompositeError(RemoteError): """Error for representing possibly multiple errors on engines""" + tb_limit = 4 # limit on how many tracebacks to draw + def __init__(self, message, elist): Exception.__init__(self, *(message, elist)) # Don't use pack_exception because it will conflict with the .message @@ -256,22 +258,28 @@ class CompositeError(RemoteError): def __str__(self): s = str(self.msg) - for en, ev, etb, ei in self.elist: + for en, ev, etb, ei in self.elist[:self.tb_limit]: engine_str = self._get_engine_str(ei) s = s + '\n' + engine_str + en + ': ' + str(ev) + if len(self.elist) > self.tb_limit: + s = s + '\n.... %i more exceptions ...' % (len(self.elist) - self.tb_limit) return s def __repr__(self): - return "CompositeError(%i)"%len(self.elist) + return "CompositeError(%i)" % len(self.elist) def render_traceback(self, excid=None): """render one or all of my tracebacks to a list of lines""" lines = [] if excid is None: - for (en,ev,etb,ei) in self.elist: + for (en,ev,etb,ei) in self.elist[:self.tb_limit]: lines.append(self._get_engine_str(ei)) lines.extend((etb or 'No traceback available').splitlines()) lines.append('') + if len(self.elist) > self.tb_limit: + lines.append( + '... %i more exceptions ...' % (len(self.elist) - self.tb_limit) + ) else: try: en,ev,etb,ei = self.elist[excid] diff --git a/IPython/parallel/tests/test_view.py b/IPython/parallel/tests/test_view.py index 042fe9e..106e4f2 100644 --- a/IPython/parallel/tests/test_view.py +++ b/IPython/parallel/tests/test_view.py @@ -619,6 +619,33 @@ class TestView(ClusterTestCase, ParametricTestCase): self.assertEqual(io.stdout.count('by zero'), len(view), io.stdout) self.assertEqual(io.stdout.count(':execute'), len(view), io.stdout) + def test_compositeerror_truncate(self): + """Truncate CompositeErrors with many exceptions""" + view = self.client[:] + msg_ids = [] + for i in range(10): + ar = view.execute("1/0") + msg_ids.extend(ar.msg_ids) + + ar = self.client.get_result(msg_ids) + try: + ar.get() + except error.CompositeError as e: + pass + else: + self.fail("Should have raised CompositeError") + + lines = e.render_traceback() + with capture_output() as io: + e.print_traceback() + + self.assertTrue("more exceptions" in lines[-1]) + count = e.tb_limit + + self.assertEqual(io.stdout.count('ZeroDivisionError'), 2 * count, io.stdout) + self.assertEqual(io.stdout.count('by zero'), count, io.stdout) + self.assertEqual(io.stdout.count(':execute'), count, io.stdout) + @dec.skipif_not_matplotlib def test_magic_pylab(self): """%pylab works on engines"""