]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.9] bpo-45238: Fix unittest.IsolatedAsyncioTestCase.debug() (GH-28449) (GH-28522)
authorŁukasz Langa <lukasz@langa.pl>
Wed, 22 Sep 2021 16:48:17 +0000 (18:48 +0200)
committerGitHub <noreply@github.com>
Wed, 22 Sep 2021 16:48:17 +0000 (18:48 +0200)
It runs now asynchronous methods and callbacks.

If it fails, doCleanups() can be called for cleaning up..
(cherry picked from commit ecb6922ff2d56476a6cfb0941ae55aca5e7fae3d)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/unittest/async_case.py
Lib/unittest/case.py
Lib/unittest/test/test_async_case.py
Misc/NEWS.d/next/Library/2021-09-18-16-56-33.bpo-45238.Hng_9V.rst [new file with mode: 0644]

index 520213c37275555f29d37bf361202fb72d1af4ec..3938efa3d423f3eb7838b052b49bb4552a7308ec 100644 (file)
@@ -72,15 +72,15 @@ class IsolatedAsyncioTestCase(TestCase):
         self._callMaybeAsync(function, *args, **kwargs)
 
     def _callAsync(self, func, /, *args, **kwargs):
-        assert self._asyncioTestLoop is not None
+        assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
         ret = func(*args, **kwargs)
-        assert inspect.isawaitable(ret)
+        assert inspect.isawaitable(ret), f'{func!r} returned non-awaitable'
         fut = self._asyncioTestLoop.create_future()
         self._asyncioCallsQueue.put_nowait((fut, ret))
         return self._asyncioTestLoop.run_until_complete(fut)
 
     def _callMaybeAsync(self, func, /, *args, **kwargs):
-        assert self._asyncioTestLoop is not None
+        assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
         ret = func(*args, **kwargs)
         if inspect.isawaitable(ret):
             fut = self._asyncioTestLoop.create_future()
@@ -109,7 +109,7 @@ class IsolatedAsyncioTestCase(TestCase):
                     fut.set_exception(ex)
 
     def _setupAsyncioLoop(self):
-        assert self._asyncioTestLoop is None
+        assert self._asyncioTestLoop is None, 'asyncio test loop already initialized'
         loop = asyncio.new_event_loop()
         asyncio.set_event_loop(loop)
         loop.set_debug(True)
@@ -119,7 +119,7 @@ class IsolatedAsyncioTestCase(TestCase):
         loop.run_until_complete(fut)
 
     def _tearDownAsyncioLoop(self):
-        assert self._asyncioTestLoop is not None
+        assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
         loop = self._asyncioTestLoop
         self._asyncioTestLoop = None
         self._asyncioCallsQueue.put_nowait(None)
@@ -158,3 +158,12 @@ class IsolatedAsyncioTestCase(TestCase):
             return super().run(result)
         finally:
             self._tearDownAsyncioLoop()
+
+    def debug(self):
+        self._setupAsyncioLoop()
+        super().debug()
+        self._tearDownAsyncioLoop()
+
+    def __del__(self):
+        if self._asyncioTestLoop is not None:
+            self._tearDownAsyncioLoop()
index 867455b96fa9c0b4de68aabd4a674b334483f771..34f03628ed068a986a99aeda1162a8c48dc22966 100644 (file)
@@ -660,12 +660,12 @@ class TestCase(object):
                         or getattr(testMethod, '__unittest_skip_why__', ''))
             raise SkipTest(skip_why)
 
-        self.setUp()
-        testMethod()
-        self.tearDown()
+        self._callSetUp()
+        self._callTestMethod(testMethod)
+        self._callTearDown()
         while self._cleanups:
-            function, args, kwargs = self._cleanups.pop(-1)
-            function(*args, **kwargs)
+            function, args, kwargs = self._cleanups.pop()
+            self._callCleanup(function, *args, **kwargs)
 
     def skipTest(self, reason):
         """Skip this test."""
index d01864b6936ca8d2ec5d47e2c2b6bd7fb1ab092f..2b6e751f3b027fad50809923be9fdd4ab0fde420 100644 (file)
@@ -1,5 +1,10 @@
 import asyncio
 import unittest
+from test import support
+
+
+class MyException(Exception):
+    pass
 
 
 def tearDownModule():
@@ -7,9 +12,14 @@ def tearDownModule():
 
 
 class TestAsyncCase(unittest.TestCase):
