diff options
-rw-r--r-- | sleekxmpp/stanza/rootstanza.py | 2 | ||||
-rw-r--r-- | sleekxmpp/xmlstream/xmlstream.py | 24 | ||||
-rw-r--r-- | 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): </message> """) - if sys.version_info < (3, 0): - self.send(""" - <message type="error"> - <error type="cancel"> - <undefined-condition - xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> - <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> - SleekXMPP got into trouble. - </text> - </error> - </message> - """) - else: - # Unfortunately, tracebacks do not make for very portable tests. - pass + self.send(""" + <message type="error"> + <error type="cancel"> + <undefined-condition + xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> + <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> + SleekXMPP got into trouble. + </text> + </error> + </message> + """) + + self.assertEqual(raised_errors, [True], "Exception was not raised: %s" % raised_errors) + suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExceptions) |