from tornado import gen, ioloop from tornado.httpserver import HTTPServer from tornado.locks import Event from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, bind_unused_port, gen_test from tornado.web import Application import asyncio import contextlib import gc import os import platform import traceback import unittest import warnings @contextlib.contextmanager def set_environ(name, value): old_value = os.environ.get(name) os.environ[name] = value try: yield finally: if old_value is None: del os.environ[name] else: os.environ[name] = old_value class AsyncTestCaseTest(AsyncTestCase): def test_wait_timeout(self): time = self.io_loop.time # Accept default 5-second timeout, no error self.io_loop.add_timeout(time() + 0.01, self.stop) self.wait() # Timeout passed to wait() self.io_loop.add_timeout(time() + 1, self.stop) with self.assertRaises(self.failureException): self.wait(timeout=0.01) # Timeout set with environment variable self.io_loop.add_timeout(time() + 1, self.stop) with set_environ("ASYNC_TEST_TIMEOUT", "0.01"): with self.assertRaises(self.failureException): self.wait() def test_subsequent_wait_calls(self): """ This test makes sure that a second call to wait() clears the first timeout. """ # The first wait ends with time left on the clock self.io_loop.add_timeout(self.io_loop.time() + 0.00, self.stop) self.wait(timeout=0.1) # The second wait has enough time for itself but would fail if the # first wait's deadline were still in effect. self.io_loop.add_timeout(self.io_loop.time() + 0.2, self.stop) self.wait(timeout=0.4) class LeakTest(AsyncTestCase): def tearDown(self): super().tearDown() # Trigger a gc to make warnings more deterministic. gc.collect() def test_leaked_coroutine(self): # This test verifies that "leaked" coroutines are shut down # without triggering warnings like "task was destroyed but it # is pending". If this test were to fail, it would fail # because runtests.py detected unexpected output to stderr. event = Event() async def callback(): try: await event.wait() except asyncio.CancelledError: pass self.io_loop.add_callback(callback) self.io_loop.add_callback(self.stop) self.wait() class AsyncHTTPTestCaseTest(AsyncHTTPTestCase): def setUp(self): super().setUp() # Bind a second port. sock, port = bind_unused_port() app = Application() server = HTTPServer(app, **self.get_httpserver_options()) server.add_socket(sock) self.second_port = port self.second_server = server def get_app(self): return Application() def test_fetch_segment(self): path = "/path" response = self.fetch(path) self.assertEqual(response.request.url, self.get_url(path)) def test_fetch_full_http_url(self): # Ensure that self.fetch() recognizes absolute urls and does # not transform them into references to our main test server. path = "http://localhost:%d/path" % self.second_port response = self.fetch(path) self.assertEqual(response.request.url, path) def tearDown(self): self.second_server.stop() super().tearDown() class AsyncTestCaseWrapperTest(unittest.TestCase): def test_undecorated_generator(self): class Test(AsyncTestCase): def test_gen(self): yield test = Test("test_gen") result = unittest.TestResult() test.run(result) self.assertEqual(len(result.errors), 1) self.assertIn("should be decorated", result.errors[0][1]) @unittest.skipIf( platform.python_implementation() == "PyPy", "pypy destructor warnings cannot be silenced", ) def test_undecorated_coroutine(self): class Test(AsyncTestCase): async def test_coro(self): pass test = Test("test_coro") result = unittest.TestResult() # Silence "RuntimeWarning: coroutine 'test_coro' was never awaited". with warnings.catch_warnings(): warnings.simplefilter("ignore") test.run(result) self.assertEqual(len(result.errors), 1) self.assertIn("should be decorated", result.errors[0][1]) def test_undecorated_generator_with_skip(self): class Test(AsyncTestCase): @unittest.skip("don't run this") def test_gen(self): yield test = Test("test_gen") result = unittest.TestResult() test.run(result) self.assertEqual(len(result.errors), 0) self.assertEqual(len(result.skipped), 1) def test_other_return(self): class Test(AsyncTestCase): def test_other_return(self): return 42 test = Test("test_other_return") result = unittest.TestResult() test.run(result) self.assertEqual(len(result.errors), 1) self.assertIn("Return value from test method ignored", result.errors[0][1]) class SetUpTearDownTest(unittest.TestCase): def test_set_up_tear_down(self): """ This test makes sure that AsyncTestCase calls super methods for setUp and tearDown. InheritBoth is a subclass of both AsyncTestCase and SetUpTearDown, with the ordering so that the super of AsyncTestCase will be SetUpTearDown. """ events = [] result = unittest.TestResult() class SetUpTearDown(unittest.TestCase): def setUp(self): events.append("setUp") def tearDown(self): events.append("tearDown") class InheritBoth(AsyncTestCase, SetUpTearDown): def test(self): events.append("test") InheritBoth("test").run(result) expected = ["setUp", "test", "tearDown"] self.assertEqual(expected, events) class AsyncHTTPTestCaseSetUpTearDownTest(unittest.TestCase): def test_tear_down_releases_app_and_http_server(self): result = unittest.TestResult() class SetUpTearDown(AsyncHTTPTestCase): def get_app(self): return Application() def test(self): self.assertTrue(hasattr(self, "_app")) self.assertTrue(hasattr(self, "http_server")) test = SetUpTearDown("test") test.run(result) self.assertFalse(hasattr(test, "_app")) self.assertFalse(hasattr(test, "http_server")) class GenTest(AsyncTestCase): def setUp(self): super().setUp() self.finished = False def tearDown(self): self.assertTrue(self.finished) super().tearDown() @gen_test def test_sync(self): self.finished = True @gen_test def test_async(self): yield gen.moment self.finished = True def test_timeout(self): # Set a short timeout and exceed it. @gen_test(timeout=0.1) def test(self): yield gen.sleep(1) # This can't use assertRaises because we need to inspect the # exc_info triple (and not just the exception object) try: test(self) self.fail("did not get expected exception") except ioloop.TimeoutError: # The stack trace should blame the add_timeout line, not just # unrelated IOLoop/testing internals. self.assertIn("gen.sleep(1)", traceback.format_exc()) self.finished = True def test_no_timeout(self): # A test that does not exceed its timeout should succeed. @gen_test(timeout=1) def test(self): yield gen.sleep(0.1) test(self) self.finished = True def test_timeout_environment_variable(self): @gen_test(timeout=0.5) def test_long_timeout(self): yield gen.sleep(0.25) # Uses provided timeout of 0.5 seconds, doesn't time out. with set_environ("ASYNC_TEST_TIMEOUT", "0.1"): test_long_timeout(self) self.finished = True def test_no_timeout_environment_variable(self): @gen_test(timeout=0.01) def test_short_timeout(self): yield gen.sleep(1) # Uses environment-variable timeout of 0.1, times out. with set_environ("ASYNC_TEST_TIMEOUT", "0.1"): with self.assertRaises(ioloop.TimeoutError): test_short_timeout(self) self.finished = True def test_with_method_args(self): @gen_test def test_with_args(self, *args): self.assertEqual(args, ("test",)) yield gen.moment test_with_args(self, "test") self.finished = True def test_with_method_kwargs(self): @gen_test def test_with_kwargs(self, **kwargs): self.assertDictEqual(kwargs, {"test": "test"}) yield gen.moment test_with_kwargs(self, test="test") self.finished = True def test_native_coroutine(self): @gen_test async def test(self): self.finished = True test(self) def test_native_coroutine_timeout(self): # Set a short timeout and exceed it. @gen_test(timeout=0.1) async def test(self): await gen.sleep(1) try: test(self) self.fail("did not get expected exception") except ioloop.TimeoutError: self.finished = True class GetNewIOLoopTest(AsyncTestCase): def get_new_ioloop(self): # Use the current loop instead of creating a new one here. return ioloop.IOLoop.current() def setUp(self): # This simulates the effect of an asyncio test harness like # pytest-asyncio. self.orig_loop = asyncio.get_event_loop() self.new_loop = asyncio.new_event_loop() asyncio.set_event_loop(self.new_loop) super().setUp() def tearDown(self): super().tearDown() # AsyncTestCase must not affect the existing asyncio loop. self.assertFalse(asyncio.get_event_loop().is_closed()) asyncio.set_event_loop(self.orig_loop) self.new_loop.close() def test_loop(self): self.assertIs(self.io_loop.asyncio_loop, self.new_loop) # type: ignore if __name__ == "__main__": unittest.main()