00001 from __future__ import absolute_import, division, with_statement
00002 import functools
00003 from tornado.escape import url_escape
00004 from tornado.httpclient import AsyncHTTPClient
00005 from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, LogTrapTestCase
00006 from tornado.util import b
00007 from tornado.web import Application, RequestHandler, asynchronous
00008
00009 from tornado import gen
00010
00011
00012 class GenTest(AsyncTestCase):
00013 def run_gen(self, f):
00014 f()
00015 self.wait()
00016
00017 def delay_callback(self, iterations, callback, arg):
00018 """Runs callback(arg) after a number of IOLoop iterations."""
00019 if iterations == 0:
00020 callback(arg)
00021 else:
00022 self.io_loop.add_callback(functools.partial(
00023 self.delay_callback, iterations - 1, callback, arg))
00024
00025 def test_no_yield(self):
00026 @gen.engine
00027 def f():
00028 self.stop()
00029 self.run_gen(f)
00030
00031 def test_inline_cb(self):
00032 @gen.engine
00033 def f():
00034 (yield gen.Callback("k1"))()
00035 res = yield gen.Wait("k1")
00036 assert res is None
00037 self.stop()
00038 self.run_gen(f)
00039
00040 def test_ioloop_cb(self):
00041 @gen.engine
00042 def f():
00043 self.io_loop.add_callback((yield gen.Callback("k1")))
00044 yield gen.Wait("k1")
00045 self.stop()
00046 self.run_gen(f)
00047
00048 def test_exception_phase1(self):
00049 @gen.engine
00050 def f():
00051 1 / 0
00052 self.assertRaises(ZeroDivisionError, self.run_gen, f)
00053
00054 def test_exception_phase2(self):
00055 @gen.engine
00056 def f():
00057 self.io_loop.add_callback((yield gen.Callback("k1")))
00058 yield gen.Wait("k1")
00059 1 / 0
00060 self.assertRaises(ZeroDivisionError, self.run_gen, f)
00061
00062 def test_exception_in_task_phase1(self):
00063 def fail_task(callback):
00064 1 / 0
00065
00066 @gen.engine
00067 def f():
00068 try:
00069 yield gen.Task(fail_task)
00070 raise Exception("did not get expected exception")
00071 except ZeroDivisionError:
00072 self.stop()
00073 self.run_gen(f)
00074
00075 def test_exception_in_task_phase2(self):
00076
00077 def fail_task(callback):
00078 self.io_loop.add_callback(lambda: 1 / 0)
00079
00080 @gen.engine
00081 def f():
00082 try:
00083 yield gen.Task(fail_task)
00084 raise Exception("did not get expected exception")
00085 except ZeroDivisionError:
00086 self.stop()
00087 self.run_gen(f)
00088
00089 def test_with_arg(self):
00090 @gen.engine
00091 def f():
00092 (yield gen.Callback("k1"))(42)
00093 res = yield gen.Wait("k1")
00094 self.assertEqual(42, res)
00095 self.stop()
00096 self.run_gen(f)
00097
00098 def test_key_reuse(self):
00099 @gen.engine
00100 def f():
00101 yield gen.Callback("k1")
00102 yield gen.Callback("k1")
00103 self.stop()
00104 self.assertRaises(gen.KeyReuseError, self.run_gen, f)
00105
00106 def test_key_mismatch(self):
00107 @gen.engine
00108 def f():
00109 yield gen.Callback("k1")
00110 yield gen.Wait("k2")
00111 self.stop()
00112 self.assertRaises(gen.UnknownKeyError, self.run_gen, f)
00113
00114 def test_leaked_callback(self):
00115 @gen.engine
00116 def f():
00117 yield gen.Callback("k1")
00118 self.stop()
00119 self.assertRaises(gen.LeakedCallbackError, self.run_gen, f)
00120
00121 def test_parallel_callback(self):
00122 @gen.engine
00123 def f():
00124 for k in range(3):
00125 self.io_loop.add_callback((yield gen.Callback(k)))
00126 yield gen.Wait(1)
00127 self.io_loop.add_callback((yield gen.Callback(3)))
00128 yield gen.Wait(0)
00129 yield gen.Wait(3)
00130 yield gen.Wait(2)
00131 self.stop()
00132 self.run_gen(f)
00133
00134 def test_bogus_yield(self):
00135 @gen.engine
00136 def f():
00137 yield 42
00138 self.assertRaises(gen.BadYieldError, self.run_gen, f)
00139
00140 def test_reuse(self):
00141 @gen.engine
00142 def f():
00143 self.io_loop.add_callback((yield gen.Callback(0)))
00144 yield gen.Wait(0)
00145 self.stop()
00146 self.run_gen(f)
00147 self.run_gen(f)
00148
00149 def test_task(self):
00150 @gen.engine
00151 def f():
00152 yield gen.Task(self.io_loop.add_callback)
00153 self.stop()
00154 self.run_gen(f)
00155
00156 def test_wait_all(self):
00157 @gen.engine
00158 def f():
00159 (yield gen.Callback("k1"))("v1")
00160 (yield gen.Callback("k2"))("v2")
00161 results = yield gen.WaitAll(["k1", "k2"])
00162 self.assertEqual(results, ["v1", "v2"])
00163 self.stop()
00164 self.run_gen(f)
00165
00166 def test_exception_in_yield(self):
00167 @gen.engine
00168 def f():
00169 try:
00170 yield gen.Wait("k1")
00171 raise Exception("did not get expected exception")
00172 except gen.UnknownKeyError:
00173 pass
00174 self.stop()
00175 self.run_gen(f)
00176
00177 def test_resume_after_exception_in_yield(self):
00178 @gen.engine
00179 def f():
00180 try:
00181 yield gen.Wait("k1")
00182 raise Exception("did not get expected exception")
00183 except gen.UnknownKeyError:
00184 pass
00185 (yield gen.Callback("k2"))("v2")
00186 self.assertEqual((yield gen.Wait("k2")), "v2")
00187 self.stop()
00188 self.run_gen(f)
00189
00190 def test_orphaned_callback(self):
00191 @gen.engine
00192 def f():
00193 self.orphaned_callback = yield gen.Callback(1)
00194 try:
00195 self.run_gen(f)
00196 raise Exception("did not get expected exception")
00197 except gen.LeakedCallbackError:
00198 pass
00199 self.orphaned_callback()
00200
00201 def test_multi(self):
00202 @gen.engine
00203 def f():
00204 (yield gen.Callback("k1"))("v1")
00205 (yield gen.Callback("k2"))("v2")
00206 results = yield [gen.Wait("k1"), gen.Wait("k2")]
00207 self.assertEqual(results, ["v1", "v2"])
00208 self.stop()
00209 self.run_gen(f)
00210
00211 def test_multi_delayed(self):
00212 @gen.engine
00213 def f():
00214
00215 responses = yield [
00216 gen.Task(self.delay_callback, 3, arg="v1"),
00217 gen.Task(self.delay_callback, 1, arg="v2"),
00218 ]
00219 self.assertEqual(responses, ["v1", "v2"])
00220 self.stop()
00221 self.run_gen(f)
00222
00223 def test_arguments(self):
00224 @gen.engine
00225 def f():
00226 (yield gen.Callback("noargs"))()
00227 self.assertEqual((yield gen.Wait("noargs")), None)
00228 (yield gen.Callback("1arg"))(42)
00229 self.assertEqual((yield gen.Wait("1arg")), 42)
00230
00231 (yield gen.Callback("kwargs"))(value=42)
00232 result = yield gen.Wait("kwargs")
00233 self.assertTrue(isinstance(result, gen.Arguments))
00234 self.assertEqual(((), dict(value=42)), result)
00235 self.assertEqual(dict(value=42), result.kwargs)
00236
00237 (yield gen.Callback("2args"))(42, 43)
00238 result = yield gen.Wait("2args")
00239 self.assertTrue(isinstance(result, gen.Arguments))
00240 self.assertEqual(((42, 43), {}), result)
00241 self.assertEqual((42, 43), result.args)
00242
00243 def task_func(callback):
00244 callback(None, error="foo")
00245 result = yield gen.Task(task_func)
00246 self.assertTrue(isinstance(result, gen.Arguments))
00247 self.assertEqual(((None,), dict(error="foo")), result)
00248
00249 self.stop()
00250 self.run_gen(f)
00251
00252 def test_stack_context_leak(self):
00253
00254
00255 from tornado import stack_context
00256
00257 @gen.engine
00258 def inner(callback):
00259 yield gen.Task(self.io_loop.add_callback)
00260 callback()
00261
00262 @gen.engine
00263 def outer():
00264 for i in xrange(10):
00265 yield gen.Task(inner)
00266 stack_increase = len(stack_context._state.contexts) - initial_stack_depth
00267 self.assertTrue(stack_increase <= 2)
00268 self.stop()
00269 initial_stack_depth = len(stack_context._state.contexts)
00270 self.run_gen(outer)
00271
00272
00273 class GenSequenceHandler(RequestHandler):
00274 @asynchronous
00275 @gen.engine
00276 def get(self):
00277 self.io_loop = self.request.connection.stream.io_loop
00278 self.io_loop.add_callback((yield gen.Callback("k1")))
00279 yield gen.Wait("k1")
00280 self.write("1")
00281 self.io_loop.add_callback((yield gen.Callback("k2")))
00282 yield gen.Wait("k2")
00283 self.write("2")
00284
00285 self.io_loop.add_callback((yield gen.Callback("k1")))
00286 yield gen.Wait("k1")
00287 self.finish("3")
00288
00289
00290 class GenTaskHandler(RequestHandler):
00291 @asynchronous
00292 @gen.engine
00293 def get(self):
00294 io_loop = self.request.connection.stream.io_loop
00295 client = AsyncHTTPClient(io_loop=io_loop)
00296 response = yield gen.Task(client.fetch, self.get_argument('url'))
00297 response.rethrow()
00298 self.finish(b("got response: ") + response.body)
00299
00300
00301 class GenExceptionHandler(RequestHandler):
00302 @asynchronous
00303 @gen.engine
00304 def get(self):
00305
00306 io_loop = self.request.connection.stream.io_loop
00307 yield gen.Task(io_loop.add_callback)
00308 raise Exception("oops")
00309
00310
00311 class GenYieldExceptionHandler(RequestHandler):
00312 @asynchronous
00313 @gen.engine
00314 def get(self):
00315 io_loop = self.request.connection.stream.io_loop
00316
00317
00318 def fail_task(callback):
00319 io_loop.add_callback(lambda: 1 / 0)
00320 try:
00321 yield gen.Task(fail_task)
00322 raise Exception("did not get expected exception")
00323 except ZeroDivisionError:
00324 self.finish('ok')
00325
00326
00327 class GenWebTest(AsyncHTTPTestCase, LogTrapTestCase):
00328 def get_app(self):
00329 return Application([
00330 ('/sequence', GenSequenceHandler),
00331 ('/task', GenTaskHandler),
00332 ('/exception', GenExceptionHandler),
00333 ('/yield_exception', GenYieldExceptionHandler),
00334 ])
00335
00336 def test_sequence_handler(self):
00337 response = self.fetch('/sequence')
00338 self.assertEqual(response.body, b("123"))
00339
00340 def test_task_handler(self):
00341 response = self.fetch('/task?url=%s' % url_escape(self.get_url('/sequence')))
00342 self.assertEqual(response.body, b("got response: 123"))
00343
00344 def test_exception_handler(self):
00345
00346 response = self.fetch('/exception')
00347 self.assertEqual(500, response.code)
00348
00349 def test_yield_exception_handler(self):
00350 response = self.fetch('/yield_exception')
00351 self.assertEqual(response.body, b('ok'))