pytest-trio icon indicating copy to clipboard operation
pytest-trio copied to clipboard

Async fixtures don't support `pytest.mark.usefixtures()`

Open mikenerone opened this issue 4 years ago • 2 comments

Async fixtures can't be used in @pytest.mark.usefixtures(...) decorators. I'm sure this is a side-effect of the trio-fixture workaround approach explained in the docs, but it should be possible to examine the test function's marks and honor them.

mikenerone avatar Sep 07 '21 19:09 mikenerone

I guess this has the same cause as #123 -- we're not properly querying pytest to figure out the list of fixtures. I would guess fixing one would also fix the other.

njsmith avatar Sep 08 '21 00:09 njsmith

I just got bit by this too. Min viable reproduction, in case it's useful as a testcase somewhere (though probably not because of the added trio_asyncio dep):

import asyncio

import pytest
import trio
import trio_asyncio


@pytest.fixture
async def trio_asyncio_loop():
    # When a ^C happens, trio send a Cancelled exception to each running
    # coroutine. We must protect this one to avoid deadlock if it is cancelled
    # before another coroutine that uses trio-asyncio.
    with trio.CancelScope(shield=True):
        async with trio_asyncio.open_loop() as loop:
            yield loop


@pytest.mark.usefixtures('trio_asyncio_loop')
class TestMetatest:

    async def test_the_thing(self):
        await trio_asyncio.aio_as_trio(asyncio.sleep)(0)

For posterity / any wandering ~~googlers~~ travelers:

_____________________________________________________________________________________________________________________________ TestMetatest.test_the_thing ______________________________________________________________________________________________________________________________
self = <tests.unittests.datastores.test_postgres.TestMetatest object at 0x7fbd4be01af0>

    async def test_the_thing(self):
        from trio_asyncio import aio_as_trio
>       await aio_as_trio(asyncio.sleep)(0)

tests/unittests/datastores/test_postgres.py:21:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <trio_asyncio._adapter.Asyncio_Trio_Wrapper object at 0x7fbd4be319d0>, args = (0,), kwargs = {}, f = <coroutine object _call_defer at 0x7fbd4bd56c00>

    async def __call__(self, *args, **kwargs):
        if self.args:
            raise RuntimeError("Call 'aio_as_trio(proc)(*args)', not 'aio_as_trio(proc, *args)'")

        # We route this through _calL_defer because some wrappers require
        # running in asyncio context
        f = _call_defer(self.proc, *args, **kwargs)
>       return await self.loop.run_aio_coroutine(f)
E       AttributeError: 'NoneType' object has no attribute 'run_aio_coroutine'

The workaround is simple, though not DRY:

class TestMetatest:

    # Explicitly call for the fixture on every test case instead of using mark.usefixtures
    async def test_the_thing(self, trio_asyncio_loop):
        await trio_asyncio.aio_as_trio(asyncio.sleep)(0)

Badg avatar Feb 02 '24 14:02 Badg