pytest-dev / pytest-dev/pytest-asyncio

RuntimeError: Cannot run the event loop while another loop is running using pytest.main

Open
#374 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature
Dominant language
Python
Stars
1.7k
Forks
207
Avg merge
5h 35m
Merged PRs (30d)
9

Description

To give you a minimal proof of concept in python3.10:
runner.py

import pytest                                                                                                   
import asyncio                                                                                                  
                 
# using nest_asyncio mitigates the problem                                                                                               
#import nest_asyncio                                                                                            
#nest_asyncio.apply()                                                                                                      
                                                                                                                
async def runner():                                                                                             
    pytest.main(['-k', 'test_asdf', 'test_file.py'])                                                            
                                                                                                                
loop = asyncio.get_event_loop()                                                                                 
loop.run_until_complete(runner())                                                                               
           
# does not raise an exception                                                                                                                                                                                                                     
# pytest.main(['-k', 'test_asdf', 'test_file.py'])     

test_file.py

import pytest                                                                                                   
                                                                                                                
@pytest.mark.asyncio                                                                                            
async def test_asdf():                                                                                          
    assert True     
/home/peter/Documents/pytestbug/test_runner.py:14: DeprecationWarning: There is no current event loop
  loop = asyncio.get_event_loop()
============================================= test session starts ==============================================
platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /home/peter/Documents/pytestbug
plugins: asyncio-0.18.4.dev36+gc021932.d20220609
asyncio: mode=legacy
collected 1 item                                                                                               

test_file.py F                                                                                           [100%]

=================================================== FAILURES ===================================================
__________________________________________________ test_asdf ___________________________________________________

args = (), kwargs = {}, coro = <coroutine object test_asdf at 0x7f7c47cb3300>
old_loop = <_UnixSelectorEventLoop running=False closed=False debug=False>
task = <Task pending name='Task-2' coro=<test_asdf() running at /home/peter/Documents/pytestbug/test_file.py:5>>

    @functools.wraps(func)
    def inner(*args, **kwargs):
        coro = func(*args, **kwargs)
        if not inspect.isawaitable(coro):
            pyfuncitem.warn(
                pytest.PytestWarning(
                    f"The test {pyfuncitem} is marked with '@pytest.mark.asyncio' "
                    "but it is not an async function. "
                    "Please remove asyncio marker. "
                    "If the test is not marked explicitly, "
                    "check for global markers applied via 'pytestmark'."
                )
            )
            return
        nonlocal _loop
        _loop.stop()
        old_loop = _loop
        # old_loop
        _loop = asyncio.new_event_loop()
        asyncio.set_event_loop(_loop)
        task = asyncio.ensure_future(coro, loop=_loop)
        try:
>           _loop.run_until_complete(task)

../../.virtualenvs/pytestbug/lib/python3.10/site-packages/pytest_asyncio/plugin.py:460: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.10/asyncio/base_events.py:622: in run_until_complete
    self._check_running()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_UnixSelectorEventLoop running=False closed=False debug=False>

    def _check_running(self):
        if self.is_running():
            raise RuntimeError('This event loop is already running')
        if events._get_running_loop() is not None:
>           raise RuntimeError(
                'Cannot run the event loop while another loop is running')
E           RuntimeError: Cannot run the event loop while another loop is running

/usr/lib/python3.10/asyncio/base_events.py:584: RuntimeError
=============================================== warnings summary ===============================================
../../.virtualenvs/pytestbug/lib/python3.10/site-packages/pytest_asyncio/plugin.py:191
  /home/peter/.virtualenvs/pytestbug/lib/python3.10/site-packages/pytest_asyncio/plugin.py:191: DeprecationWarning: The 'asyncio_mode' default value will change to 'strict' in future, please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file.
    config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================================== short test summary info ============================================
FAILED test_file.py::test_asdf - RuntimeError: Cannot run the event loop while another loop is running
========================================= 1 failed, 1 warning in 0.10s =========================================
/usr/lib/python3.10/asyncio/base_events.py:685: ResourceWarning: unclosed event loop <_UnixSelectorEventLoop running=False closed=False debug=False>
sys:1: RuntimeWarning: coroutine 'test_asdf' was never awaited
Task was destroyed but it is pending!
task: <Task pending name='Task-2' coro=<test_asdf() running at /home/peter/Documents/pytestbug/test_file.py:5>>

The cause for this exception is that my setup already has an existing running EventLoop and in that loop I invoke pytest.main.
pytest.asyncio uses wrap_in_sync in the async fixtures and tests, which calls https://github.com/pytest-dev/pytest-asyncio/blob/860ff5113c3e73ade396c632c822a171a81e5b78/pytest_asyncio/plugin.py#L454
Therefore calling run_until_complete within a coroutine raises an exception.
Code executed inside an EventLoop can't stop the current EvenntLoop, create a new loop, run the code in that loop and restore the old loop. At least not without heavy modification. Python developers also stated that this behavior will not be changed.

This can be mitigated by using nest_asyncio, however there are different eventloop implementations that are not compatible with nest_asyncio. (My project used fastapi which uses a different loop implementation)

If there is an async entrypoiny like pytest.async_main into pytest that may be used as well. If we make all functions awaitable, we would not need to use run_until_complete, but could simply use await. However I don't think that this is feasible.

In the end I just ran pytest in a subprocess to call it outside of a coroutine.

As requested I further elaborated on my problem from https://github.com/pytest-dev/pytest-asyncio/issues/359#issuecomment-1151121091

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with runner.py invoking pytest.main and test_file.py's @pytest.mark.asyncio test, then inspect pytest_asyncio/plugin.py at wrap_in_sync around line 454. The issue has no settled implementation or acceptance criteria; done would require defining and validating an async pytest entry point or another compatible resolution.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
testing-qa
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.