diff --git a/tornado/stack_context.py b/tornado/stack_context.py index 5e744815..df186999 100644 --- a/tornado/stack_context.py +++ b/tornado/stack_context.py @@ -74,6 +74,7 @@ import itertools import sys import threading +from tornado.util import raise_exc_info class _State(threading.local): def __init__(self): @@ -248,4 +249,4 @@ def _nested(*managers): # Don't rely on sys.exc_info() still containing # the right information. Another exception may # have been raised and caught by an exit method - raise exc[0], exc[1], exc[2] + raise_exc_info(exc) diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py index fdedd438..8bf8eef9 100755 --- a/tornado/test/runtests.py +++ b/tornado/test/runtests.py @@ -24,6 +24,7 @@ TEST_MODULES = [ 'tornado.test.template_test', 'tornado.test.testing_test', 'tornado.test.twisted_test', + 'tornado.test.util_test', 'tornado.test.web_test', 'tornado.test.wsgi_test', ] diff --git a/tornado/test/util_test.py b/tornado/test/util_test.py new file mode 100644 index 00000000..24f607a4 --- /dev/null +++ b/tornado/test/util_test.py @@ -0,0 +1,25 @@ +from __future__ import absolute_import, division, with_statement +import sys +import unittest + +from tornado.util import raise_exc_info + +class RaiseExcInfoTest(unittest.TestCase): + def test_two_arg_exception(self): + # This test would fail on python 3 if raise_exc_info were simply + # a three-argument raise statement, because TwoArgException + # doesn't have a "copy constructor" + class TwoArgException(Exception): + def __init__(self, a, b): + super(TwoArgException, self).__init__() + self.a, self.b = a, b + + try: + raise TwoArgException(1, 2) + except TwoArgException: + exc_info = sys.exc_info() + try: + raise_exc_info(exc_info) + self.fail("didn't get expected exception") + except TwoArgException, e: + self.assertTrue(e is exc_info[1]) diff --git a/tornado/testing.py b/tornado/testing.py index 5484e0eb..30fd87ec 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -32,6 +32,7 @@ except ImportError: HTTPServer = None IOLoop = None from tornado.stack_context import StackContext, NullContext +from tornado.util import raise_exc_info import contextlib import logging import signal @@ -142,12 +143,7 @@ class AsyncTestCase(unittest.TestCase): if self.__failure is not None: failure = self.__failure self.__failure = None - # 2to3 isn't smart enough to convert three-argument raise - # statements correctly in some cases. - if isinstance(failure[1], failure[0]): - raise failure[1], None, failure[2] - else: - raise failure[0], failure[1], failure[2] + raise_exc_info(failure) def run(self, result=None): diff --git a/tornado/util.py b/tornado/util.py index 23ffda1f..e19ca900 100644 --- a/tornado/util.py +++ b/tornado/util.py @@ -46,6 +46,24 @@ else: bytes_type = str +def raise_exc_info(exc_info): + """Re-raise an exception (with original traceback) from an exc_info tuple. + + The argument is a ``(type, value, traceback)`` tuple as returned by + `sys.exc_info`. + """ + # 2to3 isn't smart enough to convert three-argument raise + # statements correctly in some cases. + if isinstance(exc_info[1], exc_info[0]): + raise exc_info[1], None, exc_info[2] + # After 2to3: raise exc_info[1].with_traceback(exc_info[2]) + else: + # I think this branch is only taken for string exceptions, + # which were removed in Python 2.6. + raise exc_info[0], exc_info[1], exc_info[2] + # After 2to3: raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) + + def doctests(): import doctest return doctest.DocTestSuite() diff --git a/tornado/web.py b/tornado/web.py index ab617379..645eb68f 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -83,7 +83,7 @@ from tornado import locale from tornado import stack_context from tornado import template from tornado.escape import utf8, _unicode -from tornado.util import b, bytes_type, import_object, ObjectDict +from tornado.util import b, bytes_type, import_object, ObjectDict, raise_exc_info try: from io import BytesIO # python 3 @@ -739,7 +739,7 @@ class RequestHandler(object): kwargs['exception'] = exc_info[1] try: # Put the traceback into sys.exc_info() - raise exc_info[0], exc_info[1], exc_info[2] + raise_exc_info(exc_info) except Exception: self.finish(self.get_error_html(status_code, **kwargs)) else: @@ -984,7 +984,7 @@ class RequestHandler(object): # the exception value instead of the full triple, # so re-raise the exception to ensure that it's in # sys.exc_info() - raise type, value, traceback + raise_exc_info((type, value, traceback)) except Exception: self._handle_request_exception(value) return True