pytest-dev / pytest-asyncio

Asyncio support for pytest
https://pytest-asyncio.readthedocs.io
Apache License 2.0
1.43k stars 152 forks source link

"OSError: could not get source code" Came Up When "pytest_fixture_setup()" Executed #830

Closed muazhari closed 5 months ago

muazhari commented 6 months ago

Every initial test case will fail when pytest_fixture_setup is executed. I have compared pytest==8.1.1 and pytest==8.2.0. The 8.1.1 has no error, and the 8.2.0 has an error.

My conftest.py:

import asyncio
from asyncio import AbstractEventLoop

import pytest
import pytest_asyncio

from tests.containers.test_container import TestContainer

@pytest_asyncio.fixture(scope="session")
def event_loop():
    loop: AbstractEventLoop = asyncio.get_event_loop()
    yield loop
    loop.close()

@pytest_asyncio.fixture(scope="function")
async def main_context(request: pytest.FixtureRequest):
    test_container: TestContainer = TestContainer()
    main_context = test_container.main_context()
    await main_context.all_seeder.up()
    yield main_context
    await main_context.all_seeder.down()

Comparison:

fixturedef =

@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(
    fixturedef: FixtureDef,
) -> Generator[None, Any, None]:
    """Adjust the event loop policy when an event loop is produced."""
    if fixturedef.argname == "event_loop":
        # The use of a fixture finalizer is preferred over the
        # pytest_fixture_post_finalizer hook. The fixture finalizer is invoked once
        # for each fixture, whereas the hook may be invoked multiple times for
        # any specific fixture.
        # see https://github.com/pytest-dev/pytest/issues/5848
        _add_finalizers(
            fixturedef,
            _close_event_loop,
            _restore_event_loop_policy(asyncio.get_event_loop_policy()),
            _provide_clean_event_loop,
        )
        outcome = yield
        loop: asyncio.AbstractEventLoop = outcome.get_result()
        # Weird behavior was observed when checking for an attribute of FixtureDef.func
        # Instead, we now check for a special attribute of the returned event loop
        fixture_filename = inspect.getsourcefile(fixturedef.func)
        if not getattr(loop, "__original_fixture_loop", False):
          _, fixture_line_number = inspect.getsourcelines(fixturedef.func)

/usr/local/lib/python3.10/dist-packages/pytest_asyncio/plugin.py:758:


/usr/lib/python3.10/inspect.py:1121: in getsourcelines lines, lnum = findsource(object)


object = <function event_loop at 0x7f7f854153f0>

def findsource(object):
    """Return the entire source file and starting line number for an object.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a list of all the lines
    in the file and the line number indexes a line in that list.  An OSError
    is raised if the source code cannot be retrieved."""

    file = getsourcefile(object)
    if file:
        # Invalidate cache if needed.
        linecache.checkcache(file)
    else:
        file = getfile(object)
        # Allow filenames in form of "<something>" to pass through.
        # `doctest` monkeypatches `linecache` module to enable
        # inspection, so let `linecache.getlines` to be called.
        if not (file.startswith('<') and file.endswith('>')):
            raise OSError('source code not available')

    module = getmodule(object, file)
    if module:
        lines = linecache.getlines(file, module.__dict__)
    else:
        lines = linecache.getlines(file)
    if not lines:
      raise OSError('could not get source code')

E OSError: could not get source code

/usr/lib/python3.10/inspect.py:958: OSError

Klavionik commented 6 months ago

The same error here, pytest 7.4.4 and pytest-asyncio 0.23.4. It was harassing me for hours yesterday and then suddenly gone (just as suddenly, as it started).

halvomez commented 6 months ago

pytest-asyncio 0.23.6 and pytest 8.2.0 same

gone with pytest==8.1.1

seifertm commented 6 months ago

Thanks for reporting this. The pytest-asyncio tests don't seem to be affected, so it's hard to find the cause of the error.

@muazhari @Klavionik @halvomez: Can any one provide a minimal code example that reproduces the issue?

muazhari commented 6 months ago

Fixed in pytest==8.2.1. However, I can't reproduce the error with minimal codes with pytest==8.2.0 and the same environment. I don't know why.

============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-8.2.1, pluggy-1.5.0
rootdir: /app
plugins: cov-5.0.0, asyncio-0.23.6, xdist-3.6.1, anyio-4.3.0
asyncio: mode=strict
1 worker [6 items] 
......       
seifertm commented 5 months ago

Since this issue seems to be fixed with more recent versions of pytest and I cannot reproduce it, I'll close the ticket until further evidence arrives.

mike-oakley commented 3 months ago

Also encountered this on the latest pytest (8.3.2). Causes the first test in the session to fail, but then all the subsequent ones succeed. Reverting to 0.21.2 resolves this 🤔

platform linux -- Python 3.9.6, pytest-8.3.2, pluggy-1.5.0

plugins: asyncio-0.23.8

asyncio: mode=auto
seifertm commented 3 months ago

@mike-oakley Any chance you can cook up a reproducer so we can get to the bottom of this?

c137santos commented 3 months ago

The same problem:

`$ poetry show pytest
 name         : pytest                                      
 version      : 8.3.2                                       
 description  : pytest: simple powerful testing with Python `

and

`$ poetry show pytest-asyncio
 name         : pytest-asyncio             
 version      : 0.23.8                     
 description  : Pytest support for asyncio `
seifertm commented 3 months ago

There's really not much I can do to help, unless someone can provide a minimal reproducer.

devTarik commented 3 months ago

There's really not much I can do to help, unless someone can provide a minimal reproducer.

Here is example fixture "event_loop" which we have a problem with

@pytest.fixture(scope='session')
def event_loop():
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    yield loop
    loop.close()

Try to run any tests, you might get error always in first test, because problem in moment initialization.

pytest==8.1.1 work good, 8.2.1 sometimes work, 8.2.2 doesn't work

You can try this and reproduce: conftest.py

import asyncio
from httpx import AsyncClient

@pytest.fixture(scope='session')
def event_loop():
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    yield loop
    loop.close()

@pytest.fixture(scope='session')
async def http_client():
    async with AsyncClient(app=app, base_url='https://test.io/') as client:
        yield client

test_ping.py

from httpx import AsyncClient

async def test__ping(http_client: AsyncClient):
    response = await http_client.get('/ping')
    assert response.status_code == 200
    assert response.json() == {'msg': 'pong'}

Even this tiny test return this error

seifertm commented 2 months ago

@devTarik Thanks for backing this issue up with some more code. However, when I try to run your example, I get the following error:

    @pytest.fixture(scope='session')
    async def http_client():
>       async with AsyncClient(app=app, base_url='https://test.io/') as client:
E       NameError: name 'app' is not defined

conftest.py:18: NameError

Is there a minimal implementation of app you can share?