bloomberg / pytest-memray

pytest plugin for easy integration of memray memory profiler
https://pytest-memray.readthedocs.io/en/latest/
Apache License 2.0
322 stars 23 forks source link

pytest-memray breaks anyio #119

Open godlygeek opened 3 weeks ago

godlygeek commented 3 weeks ago

Hola @pablogsal,

I am facing the same issue: the async tests are skipped if we pass --memray argument to pytest.

Steps to reproduce the issue:

Use the following test file: test_async.py

import pytest

@pytest.fixture
def anyio_backend():
    return 'asyncio'

@pytest.mark.anyio
async def test_async():
    assert True

Install required dependencies:

python -m pip install pytest anyio pytest-memray

The test runs as expected if --memray is not passed:

 python -m pytest -vv -x test_async.py

Output:

plugins: memray-1.6.0, anyio-4.0.0
collected 1 item                                                                                                                                                                                          

test_async.py::test_async PASSED

However, the test is skipped if we pass --memray:

 python -m pytest --memray -vv -x test_async.py

Output:

plugins: memray-1.6.0, anyio-4.0.0
collected 1 item                                                                                                                                                                                          

test_async.py::test_async SKIPPED (async def function and no async plugin installed (see warnings))                                                                                                 [100%]

============================================================================================ warnings summary =============================================================================================
test_async.py::test_async
  <MY_PROJECT_PATH>/.venv/lib/python3.9/site-packages/_pytest/python.py:151: PytestUnhandledCoroutineWarning: async def functions are not natively supported and have been skipped.
  You need to install a suitable plugin for your async framework, for example:
    - anyio
    - pytest-asyncio
    - pytest-tornasync
    - pytest-trio
    - pytest-twisted
    warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

Originally posted by @albertvillanova in https://github.com/bloomberg/pytest-memray/discussions/101#discussioncomment-9738673

godlygeek commented 3 weeks ago

It looks anyio stores some state on the test function, and pytest-memray replaces the test function with a wrapper: https://github.com/bloomberg/pytest-memray/blob/0654e6b8c57cd98314c27484c280cb54c1b3bb94/src/pytest_memray/plugin.py#L213 Those two things don't play nicely with each other.

godlygeek commented 3 weeks ago

Actually, looks like pytest is storing that state on the test function. anyio is just retrieving it.

We probably need to copy the test function's __dict__ over onto our wrapper.

albertvillanova commented 3 weeks ago

@godlygeek thanks for the investigation.

godlygeek commented 3 weeks ago

My original analysis didn't spot the problem, actually. It's not that anyio isn't finding the state it expect on the wrapper, it's that anyio is completely ignoring the wrapper, because it only wants to process coroutine functions and ignore other stuff, and our wrapper isn't a coroutine function (even though calling it does return a coroutine, anyio is specifically looking only for async def functions).

We might be able to get around that by defining an async def wrapper if the function we're wrapping is an async def coroutine...