Skip to content

Commit bb8628a

Browse files
committed
Reinstate event loop if unset
1 parent 2c75c45 commit bb8628a

File tree

4 files changed

+98
-1
lines changed

4 files changed

+98
-1
lines changed

changelog.d/1172.added.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Pyright type checking support in tox configuration to improve type safety and compatibility.

changelog.d/1177.fixed.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``RuntimeError: There is no current event loop in thread 'MainThread'`` when using shared event loops after any test unsets the event loop (such as when using ``asyncio.run`` and ``asyncio.Runner``).

pytest_asyncio/plugin.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import inspect
1111
import socket
1212
import sys
13+
import threading
1314
import traceback
1415
import warnings
1516
from asyncio import AbstractEventLoop, AbstractEventLoopPolicy
@@ -602,6 +603,12 @@ def _set_event_loop(loop: AbstractEventLoop | None) -> None:
602603
asyncio.set_event_loop(loop)
603604

604605

606+
def _reinstate_event_loop_on_main_thread() -> None:
607+
if threading.current_thread() is threading.main_thread():
608+
policy = _get_event_loop_policy()
609+
policy.set_event_loop(policy.new_event_loop())
610+
611+
605612
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
606613
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
607614
"""
@@ -663,7 +670,12 @@ def wrap_in_sync(
663670
@functools.wraps(func)
664671
def inner(*args, **kwargs):
665672
coro = func(*args, **kwargs)
666-
_loop = _get_event_loop_no_warn()
673+
try:
674+
_loop = _get_event_loop_no_warn()
675+
except RuntimeError:
676+
# Handle situation where asyncio.set_event_loop(None) removes shared loops.
677+
_reinstate_event_loop_on_main_thread()
678+
_loop = _get_event_loop_no_warn()
667679
task = asyncio.ensure_future(coro, loop=_loop)
668680
try:
669681
_loop.run_until_complete(task)

tests/test_set_event_loop.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from textwrap import dedent
4+
5+
import pytest
6+
from pytest import Pytester
7+
8+
9+
@pytest.mark.parametrize("test_loop_scope", ("function", "module", "session"))
10+
def test_set_event_loop_none(pytester: Pytester, test_loop_scope: str):
11+
pytester.makeini(
12+
dedent(
13+
f"""\
14+
[pytest]
15+
asyncio_default_test_loop_scope = {test_loop_scope}
16+
asyncio_default_fixture_loop_scope = function
17+
"""
18+
)
19+
)
20+
pytester.makepyfile(
21+
dedent(
22+
"""\
23+
import asyncio
24+
import pytest
25+
26+
pytest_plugins = "pytest_asyncio"
27+
28+
@pytest.mark.asyncio
29+
async def test_before():
30+
pass
31+
32+
33+
def test_set_event_loop_none():
34+
asyncio.set_event_loop(None)
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_after():
39+
pass
40+
"""
41+
)
42+
)
43+
result = pytester.runpytest_subprocess()
44+
result.assert_outcomes(passed=3)
45+
46+
47+
def test_set_event_loop_none_class(pytester: Pytester):
48+
pytester.makeini(
49+
dedent(
50+
"""\
51+
[pytest]
52+
asyncio_default_test_loop_scope = class
53+
asyncio_default_fixture_loop_scope = function
54+
"""
55+
)
56+
)
57+
pytester.makepyfile(
58+
dedent(
59+
"""\
60+
import asyncio
61+
import pytest
62+
63+
pytest_plugins = "pytest_asyncio"
64+
65+
66+
class TestClass:
67+
@pytest.mark.asyncio
68+
async def test_before(self):
69+
pass
70+
71+
72+
def test_set_event_loop_none(self):
73+
asyncio.set_event_loop(None)
74+
75+
76+
@pytest.mark.asyncio
77+
async def test_after(self):
78+
pass
79+
"""
80+
)
81+
)
82+
result = pytester.runpytest_subprocess()
83+
result.assert_outcomes(passed=3)

0 commit comments

Comments
 (0)