mirror of
https://github.com/python/cpython.git
synced 2025-07-23 19:25:40 +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 @@ class IsolatedAsyncioTestCase(TestCase):
|
||||||
self._callMaybeAsync(function, *args, **kwargs)
|
self._callMaybeAsync(function, *args, **kwargs)
|
||||||
|
|
||||||
def _callAsync(self, func, /, *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)
|
ret = func(*args, **kwargs)
|
||||||
assert inspect.isawaitable(ret)
|
assert inspect.isawaitable(ret), f'{func!r} returned non-awaitable'
|
||||||
fut = self._asyncioTestLoop.create_future()
|
fut = self._asyncioTestLoop.create_future()
|
||||||
self._asyncioCallsQueue.put_nowait((fut, ret))
|
self._asyncioCallsQueue.put_nowait((fut, ret))
|
||||||
return self._asyncioTestLoop.run_until_complete(fut)
|
return self._asyncioTestLoop.run_until_complete(fut)
|
||||||
|
|
||||||
def _callMaybeAsync(self, func, /, *args, **kwargs):
|
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)
|
ret = func(*args, **kwargs)
|
||||||
if inspect.isawaitable(ret):
|
if inspect.isawaitable(ret):
|
||||||
fut = self._asyncioTestLoop.create_future()
|
fut = self._asyncioTestLoop.create_future()
|
||||||
|
@ -112,7 +112,7 @@ class IsolatedAsyncioTestCase(TestCase):
|
||||||
fut.set_exception(ex)
|
fut.set_exception(ex)
|
||||||
|
|
||||||
def _setupAsyncioLoop(self):
|
def _setupAsyncioLoop(self):
|
||||||
assert self._asyncioTestLoop is None
|
assert self._asyncioTestLoop is None, 'asyncio test loop already initialized'
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
loop.set_debug(True)
|
loop.set_debug(True)
|
||||||
|
@ -122,7 +122,7 @@ class IsolatedAsyncioTestCase(TestCase):
|
||||||
loop.run_until_complete(fut)
|
loop.run_until_complete(fut)
|
||||||
|
|
||||||
def _tearDownAsyncioLoop(self):
|
def _tearDownAsyncioLoop(self):
|
||||||
assert self._asyncioTestLoop is not None
|
assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
|
||||||
loop = self._asyncioTestLoop
|
loop = self._asyncioTestLoop
|
||||||
self._asyncioTestLoop = None
|
self._asyncioTestLoop = None
|
||||||
self._asyncioCallsQueue.put_nowait(None)
|
self._asyncioCallsQueue.put_nowait(None)
|
||||||
|
@ -161,3 +161,12 @@ class IsolatedAsyncioTestCase(TestCase):
|
||||||
return super().run(result)
|
return super().run(result)
|
||||||
finally:
|
finally:
|
||||||
self._tearDownAsyncioLoop()
|
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 @@ class TestCase(object):
|
||||||
or getattr(testMethod, '__unittest_skip_why__', ''))
|
or getattr(testMethod, '__unittest_skip_why__', ''))
|
||||||
raise SkipTest(skip_why)
|
raise SkipTest(skip_why)
|
||||||
|
|
||||||
self.setUp()
|
self._callSetUp()
|
||||||
testMethod()
|
self._callTestMethod(testMethod)
|
||||||
self.tearDown()
|
self._callTearDown()
|
||||||
while self._cleanups:
|
while self._cleanups:
|
||||||
function, args, kwargs = self._cleanups.pop(-1)
|
function, args, kwargs = self._cleanups.pop()
|
||||||
function(*args, **kwargs)
|
self._callCleanup(function, *args, **kwargs)
|
||||||
|
|
||||||
def skipTest(self, reason):
|
def skipTest(self, reason):
|
||||||
"""Skip this test."""
|
"""Skip this test."""
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import unittest
|
import unittest
|
||||||
|
from test import support
|
||||||
|
|
||||||
|
|
||||||
|
class MyException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule():
|
def tearDownModule():
|
||||||
|
@ -7,9 +12,14 @@ def tearDownModule():
|
||||||
|
|
||||||
|
|
||||||
class TestAsyncCase(unittest.TestCase):
|
class TestAsyncCase(unittest.TestCase):
|
||||||
def test_full_cycle(self):
|
maxDiff = None
|
||||||
events = []
|
|
||||||
|
|
||||||
|
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):
|
class Test(unittest.IsolatedAsyncioTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.assertEqual(events, [])
|
self.assertEqual(events, [])
|
||||||
|
@ -18,12 +28,13 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
self.assertEqual(events, ['setUp'])
|
self.assertEqual(events, ['setUp'])
|
||||||
events.append('asyncSetUp')
|
events.append('asyncSetUp')
|
||||||
|
self.addAsyncCleanup(self.on_cleanup1)
|
||||||
|
|
||||||
async def test_func(self):
|
async def test_func(self):
|
||||||
self.assertEqual(events, ['setUp',
|
self.assertEqual(events, ['setUp',
|
||||||
'asyncSetUp'])
|
'asyncSetUp'])
|
||||||
events.append('test')
|
events.append('test')
|
||||||
self.addAsyncCleanup(self.on_cleanup)
|
self.addAsyncCleanup(self.on_cleanup2)
|
||||||
|
|
||||||
async def asyncTearDown(self):
|
async def asyncTearDown(self):
|
||||||
self.assertEqual(events, ['setUp',
|
self.assertEqual(events, ['setUp',
|
||||||
|
@ -38,34 +49,48 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
'asyncTearDown'])
|
'asyncTearDown'])
|
||||||
events.append('tearDown')
|
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',
|
self.assertEqual(events, ['setUp',
|
||||||
'asyncSetUp',
|
'asyncSetUp',
|
||||||
'test',
|
'test',
|
||||||
'asyncTearDown',
|
'asyncTearDown',
|
||||||
'tearDown'])
|
'tearDown'])
|
||||||
events.append('cleanup')
|
events.append('cleanup2')
|
||||||
|
|
||||||
|
events = []
|
||||||
test = Test("test_func")
|
test = Test("test_func")
|
||||||
test.run()
|
result = test.run()
|
||||||
self.assertEqual(events, ['setUp',
|
self.assertEqual(result.errors, [])
|
||||||
'asyncSetUp',
|
self.assertEqual(result.failures, [])
|
||||||
'test',
|
expected = ['setUp', 'asyncSetUp', 'test',
|
||||||
'asyncTearDown',
|
'asyncTearDown', 'tearDown', 'cleanup2', 'cleanup1']
|
||||||
'tearDown',
|
self.assertEqual(events, expected)
|
||||||
'cleanup'])
|
|
||||||
|
events = []
|
||||||
|
test = Test("test_func")
|
||||||
|
test.debug()
|
||||||
|
self.assertEqual(events, expected)
|
||||||
|
test.doCleanups()
|
||||||
|
self.assertEqual(events, expected)
|
||||||
|
|
||||||
def test_exception_in_setup(self):
|
def test_exception_in_setup(self):
|
||||||
events = []
|
|
||||||
|
|
||||||
class Test(unittest.IsolatedAsyncioTestCase):
|
class Test(unittest.IsolatedAsyncioTestCase):
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
events.append('asyncSetUp')
|
events.append('asyncSetUp')
|
||||||
raise Exception()
|
self.addAsyncCleanup(self.on_cleanup)
|
||||||
|
raise MyException()
|
||||||
|
|
||||||
async def test_func(self):
|
async def test_func(self):
|
||||||
events.append('test')
|
events.append('test')
|
||||||
self.addAsyncCleanup(self.on_cleanup)
|
|
||||||
|
|
||||||
async def asyncTearDown(self):
|
async def asyncTearDown(self):
|
||||||
events.append('asyncTearDown')
|
events.append('asyncTearDown')
|
||||||
|
@ -74,35 +99,26 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
events.append('cleanup')
|
events.append('cleanup')
|
||||||
|
|
||||||
|
|
||||||
|
events = []
|
||||||
test = Test("test_func")
|
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'])
|
self.assertEqual(events, ['asyncSetUp'])
|
||||||
|
test.doCleanups()
|
||||||
|
self.assertEqual(events, ['asyncSetUp', 'cleanup'])
|
||||||
|
|
||||||
def test_exception_in_test(self):
|
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):
|
class Test(unittest.IsolatedAsyncioTestCase):
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
events.append('asyncSetUp')
|
events.append('asyncSetUp')
|
||||||
|
@ -110,7 +126,7 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
async def test_func(self):
|
async def test_func(self):
|
||||||
events.append('test')
|
events.append('test')
|
||||||
self.addAsyncCleanup(self.on_cleanup)
|
self.addAsyncCleanup(self.on_cleanup)
|
||||||
raise Exception()
|
raise MyException()
|
||||||
|
|
||||||
async def asyncTearDown(self):
|
async def asyncTearDown(self):
|
||||||
events.append('asyncTearDown')
|
events.append('asyncTearDown')
|
||||||
|
@ -118,13 +134,26 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
async def on_cleanup(self):
|
async def on_cleanup(self):
|
||||||
events.append('cleanup')
|
events.append('cleanup')
|
||||||
|
|
||||||
|
events = []
|
||||||
test = Test("test_func")
|
test = Test("test_func")
|
||||||
test.run()
|
result = test.run()
|
||||||
self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
|
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):
|
def test_exception_in_tear_down(self):
|
||||||
events = []
|
|
||||||
|
|
||||||
class Test(unittest.IsolatedAsyncioTestCase):
|
class Test(unittest.IsolatedAsyncioTestCase):
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
events.append('asyncSetUp')
|
events.append('asyncSetUp')
|
||||||
|
@ -135,37 +164,70 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
|
|
||||||
async def asyncTearDown(self):
|
async def asyncTearDown(self):
|
||||||
events.append('asyncTearDown')
|
events.append('asyncTearDown')
|
||||||
raise Exception()
|
raise MyException()
|
||||||
|
|
||||||
async def on_cleanup(self):
|
async def on_cleanup(self):
|
||||||
events.append('cleanup')
|
events.append('cleanup')
|
||||||
|
|
||||||
|
events = []
|
||||||
test = Test("test_func")
|
test = Test("test_func")
|
||||||
test.run()
|
result = test.run()
|
||||||
self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
|
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):
|
def test_exception_in_tear_clean_up(self):
|
||||||
events = []
|
|
||||||
|
|
||||||
class Test(unittest.IsolatedAsyncioTestCase):
|
class Test(unittest.IsolatedAsyncioTestCase):
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
events.append('asyncSetUp')
|
events.append('asyncSetUp')
|
||||||
|
|
||||||
async def test_func(self):
|
async def test_func(self):
|
||||||
events.append('test')
|
events.append('test')
|
||||||
self.addAsyncCleanup(self.on_cleanup)
|
self.addAsyncCleanup(self.on_cleanup1)
|
||||||
|
self.addAsyncCleanup(self.on_cleanup2)
|
||||||
|
|
||||||
async def asyncTearDown(self):
|
async def asyncTearDown(self):
|
||||||
events.append('asyncTearDown')
|
events.append('asyncTearDown')
|
||||||
|
|
||||||
async def on_cleanup(self):
|
async def on_cleanup1(self):
|
||||||
events.append('cleanup')
|
events.append('cleanup1')
|
||||||
raise Exception()
|
raise MyException('some error')
|
||||||
|
|
||||||
|
async def on_cleanup2(self):
|
||||||
|
events.append('cleanup2')
|
||||||
|
raise MyException('other error')
|
||||||
|
|
||||||
|
events = []
|
||||||
test = Test("test_func")
|
test = Test("test_func")
|
||||||
test.run()
|
result = test.run()
|
||||||
self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup'])
|
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):
|
def test_deprecation_of_return_val_from_test(self):
|
||||||
# Issue 41322 - deprecate return of value!=None from a test
|
# Issue 41322 - deprecate return of value!=None from a test
|
||||||
|
@ -255,7 +317,49 @@ class TestAsyncCase(unittest.TestCase):
|
||||||
output = test.run()
|
output = test.run()
|
||||||
self.assertTrue(cancelled)
|
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__":
|
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