forked from pytest-dev/pytest-asyncio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path_runner.py
89 lines (74 loc) · 2.99 KB
/
_runner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import asyncio
from typing import Awaitable, TypeVar, Union
import pytest
_R = TypeVar("_R")
class Runner:
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
self._loop = loop
self._task = None
self._timeout_hande = None
self._timeout_reached = False
def run(self, coro: Awaitable[_R]) -> _R:
return self._loop.run_until_complete(self._async_wrapper(coro))
def run_test(self, coro: Awaitable[None]) -> None:
task = asyncio.ensure_future(coro, loop=self._loop)
try:
self.run(task)
except BaseException:
# run_until_complete doesn't get the result from exceptions
# that are not subclasses of `Exception`. Consume all
# exceptions to prevent asyncio's warning from logging.
if task.done() and not task.cancelled():
task.exception()
raise
def set_timer(self, timeout: Union[int, float]) -> None:
if self._timeout_hande is not None:
self._timeout_hande.cancel()
self._timeout_reached = False
self._timeout_hande = self._loop.call_later(timeout, self._on_timeout)
def cancel_timer(self) -> None:
if self._timeout_hande is not None:
self._timeout_hande.cancel()
self._timeout_reached = False
self._timeout_hande = None
async def _async_wrapper(self, coro: Awaitable[_R]) -> _R:
if self._timeout_reached:
# timeout can happen in a gap between tasks execution,
# it should be handled anyway
raise asyncio.TimeoutError()
task = asyncio.current_task()
assert self._task is None
self._task = task
try:
return await coro
except asyncio.CancelledError:
if self._timeout_reached:
raise asyncio.TimeoutError()
finally:
self._task = None
def _on_timeout(self) -> None:
# the plugin is optional,
# pytest-asyncio should work fine without pytest-timeout
# That's why the lazy import is required here
import pytest_timeout
if pytest_timeout.is_debugging():
return
self._timeout_reached = True
if self._task is not None:
self._task.cancel()
def _install_runner(item: pytest.Item, loop: asyncio.AbstractEventLoop) -> None:
item._pytest_asyncio_runner = Runner(loop)
def _get_runner(item: pytest.Item) -> Runner:
runner = getattr(item, "_pytest_asyncio_runner", None)
if runner is not None:
return runner
else:
parent = item.parent
if parent is not None:
parent_runner = _get_runner(parent)
runner = item._pytest_asyncio_runner = Runner(parent_runner._loop)
return runner
else: # pragma: no cover
# can happen only if the plugin is broken and no event_loop fixture
# dependency was installed.
raise RuntimeError(f"There is no event_loop associated with {item}")