aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2021-09-22 18:43:23 +0300
committerGitHub <noreply@github.com>2021-09-22 17:43:23 +0200
commitecb6922ff2d56476a6cfb0941ae55aca5e7fae3d (patch)
treea17fe9b865ff2b1d33a44c4e9e3fa387b15a5d58
parentbpo-21302: time.sleep() uses waitable timer on Windows (GH-28483) (diff)
downloadcpython-ecb6922ff2d56476a6cfb0941ae55aca5e7fae3d.tar.gz
cpython-ecb6922ff2d56476a6cfb0941ae55aca5e7fae3d.tar.bz2
cpython-ecb6922ff2d56476a6cfb0941ae55aca5e7fae3d.zip
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.
-rw-r--r--Lib/unittest/async_case.py19
-rw-r--r--Lib/unittest/case.py10
-rw-r--r--Lib/unittest/test/test_async_case.py206
-rw-r--r--Misc/NEWS.d/next/Library/2021-09-18-16-56-33.bpo-45238.Hng_9V.rst2
4 files changed, 176 insertions, 61 deletions
diff --git a/Lib/unittest/async_case.py b/Lib/unittest/async_case.py
index bfc68a76e84..3e864d14d11 100644
--- a/Lib/unittest/async_case.py
+++ b/Lib/unittest/async_case.py
@@ -75,15 +75,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()
@@ -112,7 +112,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)
@@ -122,7 +122,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)
@@ -161,3 +161,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 908ae07bac7..f1067652cc8 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -655,12 +655,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 93ef1997e0c..3717486b265 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_deprecation_of_return_val_from_test(self):
# Issue 41322 - deprecate return of value!=None from a test
@@ -255,7 +317,49 @@ class TestAsyncCase(unittest.TestCase):
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__":
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 00000000000..857f315c520
--- /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.