]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add support in AsyncTestCase for detecting undecorated generators.
authorBen Darnell <ben@bendarnell.com>
Sat, 25 Jan 2014 18:43:27 +0000 (13:43 -0500)
committerBen Darnell <ben@bendarnell.com>
Sat, 25 Jan 2014 18:43:27 +0000 (13:43 -0500)
Fix a test that was not being run because of this issue.

tornado/test/testing_test.py
tornado/test/websocket_test.py
tornado/testing.py

index 64e5683e409745c91e560b76fcdafbbfc7df386f..569f32fc43e3a1491ebe49e0d3a5f69947413c47 100644 (file)
@@ -62,6 +62,39 @@ class AsyncTestCaseTest(AsyncTestCase):
         self.wait(timeout=0.15)
 
 
+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])
+
+    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):
         """
index 01fee72b28839a55eda84146529f6727fe7b3f7e..e4462b2ed6fe8ce1f1ca3493f6ba55d670e5c955 100644 (file)
@@ -83,8 +83,9 @@ class WebSocketTest(AsyncHTTPTestCase):
         ws.read_message(self.stop)
         response = self.wait().result()
         self.assertEqual(response, 'hello')
+        self.close_future.add_done_callback(lambda f: self.stop())
         ws.close()
-        yield self.close_future
+        self.wait()
 
     @gen_test
     def test_websocket_http_fail(self):
index 8355dcfc5f20f90191efa50adad5bce1680a94ea..c563bd711c4c69d7476d4d69f264aa7842070b8c 100644 (file)
@@ -38,6 +38,7 @@ import re
 import signal
 import socket
 import sys
+import types
 
 try:
     from cStringIO import StringIO  # py2
@@ -95,6 +96,35 @@ def get_async_test_timeout():
         return 5
 
 
+class _TestMethodWrapper(object):
+    """Wraps a test method to raise an error if it returns a value.
+
+    This is mainly used to detect undecorated generators (if a test
+    method yields it must use a decorator to consume the generator),
+    but will also detect other kinds of return values (these are not
+    necessarily errors, but we alert anyway since there is no good
+    reason to return a value from a test.
+    """
+    def __init__(self, orig_method):
+        self.orig_method = orig_method
+
+    def __call__(self):
+        result = self.orig_method()
+        if isinstance(result, types.GeneratorType):
+            raise TypeError("Generator test methods should be decorated with "
+                            "tornado.testing.gen_test")
+        elif result is not None:
+            raise ValueError("Return value from test method ignored: %r" %
+                             result)
+
+    def __getattr__(self, name):
+        """Proxy all unknown attributes to the original method.
+
+        This is important for some of the decorators in the `unittest`
+        module, such as `unittest.skipIf`.
+        """
+        return getattr(self.orig_method, name)
+
 class AsyncTestCase(unittest.TestCase):
     """`~unittest.TestCase` subclass for testing `.IOLoop`-based
     asynchronous code.
@@ -157,14 +187,20 @@ class AsyncTestCase(unittest.TestCase):
                 self.assertIn("FriendFeed", response.body)
                 self.stop()
     """
-    def __init__(self, *args, **kwargs):
-        super(AsyncTestCase, self).__init__(*args, **kwargs)
+    def __init__(self, methodName='runTest', **kwargs):
+        super(AsyncTestCase, self).__init__(methodName, **kwargs)
         self.__stopped = False
         self.__running = False
         self.__failure = None
         self.__stop_args = None
         self.__timeout = None
 
+        # It's easy to forget the @gen_test decorator, but if you do
+        # the test will silently be ignored because nothing will consume
+        # the generator.  Replace the test method with a wrapper that will
+        # make sure it's not an undecorated generator.
+        setattr(self, methodName, _TestMethodWrapper(getattr(self, methodName)))
+
     def setUp(self):
         super(AsyncTestCase, self).setUp()
         self.io_loop = self.get_new_ioloop()