From c851aa8a949524b35f72c82b45a52353aa3c0558 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 14:17:54 +0300 Subject: [PATCH] testing: allow to instantiate an empty AsyncTestCase `unittest.TestCase` has a feature where it allows instantiating `MyTestClass()` with the default method name `runTest` even if a `runTest` method doesn't actually exist. This is documented in `TestCase`'s docs under "Changed in version 3.2"[0]. Since version 8.2, pytest relies on this, and started breaking on Tornado's `AsyncTestCase`[1]. Change `AsyncTestCase` to allow empty instatiation, by matching the upstream code. [0] https://docs.python.org/3/library/unittest.html#unittest.TestCase [1] https://github.com/pytest-dev/pytest/issues/12263 --- tornado/test/testing_test.py | 9 +++++++++ tornado/testing.py | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index 0429feee83..8e2b8db428 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -61,6 +61,15 @@ def test_subsequent_wait_calls(self): self.io_loop.add_timeout(self.io_loop.time() + 0.2, self.stop) self.wait(timeout=0.4) + def test_empty_instantation_is_allowed(self): + """ + Test that empty instatiation of an AsyncTestCase is allowed. + + unittest.TestCase docs guarantee this working, and pytest's unittest + support relies on it. + """ + AsyncTestCaseTest() + class LeakTest(AsyncTestCase): def tearDown(self): diff --git a/tornado/testing.py b/tornado/testing.py index bdbff87bc3..9455411a6d 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -177,7 +177,17 @@ def __init__(self, methodName: str = "runTest") -> None: # 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))) + try: + test_method = getattr(self, methodName) + except AttributeError: + if methodName != "runTest": + # We allow instantiation with no explicit method name + # but not an *incorrect* or missing method name. + raise ValueError( + "no such test method in %s: %s" % (self.__class__, methodName) + ) + else: + setattr(self, methodName, _TestMethodWrapper(test_method)) # Not used in this class itself, but used by @gen_test self._test_generator = None # type: Optional[Union[Generator, Coroutine]]