-    def test_full_cycle(self):
-        events = []
+    maxDiff = None
+
+    def tearDown(self):
+        # Ensure that IsolatedAsyncioTestCase instances are destroyed before
+        # starting a new event loop
+        support.gc_collect()
 
+    def test_full_cycle(self):
         class Test(unittest.IsolatedAsyncioTestCase):
             def setUp(self):
                 self.assertEqual(events, [])
@@ -18,12 +28,13 @@ class TestAsyncCase(unittest.TestCase):
             async def asyncSetUp(self):
                 self.assertEqual(events, ['setUp'])
                 events.append('asyncSetUp')
+                self.addAsyncCleanup(self.on_cleanup1)
 
             async def test_func(self):
                 self.assertEqual(events, ['setUp',
                                           'asyncSetUp'])
                 events.append('test')
-                self.addAsyncCleanup(self.on_cleanup)
+                self.addAsyncCleanup(self.on_cleanup2)
 
             async def asyncTearDown(self):
                 self.assertEqual(events, ['setUp',
@@ -38,34 +49,48 @@ class TestAsyncCase(unittest.TestCase):
                                           'asyncTearDown'])
                 events.append('tearDown')
 
-            async def on_cleanup(self):
+            async def on_cleanup1(self):
+                self.assertEqual(events, ['setUp',
+                                          'asyncSetUp',
+                                          'test',
+                                          'asyncTearDown',
+                                          'tearDown',
+                                          'cleanup2'])
+                events.append('cleanup1')
+
+            async def on_cleanup2(self):
                 self.assertEqual(events, ['setUp',
                                           'asyncSetUp',
                                           'test',
                                           'asyncTearDown',
                                           'tearDown'])
-                events.append('cleanup')
+                events.append('cleanup2')
 
+        events = []
         test = Test("test_func")
-        test.run()
-        self.assertEqual(events, ['setUp',
-                                  'asyncSetUp',
-                                  'test',
-                                  'asyncTearDown',
-                                  'tearDown',
-                                  'cleanup'])
+        result = test.run()
+        self.assertEqual(result.errors, [])
+        self.assertEqual(result.failures, [])
+        expected = ['setUp', 'asyncSetUp', 'test',
+                    'asyncTearDown', 'tearDown', 'cleanup2', 'cleanup1']
+        self.assertEqual(events, expected)
 
-    def test_exception_in_setup(self):
         events = []
+        test = Test("test_func")
+        test.debug()
+        self.assertEqual(events, expected)
+        test.doCleanups()
+        self.assertEqual(events, expected)
 
+    def test_exception_in_setup(self):
         class Test(unittest.IsolatedAsyncioTestCase):
             async def asyncSetUp(self):
                 events.append('asyncSetUp')
-                raise Exception()
+                self.addAsyncCleanup(self.on_cleanup)
+                raise MyException()
 
             async def test_func(self):
                 events.append('test')
-                self.addAsyncCleanup(self.on_cleanup)
 
             async def asyncTearDown(self):
                 events.append('asyncTearDown')
@@ -74,21 +99,34 @@ class TestAsyncCase(unittest.TestCase):
                 events.append('cleanup')
 
 
+        events = []
         test = Test("test_func")
-        test.run()
-        self.assertEqual(events, ['asyncSetUp'])
+        result = test.run()
+        self.assertEqual(events, ['asyncSetUp', 'cleanup'])
+        self.assertIs(result.errors[0][0], test)
+        self.assertIn('MyException', result.errors[0][1])
 
-    def test_exception_in_test(self):
         events = []
+        test = Test("test_func")
+        try:
+            test.debug()
+        except MyException:
+            pass
+        else:
+            self.fail('Expected a MyException exception')
+        self.assertEqual(events, ['asyncSetUp'])
+        test.doCleanups()
+        self.assertEqual(events, ['asyncSetUp', 'cleanup'])
 
+    def test_exception_in_test(self):
         class Test(unittest.IsolatedAsyncioTestCase):
             async def asyncSetUp(self):
                 events.append('asyncSetUp')
 
             async def test_func(self):
                 events.append('test')
-                raise Exception()
                 self.addAsyncCleanup(self.on_cleanup)
+                raise MyException()
 
             async def asyncTearDown(self):
                 events.append('asyncTearDown')
