You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
self.doCleanups() cannot be called from a test method of a unittest.IsolatedAsyncioTestCase subclass #101018
The above passes in 3.9 and 3.10, but fails in 3.11 with
E
======================================================================
ERROR: test (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/.../cpython/Lib/unittest/async_case.py", line 75, in _callCleanup
self._callMaybeAsync(function, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../cpython/Lib/unittest/async_case.py", line 95, in _callMaybeAsync
return self._asyncioTestContext.run(func, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: cannot enter context: <_contextvars.Context object at 0x7fb2f93a9900> is already entered
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (errors=1)
Traceback (most recent call last):
File "<stdin>", line 16, in <module>
AssertionError: ['started', 'end']
I don't understand that test class very well. Do you have an analysis of the root cause (beyond pointing to the PR that introduced the regression)? Or a proposed fix?
changed the title [-]`self.doCleanups()` cannot be called from a synchronous test method of a `unittest.IsolatedAsyncioTestCase` subclass[/-][+]`self.doCleanups()` cannot be called from a test method of a `unittest.IsolatedAsyncioTestCase` subclass[/+]on Jan 17, 2023
The root cause is that self._callMaybeAsync is called from within a self._callMaybeAsync call, which tries to run self._asyncioTestContext.run recursively. At a deeper level, though, the problem is that we're trying to switch back and forth between sync and async code without knowing which direction we might actually be going, which I think means that doCleanups is just broken in IsolatedAsyncioTestCase. Some testing1 shows that an async cleanup can't be called via doCleanups in an async test case even in previous versions. Changing the title accordingly.
I don't have a great suggestion for a fix. I'm not sure if we can actually support calling doCleanups both during and after the test method. I think we could support calling it after and awaiting it during, but that's going to be ugly and doesn't sound easy to explain, either.
Footnotes
Using the same script as above, but with async def test and events.append wrapped in an async def, we get a RuntimeError: This event loop is already running in 3.9 and RuntimeError: Runner.run() cannot be called from a running event loop in 3.11 ↩
I don't think that doCleanups should be callable from a test method, if this is a supported use-case then our test suite would have caught this regression. From asyncio side this will be hard to support, someone would have to do more research.
The documentation for doCleanups says "If you need cleanup functions to be called prior to tearDown() then you can call doCleanups() yourself. doCleanups() pops methods off the stack of cleanup functions one at a time, so it can be called at any time."
Our test suite does not have complete coverage, but doCleanups is used in a few of our tests, including in tearDown, which is also broken for IsolatedAsyncioTestCase.
I concur with @kumaraditya303 that doCleanups was not purposed to be called from a test method. Calling it from tearDown may have sense, and we should investigate whether it is feasible.
I worked on a patch which makes calling tearDown a part of doCleanups. It is definitely a breaking change, although it does not break Python tests (except tests for low level details of unittest) and makes things more consistent (tearDown and callbacks registered with addCallback will be always called in LIFO order). I am wondering whether this will break your tests even more or make them simpler.
This has the same underlying issue as #101486, that issue is closed and there haven't been further complaints in last two years, @zware should this be closed as well?
If we're going to just say that the documentation is wrong, then we need to fix the documentation. The best "fix" is probably to just un-document doCleanups; after all, if you can't call it, what's the point of it existing?
Activity
gvanrossum commentedon Jan 13, 2023
I don't understand that test class very well. Do you have an analysis of the root cause (beyond pointing to the PR that introduced the regression)? Or a proposed fix?
[-]`self.doCleanups()` cannot be called from a synchronous test method of a `unittest.IsolatedAsyncioTestCase` subclass[/-][+]`self.doCleanups()` cannot be called from a test method of a `unittest.IsolatedAsyncioTestCase` subclass[/+]zware commentedon Jan 17, 2023
The root cause is that
self._callMaybeAsyncis called from within aself._callMaybeAsynccall, which tries to runself._asyncioTestContext.runrecursively. At a deeper level, though, the problem is that we're trying to switch back and forth between sync and async code without knowing which direction we might actually be going, which I think means thatdoCleanupsis just broken inIsolatedAsyncioTestCase. Some testing1 shows that an async cleanup can't be called viadoCleanupsin an async test case even in previous versions. Changing the title accordingly.I don't have a great suggestion for a fix. I'm not sure if we can actually support calling
doCleanupsboth during and after the test method. I think we could support calling it after and awaiting it during, but that's going to be ugly and doesn't sound easy to explain, either.Footnotes
Using the same script as above, but with
async def testandevents.appendwrapped in anasync def, we get aRuntimeError: This event loop is already runningin 3.9 andRuntimeError: Runner.run() cannot be called from a running event loopin 3.11 ↩kumaraditya303 commentedon Jan 17, 2023
I don't think that
doCleanupsshould be callable from a test method, if this is a supported use-case then our test suite would have caught this regression. Fromasyncioside this will be hard to support, someone would have to do more research.zware commentedon Jan 17, 2023
The documentation for
doCleanupssays "If you need cleanup functions to be called prior to tearDown() then you can call doCleanups() yourself. doCleanups() pops methods off the stack of cleanup functions one at a time, so it can be called at any time."Our test suite does not have complete coverage, but
doCleanupsis used in a few of our tests, including intearDown, which is also broken forIsolatedAsyncioTestCase.kumaraditya303 commentedon Jan 17, 2023
Okay thanks but I am not myself a user of unittest and the comment https://github.com/python/cpython/blame/c5660ae96f2ab5732c68c301ce9a63009f432d93/Lib/unittest/async_case.py#L14 is mostly out of date so I am not sure of the semantics here.
serhiy-storchaka commentedon Jan 25, 2023
I concur with @kumaraditya303 that
doCleanupswas not purposed to be called from a test method. Calling it fromtearDownmay have sense, and we should investigate whether it is feasible.I worked on a patch which makes calling
tearDowna part ofdoCleanups. It is definitely a breaking change, although it does not break Python tests (except tests for low level details ofunittest) and makes things more consistent (tearDownand callbacks registered withaddCallbackwill be always called in LIFO order). I am wondering whether this will break your tests even more or make them simpler.kumaraditya303 commentedon Jul 7, 2025
This has the same underlying issue as #101486, that issue is closed and there haven't been further complaints in last two years, @zware should this be closed as well?
zware commentedon Jul 7, 2025
If we're going to just say that the documentation is wrong, then we need to fix the documentation. The best "fix" is probably to just un-document
doCleanups; after all, if you can't call it, what's the point of it existing?