From 34c374a1e1dad468869c8e5dc6fb2ea7b1d90c7e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 17 Dec 2010 13:11:03 -0500 Subject: [PATCH] Make tests pass for catching exceptions. May now use sys.excepthook to catch exceptions from threaded handlers. --- sleekxmpp/stanza/rootstanza.py | 2 +- sleekxmpp/xmlstream/xmlstream.py | 24 +++++++++++++++++++++ tests/test_stream_exceptions.py | 37 +++++++++++++++++++------------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index 777314b9..8123c5f8 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -64,7 +64,7 @@ class RootStanza(StanzaBase): log.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) # Finally raise the exception, so it can be handled (or not) - # at a higher level + # at a higher level by using sys.excepthook. raise e register_stanza_plugin(RootStanza, Error) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 2317f04c..d5c1043b 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -682,6 +682,7 @@ class XMLStream(object): Event handlers and the send queue will be threaded regardless of this parameter's value. """ + self._thread_excepthook() self.scheduler.process(threaded=True) def start_thread(name, target): @@ -954,3 +955,26 @@ class XMLStream(object): self.disconnect() self.event_queue.put(('quit', None, None)) return + + def _thread_excepthook(self): + """ + If a threaded event handler raises an exception, there is no way to + catch it except with an excepthook. Currently, each thread has its own + excepthook, but ideally we could use the main sys.excepthook. + + Modifies threading.Thread to use sys.excepthook when an exception + is not caught. + """ + init_old = threading.Thread.__init__ + def init(self, *args, **kwargs): + init_old(self, *args, **kwargs) + run_old = self.run + def run_with_except_hook(*args, **kw): + try: + run_old(*args, **kw) + except (KeyboardInterrupt, SystemExit): + raise + except: + sys.excepthook(*sys.exc_info()) + self.run = run_with_except_hook + threading.Thread.__init__ = init diff --git a/tests/test_stream_exceptions.py b/tests/test_stream_exceptions.py index b7be6485..e1b70d39 100644 --- a/tests/test_stream_exceptions.py +++ b/tests/test_stream_exceptions.py @@ -10,6 +10,7 @@ class TestStreamExceptions(SleekTest): """ def tearDown(self): + sys.excepthook = sys.__excepthook__ self.stream_close() def testXMPPErrorException(self): @@ -78,9 +79,16 @@ class TestStreamExceptions(SleekTest): def testUnknownException(self): """Test raising an generic exception in a threaded handler.""" + raised_errors = [] + def message(msg): raise ValueError("Did something wrong") + def catch_error(*args, **kwargs): + raised_errors.append(True) + + sys.excepthook = catch_error + self.stream_start() self.xmpp.add_event_handler('message', message) @@ -90,21 +98,20 @@ class TestStreamExceptions(SleekTest): """) - if sys.version_info < (3, 0): - self.send(""" - - - - - SleekXMPP got into trouble. - - - - """) - else: - # Unfortunately, tracebacks do not make for very portable tests. - pass + self.send(""" + + + + + SleekXMPP got into trouble. + + + + """) + + self.assertEqual(raised_errors, [True], "Exception was not raised: %s" % raised_errors) + suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExceptions)