diff options
author | Ćukasz Langa <lukasz@langa.pl> | 2021-09-22 16:48:17 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-22 16:48:17 (GMT) |
commit | e06b0fddf69b933fe82f60d78a0f6248ca36a0a3 (patch) | |
tree | 584da3a296d3661e072e1ced77893ac9d56264f0 | |
parent | 5482db5800d195d43d1e13e8c05e21c708dcfa50 (diff) | |
download | cpython-e06b0fddf69b933fe82f60d78a0f6248ca36a0a3.zip cpython-e06b0fddf69b933fe82f60d78a0f6248ca36a0a3.tar.gz cpython-e06b0fddf69b933fe82f60d78a0f6248ca36a0a3.tar.bz2 |
[3.9] bpo-45238: Fix unittest.IsolatedAsyncioTestCase.debug() (GH-28449) (GH-28522)
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>
-rw-r--r-- | Lib/unittest/async_case.py | 19 | ||||
-rw-r--r-- | Lib/unittest/case.py | 10 | ||||
-rw-r--r-- | Lib/unittest/test/test_async_case.py | 165 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-09-18-16-56-33.bpo-45238.Hng_9V.rst | 2 |
4 files changed, 134 insertions, 62 deletions
diff --git a/Lib/unittest/async_case.py b/Lib/unittest/async_case.py index 520213c..3938efa 100644 --- a/Lib/unittest/async_case.py +++ b/Lib/unittest/async_case.py @@ -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() diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 867455b..34f0362 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -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.""" diff --git a/Lib/unittest/test/test_async_case.py b/Lib/unittest/test/test_async_case.py index d01864b..2b6e751 100644 --- a/Lib/unittest/test/test_async_case.py +++ b/Lib/unittest/test/test_async_case.py @@ -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 index 0000000..857f315 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-09-18-16-56-33.bpo-45238.Hng_9V.rst @@ -0,0 +1,2 @@ +Fix :meth:`unittest.IsolatedAsyncioTestCase.debug`: it runs now asynchronous +methods and callbacks. |