Skip to content

Commit df4faa5

Browse files
committed
refactor: Move decicion for which event loop a coroutine is run in from synchronizer to test item.
This allows getting rid of _get_event_loop_no_warn.
1 parent 8933c90 commit df4faa5

1 file changed

Lines changed: 30 additions & 17 deletions

File tree

pytest_asyncio/plugin.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,14 @@ def _can_substitute(item: Function) -> bool:
447447
return inspect.iscoroutinefunction(func)
448448

449449
def runtest(self) -> None:
450-
synchronized_obj = wrap_in_sync(self.obj)
450+
marker = self.get_closest_marker("asyncio")
451+
assert marker is not None
452+
default_loop_scope = _get_default_test_loop_scope(self.config)
453+
loop_scope = _get_marked_loop_scope(marker, default_loop_scope)
454+
runner_fixture_id = f"_{loop_scope}_scoped_runner"
455+
runner = self._request.getfixturevalue(runner_fixture_id)
456+
context = contextvars.copy_context()
457+
synchronized_obj = wrap_in_sync(self.obj, runner, context)
451458
with MonkeyPatch.context() as c:
452459
c.setattr(self, "obj", synchronized_obj)
453460
super().runtest()
@@ -489,7 +496,14 @@ def _can_substitute(item: Function) -> bool:
489496
)
490497

491498
def runtest(self) -> None:
492-
synchronized_obj = wrap_in_sync(self.obj)
499+
marker = self.get_closest_marker("asyncio")
500+
assert marker is not None
501+
default_loop_scope = _get_default_test_loop_scope(self.config)
502+
loop_scope = _get_marked_loop_scope(marker, default_loop_scope)
503+
runner_fixture_id = f"_{loop_scope}_scoped_runner"
504+
runner = self._request.getfixturevalue(runner_fixture_id)
505+
context = contextvars.copy_context()
506+
synchronized_obj = wrap_in_sync(self.obj, runner, context=context)
493507
with MonkeyPatch.context() as c:
494508
c.setattr(self, "obj", synchronized_obj)
495509
super().runtest()
@@ -511,7 +525,14 @@ def _can_substitute(item: Function) -> bool:
511525
)
512526

513527
def runtest(self) -> None:
514-
synchronized_obj = wrap_in_sync(self.obj.hypothesis.inner_test)
528+
marker = self.get_closest_marker("asyncio")
529+
assert marker is not None
530+
default_loop_scope = _get_default_test_loop_scope(self.config)
531+
loop_scope = _get_marked_loop_scope(marker, default_loop_scope)
532+
runner_fixture_id = f"_{loop_scope}_scoped_runner"
533+
runner = self._request.getfixturevalue(runner_fixture_id)
534+
context = contextvars.copy_context()
535+
synchronized_obj = wrap_in_sync(self.obj.hypothesis.inner_test, runner, context)
515536
with MonkeyPatch.context() as c:
516537
c.setattr(self.obj.hypothesis, "inner_test", synchronized_obj)
517538
super().runtest()
@@ -653,27 +674,19 @@ def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
653674

654675

655676
def wrap_in_sync(
656-
func: Callable[..., Awaitable[Any]],
677+
func: Callable[..., CoroutineType],
678+
runner: asyncio.Runner,
679+
context: contextvars.Context,
657680
):
658681
"""
659-
Return a sync wrapper around an async function executing it in the
660-
current event loop.
682+
Return a sync wrapper around a coroutine executing it in the
683+
specified runner and context.
661684
"""
662685

663686
@functools.wraps(func)
664687
def inner(*args, **kwargs):
665688
coro = func(*args, **kwargs)
666-
_loop = _get_event_loop_no_warn()
667-
task = asyncio.ensure_future(coro, loop=_loop)
668-
try:
669-
_loop.run_until_complete(task)
670-
except BaseException:
671-
# run_until_complete doesn't get the result from exceptions
672-
# that are not subclasses of `Exception`. Consume all
673-
# exceptions to prevent asyncio's warning from logging.
674-
if task.done() and not task.cancelled():
675-
task.exception()
676-
raise
689+
runner.run(coro, context=context)
677690

678691
return inner
679692

0 commit comments

Comments
 (0)