gen_test.py
Go to the documentation of this file.
00001 from __future__ import absolute_import, division, print_function, with_statement
00002 
00003 import contextlib
00004 import datetime
00005 import functools
00006 import sys
00007 import textwrap
00008 import time
00009 import platform
00010 import weakref
00011 
00012 from tornado.concurrent import return_future, Future
00013 from tornado.escape import url_escape
00014 from tornado.httpclient import AsyncHTTPClient
00015 from tornado.ioloop import IOLoop
00016 from tornado.log import app_log
00017 from tornado import stack_context
00018 from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test
00019 from tornado.test.util import unittest, skipOnTravis
00020 from tornado.web import Application, RequestHandler, asynchronous, HTTPError
00021 
00022 from tornado import gen
00023 
00024 try:
00025     from concurrent import futures
00026 except ImportError:
00027     futures = None
00028 
00029 skipBefore33 = unittest.skipIf(sys.version_info < (3, 3), 'PEP 380 not available')
00030 skipNotCPython = unittest.skipIf(platform.python_implementation() != 'CPython',
00031                                  'Not CPython implementation')
00032 
00033 
00034 class GenEngineTest(AsyncTestCase):
00035     def setUp(self):
00036         super(GenEngineTest, self).setUp()
00037         self.named_contexts = []
00038 
00039     def named_context(self, name):
00040         @contextlib.contextmanager
00041         def context():
00042             self.named_contexts.append(name)
00043             try:
00044                 yield
00045             finally:
00046                 self.assertEqual(self.named_contexts.pop(), name)
00047         return context
00048 
00049     def run_gen(self, f):
00050         f()
00051         return self.wait()
00052 
00053     def delay_callback(self, iterations, callback, arg):
00054         """Runs callback(arg) after a number of IOLoop iterations."""
00055         if iterations == 0:
00056             callback(arg)
00057         else:
00058             self.io_loop.add_callback(functools.partial(
00059                 self.delay_callback, iterations - 1, callback, arg))
00060 
00061     @return_future
00062     def async_future(self, result, callback):
00063         self.io_loop.add_callback(callback, result)
00064 
00065     def test_no_yield(self):
00066         @gen.engine
00067         def f():
00068             self.stop()
00069         self.run_gen(f)
00070 
00071     def test_inline_cb(self):
00072         @gen.engine
00073         def f():
00074             (yield gen.Callback("k1"))()
00075             res = yield gen.Wait("k1")
00076             self.assertTrue(res is None)
00077             self.stop()
00078         self.run_gen(f)
00079 
00080     def test_ioloop_cb(self):
00081         @gen.engine
00082         def f():
00083             self.io_loop.add_callback((yield gen.Callback("k1")))
00084             yield gen.Wait("k1")
00085             self.stop()
00086         self.run_gen(f)
00087 
00088     def test_exception_phase1(self):
00089         @gen.engine
00090         def f():
00091             1 / 0
00092         self.assertRaises(ZeroDivisionError, self.run_gen, f)
00093 
00094     def test_exception_phase2(self):
00095         @gen.engine
00096         def f():
00097             self.io_loop.add_callback((yield gen.Callback("k1")))
00098             yield gen.Wait("k1")
00099             1 / 0
00100         self.assertRaises(ZeroDivisionError, self.run_gen, f)
00101 
00102     def test_exception_in_task_phase1(self):
00103         def fail_task(callback):
00104             1 / 0
00105 
00106         @gen.engine
00107         def f():
00108             try:
00109                 yield gen.Task(fail_task)
00110                 raise Exception("did not get expected exception")
00111             except ZeroDivisionError:
00112                 self.stop()
00113         self.run_gen(f)
00114 
00115     def test_exception_in_task_phase2(self):
00116         # This is the case that requires the use of stack_context in gen.engine
00117         def fail_task(callback):
00118             self.io_loop.add_callback(lambda: 1 / 0)
00119 
00120         @gen.engine
00121         def f():
00122             try:
00123                 yield gen.Task(fail_task)
00124                 raise Exception("did not get expected exception")
00125             except ZeroDivisionError:
00126                 self.stop()
00127         self.run_gen(f)
00128 
00129     def test_with_arg(self):
00130         @gen.engine
00131         def f():
00132             (yield gen.Callback("k1"))(42)
00133             res = yield gen.Wait("k1")
00134             self.assertEqual(42, res)
00135             self.stop()
00136         self.run_gen(f)
00137 
00138     def test_with_arg_tuple(self):
00139         @gen.engine
00140         def f():
00141             (yield gen.Callback((1, 2)))((3, 4))
00142             res = yield gen.Wait((1, 2))
00143             self.assertEqual((3, 4), res)
00144             self.stop()
00145         self.run_gen(f)
00146 
00147     def test_key_reuse(self):
00148         @gen.engine
00149         def f():
00150             yield gen.Callback("k1")
00151             yield gen.Callback("k1")
00152             self.stop()
00153         self.assertRaises(gen.KeyReuseError, self.run_gen, f)
00154 
00155     def test_key_reuse_tuple(self):
00156         @gen.engine
00157         def f():
00158             yield gen.Callback((1, 2))
00159             yield gen.Callback((1, 2))
00160             self.stop()
00161         self.assertRaises(gen.KeyReuseError, self.run_gen, f)
00162 
00163     def test_key_mismatch(self):
00164         @gen.engine
00165         def f():
00166             yield gen.Callback("k1")
00167             yield gen.Wait("k2")
00168             self.stop()
00169         self.assertRaises(gen.UnknownKeyError, self.run_gen, f)
00170 
00171     def test_key_mismatch_tuple(self):
00172         @gen.engine
00173         def f():
00174             yield gen.Callback((1, 2))
00175             yield gen.Wait((2, 3))
00176             self.stop()
00177         self.assertRaises(gen.UnknownKeyError, self.run_gen, f)
00178 
00179     def test_leaked_callback(self):
00180         @gen.engine
00181         def f():
00182             yield gen.Callback("k1")
00183             self.stop()
00184         self.assertRaises(gen.LeakedCallbackError, self.run_gen, f)
00185 
00186     def test_leaked_callback_tuple(self):
00187         @gen.engine
00188         def f():
00189             yield gen.Callback((1, 2))
00190             self.stop()
00191         self.assertRaises(gen.LeakedCallbackError, self.run_gen, f)
00192 
00193     def test_parallel_callback(self):
00194         @gen.engine
00195         def f():
00196             for k in range(3):
00197                 self.io_loop.add_callback((yield gen.Callback(k)))
00198             yield gen.Wait(1)
00199             self.io_loop.add_callback((yield gen.Callback(3)))
00200             yield gen.Wait(0)
00201             yield gen.Wait(3)
00202             yield gen.Wait(2)
00203             self.stop()
00204         self.run_gen(f)
00205 
00206     def test_bogus_yield(self):
00207         @gen.engine
00208         def f():
00209             yield 42
00210         self.assertRaises(gen.BadYieldError, self.run_gen, f)
00211 
00212     def test_bogus_yield_tuple(self):
00213         @gen.engine
00214         def f():
00215             yield (1, 2)
00216         self.assertRaises(gen.BadYieldError, self.run_gen, f)
00217 
00218     def test_reuse(self):
00219         @gen.engine
00220         def f():
00221             self.io_loop.add_callback((yield gen.Callback(0)))
00222             yield gen.Wait(0)
00223             self.stop()
00224         self.run_gen(f)
00225         self.run_gen(f)
00226 
00227     def test_task(self):
00228         @gen.engine
00229         def f():
00230             yield gen.Task(self.io_loop.add_callback)
00231             self.stop()
00232         self.run_gen(f)
00233 
00234     def test_wait_all(self):
00235         @gen.engine
00236         def f():
00237             (yield gen.Callback("k1"))("v1")
00238             (yield gen.Callback("k2"))("v2")
00239             results = yield gen.WaitAll(["k1", "k2"])
00240             self.assertEqual(results, ["v1", "v2"])
00241             self.stop()
00242         self.run_gen(f)
00243 
00244     def test_exception_in_yield(self):
00245         @gen.engine
00246         def f():
00247             try:
00248                 yield gen.Wait("k1")
00249                 raise Exception("did not get expected exception")
00250             except gen.UnknownKeyError:
00251                 pass
00252             self.stop()
00253         self.run_gen(f)
00254 
00255     def test_resume_after_exception_in_yield(self):
00256         @gen.engine
00257         def f():
00258             try:
00259                 yield gen.Wait("k1")
00260                 raise Exception("did not get expected exception")
00261             except gen.UnknownKeyError:
00262                 pass
00263             (yield gen.Callback("k2"))("v2")
00264             self.assertEqual((yield gen.Wait("k2")), "v2")
00265             self.stop()
00266         self.run_gen(f)
00267 
00268     def test_orphaned_callback(self):
00269         @gen.engine
00270         def f():
00271             self.orphaned_callback = yield gen.Callback(1)
00272         try:
00273             self.run_gen(f)
00274             raise Exception("did not get expected exception")
00275         except gen.LeakedCallbackError:
00276             pass
00277         self.orphaned_callback()
00278 
00279     def test_multi(self):
00280         @gen.engine
00281         def f():
00282             (yield gen.Callback("k1"))("v1")
00283             (yield gen.Callback("k2"))("v2")
00284             results = yield [gen.Wait("k1"), gen.Wait("k2")]
00285             self.assertEqual(results, ["v1", "v2"])
00286             self.stop()
00287         self.run_gen(f)
00288 
00289     def test_multi_dict(self):
00290         @gen.engine
00291         def f():
00292             (yield gen.Callback("k1"))("v1")
00293             (yield gen.Callback("k2"))("v2")
00294             results = yield dict(foo=gen.Wait("k1"), bar=gen.Wait("k2"))
00295             self.assertEqual(results, dict(foo="v1", bar="v2"))
00296             self.stop()
00297         self.run_gen(f)
00298 
00299     # The following tests explicitly run with both gen.Multi
00300     # and gen.multi_future (Task returns a Future, so it can be used
00301     # with either).
00302     def test_multi_yieldpoint_delayed(self):
00303         @gen.engine
00304         def f():
00305             # callbacks run at different times
00306             responses = yield gen.Multi([
00307                 gen.Task(self.delay_callback, 3, arg="v1"),
00308                 gen.Task(self.delay_callback, 1, arg="v2"),
00309             ])
00310             self.assertEqual(responses, ["v1", "v2"])
00311             self.stop()
00312         self.run_gen(f)
00313 
00314     def test_multi_yieldpoint_dict_delayed(self):
00315         @gen.engine
00316         def f():
00317             # callbacks run at different times
00318             responses = yield gen.Multi(dict(
00319                 foo=gen.Task(self.delay_callback, 3, arg="v1"),
00320                 bar=gen.Task(self.delay_callback, 1, arg="v2"),
00321             ))
00322             self.assertEqual(responses, dict(foo="v1", bar="v2"))
00323             self.stop()
00324         self.run_gen(f)
00325 
00326     def test_multi_future_delayed(self):
00327         @gen.engine
00328         def f():
00329             # callbacks run at different times
00330             responses = yield gen.multi_future([
00331                 gen.Task(self.delay_callback, 3, arg="v1"),
00332                 gen.Task(self.delay_callback, 1, arg="v2"),
00333             ])
00334             self.assertEqual(responses, ["v1", "v2"])
00335             self.stop()
00336         self.run_gen(f)
00337 
00338     def test_multi_future_dict_delayed(self):
00339         @gen.engine
00340         def f():
00341             # callbacks run at different times
00342             responses = yield gen.multi_future(dict(
00343                 foo=gen.Task(self.delay_callback, 3, arg="v1"),
00344                 bar=gen.Task(self.delay_callback, 1, arg="v2"),
00345             ))
00346             self.assertEqual(responses, dict(foo="v1", bar="v2"))
00347             self.stop()
00348         self.run_gen(f)
00349 
00350     @skipOnTravis
00351     @gen_test
00352     def test_multi_performance(self):
00353         # Yielding a list used to have quadratic performance; make
00354         # sure a large list stays reasonable.  On my laptop a list of
00355         # 2000 used to take 1.8s, now it takes 0.12.
00356         start = time.time()
00357         yield [gen.Task(self.io_loop.add_callback) for i in range(2000)]
00358         end = time.time()
00359         self.assertLess(end - start, 1.0)
00360 
00361     @gen_test
00362     def test_multi_empty(self):
00363         # Empty lists or dicts should return the same type.
00364         x = yield []
00365         self.assertTrue(isinstance(x, list))
00366         y = yield {}
00367         self.assertTrue(isinstance(y, dict))
00368 
00369     @gen_test
00370     def test_multi_mixed_types(self):
00371         # A YieldPoint (Wait) and Future (Task) can be combined
00372         # (and use the YieldPoint codepath)
00373         (yield gen.Callback("k1"))("v1")
00374         responses = yield [gen.Wait("k1"),
00375                            gen.Task(self.delay_callback, 3, arg="v2")]
00376         self.assertEqual(responses, ["v1", "v2"])
00377 
00378     @gen_test
00379     def test_future(self):
00380         result = yield self.async_future(1)
00381         self.assertEqual(result, 1)
00382 
00383     @gen_test
00384     def test_multi_future(self):
00385         results = yield [self.async_future(1), self.async_future(2)]
00386         self.assertEqual(results, [1, 2])
00387 
00388     @gen_test
00389     def test_multi_dict_future(self):
00390         results = yield dict(foo=self.async_future(1), bar=self.async_future(2))
00391         self.assertEqual(results, dict(foo=1, bar=2))
00392 
00393     def test_arguments(self):
00394         @gen.engine
00395         def f():
00396             (yield gen.Callback("noargs"))()
00397             self.assertEqual((yield gen.Wait("noargs")), None)
00398             (yield gen.Callback("1arg"))(42)
00399             self.assertEqual((yield gen.Wait("1arg")), 42)
00400 
00401             (yield gen.Callback("kwargs"))(value=42)
00402             result = yield gen.Wait("kwargs")
00403             self.assertTrue(isinstance(result, gen.Arguments))
00404             self.assertEqual(((), dict(value=42)), result)
00405             self.assertEqual(dict(value=42), result.kwargs)
00406 
00407             (yield gen.Callback("2args"))(42, 43)
00408             result = yield gen.Wait("2args")
00409             self.assertTrue(isinstance(result, gen.Arguments))
00410             self.assertEqual(((42, 43), {}), result)
00411             self.assertEqual((42, 43), result.args)
00412 
00413             def task_func(callback):
00414                 callback(None, error="foo")
00415             result = yield gen.Task(task_func)
00416             self.assertTrue(isinstance(result, gen.Arguments))
00417             self.assertEqual(((None,), dict(error="foo")), result)
00418 
00419             self.stop()
00420         self.run_gen(f)
00421 
00422     def test_stack_context_leak(self):
00423         # regression test: repeated invocations of a gen-based
00424         # function should not result in accumulated stack_contexts
00425         def _stack_depth():
00426             head = stack_context._state.contexts[1]
00427             length = 0
00428 
00429             while head is not None:
00430                 length += 1
00431                 head = head.old_contexts[1]
00432 
00433             return length
00434 
00435         @gen.engine
00436         def inner(callback):
00437             yield gen.Task(self.io_loop.add_callback)
00438             callback()
00439 
00440         @gen.engine
00441         def outer():
00442             for i in range(10):
00443                 yield gen.Task(inner)
00444 
00445             stack_increase = _stack_depth() - initial_stack_depth
00446             self.assertTrue(stack_increase <= 2)
00447             self.stop()
00448         initial_stack_depth = _stack_depth()
00449         self.run_gen(outer)
00450 
00451     def test_stack_context_leak_exception(self):
00452         # same as previous, but with a function that exits with an exception
00453         @gen.engine
00454         def inner(callback):
00455             yield gen.Task(self.io_loop.add_callback)
00456             1 / 0
00457 
00458         @gen.engine
00459         def outer():
00460             for i in range(10):
00461                 try:
00462                     yield gen.Task(inner)
00463                 except ZeroDivisionError:
00464                     pass
00465             stack_increase = len(stack_context._state.contexts) - initial_stack_depth
00466             self.assertTrue(stack_increase <= 2)
00467             self.stop()
00468         initial_stack_depth = len(stack_context._state.contexts)
00469         self.run_gen(outer)
00470 
00471     def function_with_stack_context(self, callback):
00472         # Technically this function should stack_context.wrap its callback
00473         # upon entry.  However, it is very common for this step to be
00474         # omitted.
00475         def step2():
00476             self.assertEqual(self.named_contexts, ['a'])
00477             self.io_loop.add_callback(callback)
00478 
00479         with stack_context.StackContext(self.named_context('a')):
00480             self.io_loop.add_callback(step2)
00481 
00482     @gen_test
00483     def test_wait_transfer_stack_context(self):
00484         # Wait should not pick up contexts from where callback was invoked,
00485         # even if that function improperly fails to wrap its callback.
00486         cb = yield gen.Callback('k1')
00487         self.function_with_stack_context(cb)
00488         self.assertEqual(self.named_contexts, [])
00489         yield gen.Wait('k1')
00490         self.assertEqual(self.named_contexts, [])
00491 
00492     @gen_test
00493     def test_task_transfer_stack_context(self):
00494         yield gen.Task(self.function_with_stack_context)
00495         self.assertEqual(self.named_contexts, [])
00496 
00497     def test_raise_after_stop(self):
00498         # This pattern will be used in the following tests so make sure
00499         # the exception propagates as expected.
00500         @gen.engine
00501         def f():
00502             self.stop()
00503             1 / 0
00504 
00505         with self.assertRaises(ZeroDivisionError):
00506             self.run_gen(f)
00507 
00508     def test_sync_raise_return(self):
00509         # gen.Return is allowed in @gen.engine, but it may not be used
00510         # to return a value.
00511         @gen.engine
00512         def f():
00513             self.stop(42)
00514             raise gen.Return()
00515 
00516         result = self.run_gen(f)
00517         self.assertEqual(result, 42)
00518 
00519     def test_async_raise_return(self):
00520         @gen.engine
00521         def f():
00522             yield gen.Task(self.io_loop.add_callback)
00523             self.stop(42)
00524             raise gen.Return()
00525 
00526         result = self.run_gen(f)
00527         self.assertEqual(result, 42)
00528 
00529     def test_sync_raise_return_value(self):
00530         @gen.engine
00531         def f():
00532             raise gen.Return(42)
00533 
00534         with self.assertRaises(gen.ReturnValueIgnoredError):
00535             self.run_gen(f)
00536 
00537     def test_sync_raise_return_value_tuple(self):
00538         @gen.engine
00539         def f():
00540             raise gen.Return((1, 2))
00541 
00542         with self.assertRaises(gen.ReturnValueIgnoredError):
00543             self.run_gen(f)
00544 
00545     def test_async_raise_return_value(self):
00546         @gen.engine
00547         def f():
00548             yield gen.Task(self.io_loop.add_callback)
00549             raise gen.Return(42)
00550 
00551         with self.assertRaises(gen.ReturnValueIgnoredError):
00552             self.run_gen(f)
00553 
00554     def test_async_raise_return_value_tuple(self):
00555         @gen.engine
00556         def f():
00557             yield gen.Task(self.io_loop.add_callback)
00558             raise gen.Return((1, 2))
00559 
00560         with self.assertRaises(gen.ReturnValueIgnoredError):
00561             self.run_gen(f)
00562 
00563     def test_return_value(self):
00564         # It is an error to apply @gen.engine to a function that returns
00565         # a value.
00566         @gen.engine
00567         def f():
00568             return 42
00569 
00570         with self.assertRaises(gen.ReturnValueIgnoredError):
00571             self.run_gen(f)
00572 
00573     def test_return_value_tuple(self):
00574         # It is an error to apply @gen.engine to a function that returns
00575         # a value.
00576         @gen.engine
00577         def f():
00578             return (1, 2)
00579 
00580         with self.assertRaises(gen.ReturnValueIgnoredError):
00581             self.run_gen(f)
00582 
00583     @skipNotCPython
00584     def test_task_refcounting(self):
00585         # On CPython, tasks and their arguments should be released immediately
00586         # without waiting for garbage collection.
00587         @gen.engine
00588         def f():
00589             class Foo(object):
00590                 pass
00591             arg = Foo()
00592             self.arg_ref = weakref.ref(arg)
00593             task = gen.Task(self.io_loop.add_callback, arg=arg)
00594             self.task_ref = weakref.ref(task)
00595             yield task
00596             self.stop()
00597 
00598         self.run_gen(f)
00599         self.assertIs(self.arg_ref(), None)
00600         self.assertIs(self.task_ref(), None)
00601 
00602 
00603 class GenCoroutineTest(AsyncTestCase):
00604     def setUp(self):
00605         # Stray StopIteration exceptions can lead to tests exiting prematurely,
00606         # so we need explicit checks here to make sure the tests run all
00607         # the way through.
00608         self.finished = False
00609         super(GenCoroutineTest, self).setUp()
00610 
00611     def tearDown(self):
00612         super(GenCoroutineTest, self).tearDown()
00613         assert self.finished
00614 
00615     @gen_test
00616     def test_sync_gen_return(self):
00617         @gen.coroutine
00618         def f():
00619             raise gen.Return(42)
00620         result = yield f()
00621         self.assertEqual(result, 42)
00622         self.finished = True
00623 
00624     @gen_test
00625     def test_async_gen_return(self):
00626         @gen.coroutine
00627         def f():
00628             yield gen.Task(self.io_loop.add_callback)
00629             raise gen.Return(42)
00630         result = yield f()
00631         self.assertEqual(result, 42)
00632         self.finished = True
00633 
00634     @gen_test
00635     def test_sync_return(self):
00636         @gen.coroutine
00637         def f():
00638             return 42
00639         result = yield f()
00640         self.assertEqual(result, 42)
00641         self.finished = True
00642 
00643     @skipBefore33
00644     @gen_test
00645     def test_async_return(self):
00646         # It is a compile-time error to return a value in a generator
00647         # before Python 3.3, so we must test this with exec.
00648         # Flatten the real global and local namespace into our fake globals:
00649         # it's all global from the perspective of f().
00650         global_namespace = dict(globals(), **locals())
00651         local_namespace = {}
00652         exec(textwrap.dedent("""
00653         @gen.coroutine
00654         def f():
00655             yield gen.Task(self.io_loop.add_callback)
00656             return 42
00657         """), global_namespace, local_namespace)
00658         result = yield local_namespace['f']()
00659         self.assertEqual(result, 42)
00660         self.finished = True
00661 
00662     @skipBefore33
00663     @gen_test
00664     def test_async_early_return(self):
00665         # A yield statement exists but is not executed, which means
00666         # this function "returns" via an exception.  This exception
00667         # doesn't happen before the exception handling is set up.
00668         global_namespace = dict(globals(), **locals())
00669         local_namespace = {}
00670         exec(textwrap.dedent("""
00671         @gen.coroutine
00672         def f():
00673             if True:
00674                 return 42
00675             yield gen.Task(self.io_loop.add_callback)
00676         """), global_namespace, local_namespace)
00677         result = yield local_namespace['f']()
00678         self.assertEqual(result, 42)
00679         self.finished = True
00680 
00681     @gen_test
00682     def test_sync_return_no_value(self):
00683         @gen.coroutine
00684         def f():
00685             return
00686         result = yield f()
00687         self.assertEqual(result, None)
00688         self.finished = True
00689 
00690     @gen_test
00691     def test_async_return_no_value(self):
00692         # Without a return value we don't need python 3.3.
00693         @gen.coroutine
00694         def f():
00695             yield gen.Task(self.io_loop.add_callback)
00696             return
00697         result = yield f()
00698         self.assertEqual(result, None)
00699         self.finished = True
00700 
00701     @gen_test
00702     def test_sync_raise(self):
00703         @gen.coroutine
00704         def f():
00705             1 / 0
00706         # The exception is raised when the future is yielded
00707         # (or equivalently when its result method is called),
00708         # not when the function itself is called).
00709         future = f()
00710         with self.assertRaises(ZeroDivisionError):
00711             yield future
00712         self.finished = True
00713 
00714     @gen_test
00715     def test_async_raise(self):
00716         @gen.coroutine
00717         def f():
00718             yield gen.Task(self.io_loop.add_callback)
00719             1 / 0
00720         future = f()
00721         with self.assertRaises(ZeroDivisionError):
00722             yield future
00723         self.finished = True
00724 
00725     @gen_test
00726     def test_pass_callback(self):
00727         @gen.coroutine
00728         def f():
00729             raise gen.Return(42)
00730         result = yield gen.Task(f)
00731         self.assertEqual(result, 42)
00732         self.finished = True
00733 
00734     @gen_test
00735     def test_replace_yieldpoint_exception(self):
00736         # Test exception handling: a coroutine can catch one exception
00737         # raised by a yield point and raise a different one.
00738         @gen.coroutine
00739         def f1():
00740             1 / 0
00741 
00742         @gen.coroutine
00743         def f2():
00744             try:
00745                 yield f1()
00746             except ZeroDivisionError:
00747                 raise KeyError()
00748 
00749         future = f2()
00750         with self.assertRaises(KeyError):
00751             yield future
00752         self.finished = True
00753 
00754     @gen_test
00755     def test_swallow_yieldpoint_exception(self):
00756         # Test exception handling: a coroutine can catch an exception
00757         # raised by a yield point and not raise a different one.
00758         @gen.coroutine
00759         def f1():
00760             1 / 0
00761 
00762         @gen.coroutine
00763         def f2():
00764             try:
00765                 yield f1()
00766             except ZeroDivisionError:
00767                 raise gen.Return(42)
00768 
00769         result = yield f2()
00770         self.assertEqual(result, 42)
00771         self.finished = True
00772 
00773     @gen_test
00774     def test_replace_context_exception(self):
00775         # Test exception handling: exceptions thrown into the stack context
00776         # can be caught and replaced.
00777         # Note that this test and the following are for behavior that is
00778         # not really supported any more:  coroutines no longer create a
00779         # stack context automatically; but one is created after the first
00780         # YieldPoint (i.e. not a Future).
00781         @gen.coroutine
00782         def f2():
00783             (yield gen.Callback(1))()
00784             yield gen.Wait(1)
00785             self.io_loop.add_callback(lambda: 1 / 0)
00786             try:
00787                 yield gen.Task(self.io_loop.add_timeout,
00788                                self.io_loop.time() + 10)
00789             except ZeroDivisionError:
00790                 raise KeyError()
00791 
00792         future = f2()
00793         with self.assertRaises(KeyError):
00794             yield future
00795         self.finished = True
00796 
00797     @gen_test
00798     def test_swallow_context_exception(self):
00799         # Test exception handling: exceptions thrown into the stack context
00800         # can be caught and ignored.
00801         @gen.coroutine
00802         def f2():
00803             (yield gen.Callback(1))()
00804             yield gen.Wait(1)
00805             self.io_loop.add_callback(lambda: 1 / 0)
00806             try:
00807                 yield gen.Task(self.io_loop.add_timeout,
00808                                self.io_loop.time() + 10)
00809             except ZeroDivisionError:
00810                 raise gen.Return(42)
00811 
00812         result = yield f2()
00813         self.assertEqual(result, 42)
00814         self.finished = True
00815 
00816     @gen_test
00817     def test_moment(self):
00818         calls = []
00819         @gen.coroutine
00820         def f(name, yieldable):
00821             for i in range(5):
00822                 calls.append(name)
00823                 yield yieldable
00824         # First, confirm the behavior without moment: each coroutine
00825         # monopolizes the event loop until it finishes.
00826         immediate = Future()
00827         immediate.set_result(None)
00828         yield [f('a', immediate), f('b', immediate)]
00829         self.assertEqual(''.join(calls), 'aaaaabbbbb')
00830 
00831         # With moment, they take turns.
00832         calls = []
00833         yield [f('a', gen.moment), f('b', gen.moment)]
00834         self.assertEqual(''.join(calls), 'ababababab')
00835         self.finished = True
00836 
00837         calls = []
00838         yield [f('a', gen.moment), f('b', immediate)]
00839         self.assertEqual(''.join(calls), 'abbbbbaaaa')
00840 
00841 
00842 class GenSequenceHandler(RequestHandler):
00843     @asynchronous
00844     @gen.engine
00845     def get(self):
00846         self.io_loop = self.request.connection.stream.io_loop
00847         self.io_loop.add_callback((yield gen.Callback("k1")))
00848         yield gen.Wait("k1")
00849         self.write("1")
00850         self.io_loop.add_callback((yield gen.Callback("k2")))
00851         yield gen.Wait("k2")
00852         self.write("2")
00853         # reuse an old key
00854         self.io_loop.add_callback((yield gen.Callback("k1")))
00855         yield gen.Wait("k1")
00856         self.finish("3")
00857 
00858 
00859 class GenCoroutineSequenceHandler(RequestHandler):
00860     @gen.coroutine
00861     def get(self):
00862         self.io_loop = self.request.connection.stream.io_loop
00863         self.io_loop.add_callback((yield gen.Callback("k1")))
00864         yield gen.Wait("k1")
00865         self.write("1")
00866         self.io_loop.add_callback((yield gen.Callback("k2")))
00867         yield gen.Wait("k2")
00868         self.write("2")
00869         # reuse an old key
00870         self.io_loop.add_callback((yield gen.Callback("k1")))
00871         yield gen.Wait("k1")
00872         self.finish("3")
00873 
00874 
00875 class GenCoroutineUnfinishedSequenceHandler(RequestHandler):
00876     @asynchronous
00877     @gen.coroutine
00878     def get(self):
00879         self.io_loop = self.request.connection.stream.io_loop
00880         self.io_loop.add_callback((yield gen.Callback("k1")))
00881         yield gen.Wait("k1")
00882         self.write("1")
00883         self.io_loop.add_callback((yield gen.Callback("k2")))
00884         yield gen.Wait("k2")
00885         self.write("2")
00886         # reuse an old key
00887         self.io_loop.add_callback((yield gen.Callback("k1")))
00888         yield gen.Wait("k1")
00889         # just write, don't finish
00890         self.write("3")
00891 
00892 
00893 class GenTaskHandler(RequestHandler):
00894     @asynchronous
00895     @gen.engine
00896     def get(self):
00897         io_loop = self.request.connection.stream.io_loop
00898         client = AsyncHTTPClient(io_loop=io_loop)
00899         response = yield gen.Task(client.fetch, self.get_argument('url'))
00900         response.rethrow()
00901         self.finish(b"got response: " + response.body)
00902 
00903 
00904 class GenExceptionHandler(RequestHandler):
00905     @asynchronous
00906     @gen.engine
00907     def get(self):
00908         # This test depends on the order of the two decorators.
00909         io_loop = self.request.connection.stream.io_loop
00910         yield gen.Task(io_loop.add_callback)
00911         raise Exception("oops")
00912 
00913 
00914 class GenCoroutineExceptionHandler(RequestHandler):
00915     @gen.coroutine
00916     def get(self):
00917         # This test depends on the order of the two decorators.
00918         io_loop = self.request.connection.stream.io_loop
00919         yield gen.Task(io_loop.add_callback)
00920         raise Exception("oops")
00921 
00922 
00923 class GenYieldExceptionHandler(RequestHandler):
00924     @asynchronous
00925     @gen.engine
00926     def get(self):
00927         io_loop = self.request.connection.stream.io_loop
00928         # Test the interaction of the two stack_contexts.
00929 
00930         def fail_task(callback):
00931             io_loop.add_callback(lambda: 1 / 0)
00932         try:
00933             yield gen.Task(fail_task)
00934             raise Exception("did not get expected exception")
00935         except ZeroDivisionError:
00936             self.finish('ok')
00937 
00938 
00939 class UndecoratedCoroutinesHandler(RequestHandler):
00940     @gen.coroutine
00941     def prepare(self):
00942         self.chunks = []
00943         yield gen.Task(IOLoop.current().add_callback)
00944         self.chunks.append('1')
00945 
00946     @gen.coroutine
00947     def get(self):
00948         self.chunks.append('2')
00949         yield gen.Task(IOLoop.current().add_callback)
00950         self.chunks.append('3')
00951         yield gen.Task(IOLoop.current().add_callback)
00952         self.write(''.join(self.chunks))
00953 
00954 
00955 class AsyncPrepareErrorHandler(RequestHandler):
00956     @gen.coroutine
00957     def prepare(self):
00958         yield gen.Task(IOLoop.current().add_callback)
00959         raise HTTPError(403)
00960 
00961     def get(self):
00962         self.finish('ok')
00963 
00964 
00965 class GenWebTest(AsyncHTTPTestCase):
00966     def get_app(self):
00967         return Application([
00968             ('/sequence', GenSequenceHandler),
00969             ('/coroutine_sequence', GenCoroutineSequenceHandler),
00970             ('/coroutine_unfinished_sequence',
00971              GenCoroutineUnfinishedSequenceHandler),
00972             ('/task', GenTaskHandler),
00973             ('/exception', GenExceptionHandler),
00974             ('/coroutine_exception', GenCoroutineExceptionHandler),
00975             ('/yield_exception', GenYieldExceptionHandler),
00976             ('/undecorated_coroutine', UndecoratedCoroutinesHandler),
00977             ('/async_prepare_error', AsyncPrepareErrorHandler),
00978         ])
00979 
00980     def test_sequence_handler(self):
00981         response = self.fetch('/sequence')
00982         self.assertEqual(response.body, b"123")
00983 
00984     def test_coroutine_sequence_handler(self):
00985         response = self.fetch('/coroutine_sequence')
00986         self.assertEqual(response.body, b"123")
00987 
00988     def test_coroutine_unfinished_sequence_handler(self):
00989         response = self.fetch('/coroutine_unfinished_sequence')
00990         self.assertEqual(response.body, b"123")
00991 
00992     def test_task_handler(self):
00993         response = self.fetch('/task?url=%s' % url_escape(self.get_url('/sequence')))
00994         self.assertEqual(response.body, b"got response: 123")
00995 
00996     def test_exception_handler(self):
00997         # Make sure we get an error and not a timeout
00998         with ExpectLog(app_log, "Uncaught exception GET /exception"):
00999             response = self.fetch('/exception')
01000         self.assertEqual(500, response.code)
01001 
01002     def test_coroutine_exception_handler(self):
01003         # Make sure we get an error and not a timeout
01004         with ExpectLog(app_log, "Uncaught exception GET /coroutine_exception"):
01005             response = self.fetch('/coroutine_exception')
01006         self.assertEqual(500, response.code)
01007 
01008     def test_yield_exception_handler(self):
01009         response = self.fetch('/yield_exception')
01010         self.assertEqual(response.body, b'ok')
01011 
01012     def test_undecorated_coroutines(self):
01013         response = self.fetch('/undecorated_coroutine')
01014         self.assertEqual(response.body, b'123')
01015 
01016     def test_async_prepare_error_handler(self):
01017         response = self.fetch('/async_prepare_error')
01018         self.assertEqual(response.code, 403)
01019 
01020 
01021 class WithTimeoutTest(AsyncTestCase):
01022     @gen_test
01023     def test_timeout(self):
01024         with self.assertRaises(gen.TimeoutError):
01025             yield gen.with_timeout(datetime.timedelta(seconds=0.1),
01026                                    Future())
01027 
01028     @gen_test
01029     def test_completes_before_timeout(self):
01030         future = Future()
01031         self.io_loop.add_timeout(datetime.timedelta(seconds=0.1),
01032                                  lambda: future.set_result('asdf'))
01033         result = yield gen.with_timeout(datetime.timedelta(seconds=3600),
01034                                         future)
01035         self.assertEqual(result, 'asdf')
01036 
01037     @gen_test
01038     def test_fails_before_timeout(self):
01039         future = Future()
01040         self.io_loop.add_timeout(
01041             datetime.timedelta(seconds=0.1),
01042             lambda: future.set_exception(ZeroDivisionError))
01043         with self.assertRaises(ZeroDivisionError):
01044             yield gen.with_timeout(datetime.timedelta(seconds=3600), future)
01045 
01046     @gen_test
01047     def test_already_resolved(self):
01048         future = Future()
01049         future.set_result('asdf')
01050         result = yield gen.with_timeout(datetime.timedelta(seconds=3600),
01051                                         future)
01052         self.assertEqual(result, 'asdf')
01053 
01054     @unittest.skipIf(futures is None, 'futures module not present')
01055     @gen_test
01056     def test_timeout_concurrent_future(self):
01057         with futures.ThreadPoolExecutor(1) as executor:
01058             with self.assertRaises(gen.TimeoutError):
01059                 yield gen.with_timeout(self.io_loop.time(),
01060                                        executor.submit(time.sleep, 0.1))
01061 
01062     @unittest.skipIf(futures is None, 'futures module not present')
01063     @gen_test
01064     def test_completed_concurrent_future(self):
01065         with futures.ThreadPoolExecutor(1) as executor:
01066             yield gen.with_timeout(datetime.timedelta(seconds=3600),
01067                                    executor.submit(lambda: None))
01068 
01069 
01070 if __name__ == '__main__':
01071     unittest.main()


rosbridge_server
Author(s): Jonathan Mace
autogenerated on Wed Sep 13 2017 03:18:20