Skip to content

[3.9] bpo-45238: Fix unittest.IsolatedAsyncioTestCase.debug() (GH-28449) #28522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions Lib/unittest/async_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,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()
Expand Down Expand Up @@ -109,7 +109,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)
Expand All @@ -119,7 +119,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)
Expand Down Expand Up @@ -158,3 +158,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()
10 changes: 5 additions & 5 deletions Lib/unittest/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,12 +660,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."""
Expand Down
165 changes: 113 additions & 52 deletions Lib/unittest/test/test_async_case.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import asyncio
import unittest
from test import support


class MyException(Exception):
pass


def tearDownModule():
asyncio.set_event_loop_policy(None)


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, [])
Expand All @@ -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',
Expand All @@ -38,34 +49,48 @@ def tearDown(self):
'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')
Expand All @@ -74,98 +99,135 @@ async def on_cleanup(self):
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')

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')

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 = []
Expand Down Expand Up @@ -217,6 +279,5 @@ async def test_cancel(self):
self.assertFalse(output.wasSuccessful())



if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :meth:`unittest.IsolatedAsyncioTestCase.debug`: it runs now asynchronous
methods and callbacks.