@@ -96,13 +134,26 @@ class TestAsyncCase(unittest.TestCase):
             async def on_cleanup(self):
                 events.append('cleanup')
 
+        events = []
         test = Test("test_func")
-        test.run()
-        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown'])
+        result = test.run()
+        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
+        self.assertIs(result.errors[0][0], test)
+        self.assertIn('MyException', result.errors[0][1])
 
-    def test_exception_in_test_after_adding_cleanup(self):
         events = []
+        test = Test("test_func")
+        try:
+            test.debug()
+        except MyException:
+            pass
+        else:
+            self.fail('Expected a MyException exception')
+        self.assertEqual(events, ['asyncSetUp', 'test'])
+        test.doCleanups()
+        self.assertEqual(events, ['asyncSetUp', 'test', 'cleanup'])
 
+    def test_exception_in_tear_down(self):
         class Test(unittest.IsolatedAsyncioTestCase):
             async def asyncSetUp(self):
                 events.append('asyncSetUp')
@@ -110,62 +161,73 @@ class TestAsyncCase(unittest.TestCase):
             async def test_func(self):
                 events.append('test')
                 self.addAsyncCleanup(self.on_cleanup)
-                raise Exception()
 
             async def asyncTearDown(self):
                 events.append('asyncTearDown')
+                raise MyException()
 
             async def on_cleanup(self):
                 events.append('cleanup')
 
+        events = []
         test = Test("test_func")
-        test.run()
+        result = test.run()
         self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
+        self.assertIs(result.errors[0][0], test)
+        self.assertIn('MyException', result.errors[0][1])
 
-    def test_exception_in_tear_down(self):
         events = []
-
-        class Test(unittest.IsolatedAsyncioTestCase):
-            async def asyncSetUp(self):
-                events.append('asyncSetUp')
-
-            async def test_func(self):
-                events.append('test')
-                self.addAsyncCleanup(self.on_cleanup)
-
-            async def asyncTearDown(self):
-                events.append('asyncTearDown')
-                raise Exception()
-
-            async def on_cleanup(self):
-                events.append('cleanup')
-
         test = Test("test_func")
-        test.run()
+        try:
+            test.debug()
+        except MyException:
+            pass
+        else:
+            self.fail('Expected a MyException exception')
+        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown'])
+        test.doCleanups()
         self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
 
-
     def test_exception_in_tear_clean_up(self):
-        events = []
-
         class Test(unittest.IsolatedAsyncioTestCase):
             async def asyncSetUp(self):
                 events.append('asyncSetUp')
 
             async def test_func(self):
                 events.append('test')
-                self.addAsyncCleanup(self.on_cleanup)
+                self.addAsyncCleanup(self.on_cleanup1)
+                self.addAsyncCleanup(self.on_cleanup2)
 
             async def asyncTearDown(self):
                 events.append('asyncTearDown')
 
-            async def on_cleanup(self):
-                events.append('cleanup')
-                raise Exception()
+            async def on_cleanup1(self):
+                events.append('cleanup1')
+                raise MyException('some error')
 
+            async def on_cleanup2(self):
+                events.append('cleanup2')
+                raise MyException('other error')
+
+        events = []
         test = Test("test_func")
-        test.run()
-        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
+        result = test.run()
+        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup2', 'cleanup1'])
+        self.assertIs(result.errors[0][0], test)
+        self.assertIn('MyException: other error', result.errors[0][1])
+        self.assertIn('MyException: some error', result.errors[1][1])
+
+        events = []
+        test = Test("test_func")
+        try:
+            test.debug()
+        except MyException:
+            pass
+        else:
+            self.fail('Expected a MyException exception')
+        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup2'])
+        test.doCleanups()
+        self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup2', 'cleanup1'])
 
     def test_cleanups_interleave_order(self):
         events = []
@@ -217,6 +279,5 @@ class TestAsyncCase(unittest.TestCase):
         self.assertFalse(output.wasSuccessful())
 
 
-
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2021-09-18-16-56-33.bpo-45238.Hng_9V.rst b/Misc/NEWS.d/next/Library/2021-09-18-16-56-33.bpo-45238.Hng_9V.rst
new file mode 100644 (file)
index 0000000..857f315
--- /dev/null
@@ -0,0 +1,2 @@
+Fix :meth:`unittest.IsolatedAsyncioTestCase.debug`: it runs now asynchronous
+methods and callbacks.