mirror of
				https://github.com/python/cpython.git
				synced 2025-11-01 06:01:29 +00:00 
			
		
		
		
	bpo-45238: Fix unittest.IsolatedAsyncioTestCase.debug() (GH-28449)
It runs now asynchronous methods and callbacks. If it fails, doCleanups() can be called for cleaning up.
This commit is contained in:
		
							parent
							
								
									58f8adfda3
								
							
						
					
					
						commit
						ecb6922ff2
					
				
					 4 changed files with 180 additions and 65 deletions
				
			
		|  | @ -75,15 +75,15 @@ def _callCleanup(self, function, *args, **kwargs): | |||
|         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() | ||||
|  | @ -112,7 +112,7 @@ async def _asyncioLoopRunner(self, fut): | |||
|                     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) | ||||
|  | @ -122,7 +122,7 @@ def _setupAsyncioLoop(self): | |||
|         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) | ||||
|  | @ -161,3 +161,12 @@ def run(self, result=None): | |||
|             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() | ||||
|  |  | |||
|  | @ -655,12 +655,12 @@ def debug(self): | |||
|                         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.""" | ||||
|  |  | |||
|  | @ -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 @@ def setUp(self): | |||
|             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 @@ def tearDown(self): | |||
|                                           'asyncTearDown']) | ||||
|                 events.append('tearDown') | ||||
| 
 | ||||
|             async def on_cleanup(self): | ||||
|                 self.assertEqual(events, ['setUp', | ||||
|                                           'asyncSetUp', | ||||
|                                           'test', | ||||
|                                           'asyncTearDown', | ||||
|                                           'tearDown']) | ||||
|                 events.append('cleanup') | ||||
| 
 | ||||
|         test = Test("test_func") | ||||
|         test.run() | ||||
|             async def on_cleanup1(self): | ||||
|                 self.assertEqual(events, ['setUp', | ||||
|                                           'asyncSetUp', | ||||
|                                           'test', | ||||
|                                           'asyncTearDown', | ||||
|                                           'tearDown', | ||||
|                                   'cleanup']) | ||||
|                                           'cleanup2']) | ||||
|                 events.append('cleanup1') | ||||
| 
 | ||||
|             async def on_cleanup2(self): | ||||
|                 self.assertEqual(events, ['setUp', | ||||
|                                           'asyncSetUp', | ||||
|                                           'test', | ||||
|                                           'asyncTearDown', | ||||
|                                           'tearDown']) | ||||
|                 events.append('cleanup2') | ||||
| 
 | ||||
|         events = [] | ||||
|         test = Test("test_func") | ||||
|         result = test.run() | ||||
|         self.assertEqual(result.errors, []) | ||||
|         self.assertEqual(result.failures, []) | ||||
|         expected = ['setUp', 'asyncSetUp', 'test', | ||||
|                     'asyncTearDown', 'tearDown', 'cleanup2', 'cleanup1'] | ||||
|         self.assertEqual(events, expected) | ||||
| 
 | ||||
|         events = [] | ||||
|         test = Test("test_func") | ||||
|         test.debug() | ||||
|         self.assertEqual(events, expected) | ||||
|         test.doCleanups() | ||||
|         self.assertEqual(events, expected) | ||||
| 
 | ||||
|     def test_exception_in_setup(self): | ||||
|         events = [] | ||||
| 
 | ||||
|         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,35 +99,26 @@ async def on_cleanup(self): | |||
|                 events.append('cleanup') | ||||
| 
 | ||||
| 
 | ||||
|         events = [] | ||||
|         test = Test("test_func") | ||||
|         test.run() | ||||
|         result = test.run() | ||||
|         self.assertEqual(events, ['asyncSetUp', 'cleanup']) | ||||
|         self.assertIs(result.errors[0][0], test) | ||||
|         self.assertIn('MyException', result.errors[0][1]) | ||||
| 
 | ||||
|         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): | ||||
|         events = [] | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|             async def asyncTearDown(self): | ||||
|                 events.append('asyncTearDown') | ||||
| 
 | ||||
|             async def on_cleanup(self): | ||||
|                 events.append('cleanup') | ||||
| 
 | ||||
|         test = Test("test_func") | ||||
|         test.run() | ||||
|         self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown']) | ||||
| 
 | ||||
|     def test_exception_in_test_after_adding_cleanup(self): | ||||
|         events = [] | ||||
| 
 | ||||
|         class Test(unittest.IsolatedAsyncioTestCase): | ||||
|             async def asyncSetUp(self): | ||||
|                 events.append('asyncSetUp') | ||||
|  | @ -110,7 +126,7 @@ async def asyncSetUp(self): | |||
|             async def test_func(self): | ||||
|                 events.append('test') | ||||
|                 self.addAsyncCleanup(self.on_cleanup) | ||||
|                 raise Exception() | ||||
|                 raise MyException() | ||||
| 
 | ||||
|             async def asyncTearDown(self): | ||||
|                 events.append('asyncTearDown') | ||||
|  | @ -118,13 +134,26 @@ async def asyncTearDown(self): | |||
|             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]) | ||||
| 
 | ||||
|         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): | ||||
|         events = [] | ||||
| 
 | ||||
|         class Test(unittest.IsolatedAsyncioTestCase): | ||||
|             async def asyncSetUp(self): | ||||
|                 events.append('asyncSetUp') | ||||
|  | @ -135,37 +164,70 @@ async def test_func(self): | |||
| 
 | ||||
|             async def asyncTearDown(self): | ||||
|                 events.append('asyncTearDown') | ||||
|                 raise Exception() | ||||
|                 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]) | ||||
| 
 | ||||
|         events = [] | ||||
|         test = Test("test_func") | ||||
|         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_deprecation_of_return_val_from_test(self): | ||||
|         # Issue 41322 - deprecate return of value!=None from a test | ||||
|  | @ -255,7 +317,49 @@ async def coro(): | |||
|         output = test.run() | ||||
|         self.assertTrue(cancelled) | ||||
| 
 | ||||
|     def test_debug_cleanup_same_loop(self): | ||||
|         class Test(unittest.IsolatedAsyncioTestCase): | ||||
|             async def asyncSetUp(self): | ||||
|                 async def coro(): | ||||
|                     await asyncio.sleep(0) | ||||
|                 fut = asyncio.ensure_future(coro()) | ||||
|                 self.addAsyncCleanup(self.cleanup, fut) | ||||
|                 events.append('asyncSetUp') | ||||
| 
 | ||||
|             async def test_func(self): | ||||
|                 events.append('test') | ||||
|                 raise MyException() | ||||
| 
 | ||||
|             async def asyncTearDown(self): | ||||
|                 events.append('asyncTearDown') | ||||
| 
 | ||||
|             async def cleanup(self, fut): | ||||
|                 try: | ||||
|                     # Raises an exception if in different loop | ||||
|                     await asyncio.wait([fut]) | ||||
|                     events.append('cleanup') | ||||
|                 except: | ||||
|                     import traceback | ||||
|                     traceback.print_exc() | ||||
|                     raise | ||||
| 
 | ||||
|         events = [] | ||||
|         test = Test("test_func") | ||||
|         result = test.run() | ||||
|         self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) | ||||
|         self.assertIn('MyException', result.errors[0][1]) | ||||
| 
 | ||||
|         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']) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|  |  | |||
|  | @ -0,0 +1,2 @@ | |||
| Fix :meth:`unittest.IsolatedAsyncioTestCase.debug`: it runs now asynchronous | ||||
| methods and callbacks. | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Serhiy Storchaka
						Serhiy Storchaka