Search code examples
tornado

ExceptionStackContext for tornado testing async app


have watched ajdavis@pycon2015 about testing async apps, as below: http://pyvideo.org/video/3419/eventually-correct-testing-async-apps

I have a question about the usage of ExceptionStackContext. In the presentation, it's used as below:

import unittest
from tornado.stack_context import ExceptionStackContext
from tornado.ioloop import IOLoop

class TestAsync(unittest.TestCase):
    def test_delay(self):
        io_loop = IOLoop.current()
        def handle_exception(typ, val, tb):
            self.failure = (typ, val, tb)
            io_loop.stop()
        def done():
            self.assertAlmostEqual(time.time() - start, 2)
            self.stop()
        with ExceptionStackContext(handle_exception):
            delay(3, done)  # fail the assert
        io_loop.start()

run this test, the io_loop will not stop. because the handle_exception is not called.

my async method delay is:

import threading
import time

def delay(delay_seconds, callback):
    def wrap():    
        time.sleep(delay_seconds)
        callback()
    t = threading.Thread(target=wrap)
    t.daemon = True
    t.start()

so i think, it should ExceptionStackContext should wrap the done() , as below:

def callback():
    with ExceptionStackContext(handle_exception):
        done()
delay(2, callback)
io_loop.start()

is this the wright way to use ExceptionStackContext?

by the way, ExceptionStackContext in tornado.testing.AsyncTestCase is useless in fact:

def run(self, result=None):
    with ExceptionStackContext(self._handle_exception):
        super(AsyncTestCase, self).run(result)

super(AsyncTestCase, self).run(result) does not throw up the AssertException.


Solution

  • StackContexts are magical: there are many places in Tornado which automatically capture the current StackContext and resume it later. So when delay() calls done() (assuming delay() is implemented in terms of IOLoop.add_timeout or otherwise handles StackContext correctly), the ExceptionStackContext has been reestablished.

    Similarly, even though the ExceptionStackContext in AsyncTestCase.run() never directly catches any exceptions, it establishes the "current" StackContext to be captured anywhere within the test. (That's why it's with ExceptionStackContext instead of an ordinary try/except)