python-trio / pytest-trio

Pytest plugin for trio
Other
54 stars 25 forks source link

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

Open mikenerone opened 3 years ago

mikenerone commented 3 years ago

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.

njsmith commented 3 years ago

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.

Badg commented 8 months ago

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)