Description
A pattern I've been using is handing a class instance a nursery when it is constructed, so that it can then run background tasks. Things that are working well:
- This pattern.
- In one case a background task performing cleanup by handling
trio.Cancelled
. - I have a fixture that constructs a specialised instance of such a class for test purposes, and am testing a bunch of class functionality effectively.
However, I'm struggling to write a test that uses the fixture, cancels the nursery given to the class instances, and asserts that the cleanup was performed correctly. The docs say:
So what happens if the yield gets cancelled?
First, pytest-trio assumes that something has gone wrong and there’s no point in continuing the test. If the top-level test function is running, then it cancels it.
...and this is what is biting me here. Here's a reduced example:
import pytest
import pytest_trio
import trio
class Foo:
def __init__(self, nursery):
self.nursery = nursery
self.handled_cancellation = False
nursery.start_soon(self.foo)
async def foo(self):
try:
await trio.sleep_forever()
except trio.Cancelled:
self.handled_cancellation = True
@pytest_trio.trio_fixture
async def foo_fixture():
async with trio.open_nursery() as nursery:
yield Foo(nursery)
@pytest.mark.trio
async def test_foo(foo_fixture):
foo_fixture.nursery.cancel_scope.cancel()
# This line seems to cause the test to terminate
await trio.testing.wait_all_tasks_blocked()
assert foo_fixture.handled_cancellation # this assert never runs
assert False # if the test passes then this line definitely never ran
I notice that in newer releases, the test fails with RuntimeError: <fixture 'foo_fixture'> cancelled the test but didn't raise an error
which is nice (in older releases the test silently passes).
The behaviour is therefore as documented, but that leads me to the problem: how do I test cancellation behaviour in an object created by a fixture?
Workaround: I can copy the fixture code into a test. But this seems ugly as it requires duplication, precluding code reuse.