Coverage for /opt/homebrew/lib/python3.11/site-packages/anyio/pytest_plugin.py: 38%

96 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-05-04 13:14 +0700

1from contextlib import contextmanager 

2from inspect import isasyncgenfunction, iscoroutinefunction 

3from typing import TYPE_CHECKING, Any, Dict, Generator, Optional, Tuple, cast 

4 

5import pytest 

6import sniffio 

7from _pytest.fixtures import FixtureRequest 

8 

9from ._core._eventloop import get_all_backends, get_asynclib 

10from .abc import TestRunner 

11 

12if TYPE_CHECKING: 

13 from _pytest.config import Config 

14 

15_current_runner: Optional[TestRunner] = None 

16 

17 

18def extract_backend_and_options(backend: object) -> Tuple[str, Dict[str, Any]]: 

19 if isinstance(backend, str): 

20 return backend, {} 

21 elif isinstance(backend, tuple) and len(backend) == 2: 

22 if isinstance(backend[0], str) and isinstance(backend[1], dict): 

23 return cast(Tuple[str, Dict[str, Any]], backend) 

24 

25 raise TypeError("anyio_backend must be either a string or tuple of (string, dict)") 

26 

27 

28@contextmanager 

29def get_runner( 

30 backend_name: str, backend_options: Dict[str, Any] 

31) -> Generator[TestRunner, object, None]: 

32 global _current_runner 

33 if _current_runner: 

34 yield _current_runner 

35 return 

36 

37 asynclib = get_asynclib(backend_name) 

38 token = None 

39 if sniffio.current_async_library_cvar.get(None) is None: 

40 # Since we're in control of the event loop, we can cache the name of the async library 

41 token = sniffio.current_async_library_cvar.set(backend_name) 

42 

43 try: 

44 backend_options = backend_options or {} 

45 with asynclib.TestRunner(**backend_options) as runner: 

46 _current_runner = runner 

47 yield runner 

48 finally: 

49 _current_runner = None 

50 if token: 

51 sniffio.current_async_library_cvar.reset(token) 

52 

53 

54def pytest_configure(config: "Config") -> None: 

55 config.addinivalue_line( 

56 "markers", 

57 "anyio: mark the (coroutine function) test to be run " 

58 "asynchronously via anyio.", 

59 ) 

60 

61 

62def pytest_fixture_setup(fixturedef: Any, request: FixtureRequest) -> None: 

63 def wrapper(*args, anyio_backend, **kwargs): # type: ignore[no-untyped-def] 

64 backend_name, backend_options = extract_backend_and_options(anyio_backend) 

65 if has_backend_arg: 

66 kwargs["anyio_backend"] = anyio_backend 

67 

68 with get_runner(backend_name, backend_options) as runner: 

69 if isasyncgenfunction(func): 

70 yield from runner.run_asyncgen_fixture(func, kwargs) 

71 else: 

72 yield runner.run_fixture(func, kwargs) 

73 

74 # Only apply this to coroutine functions and async generator functions in requests that involve 

75 # the anyio_backend fixture 

76 func = fixturedef.func 

77 if isasyncgenfunction(func) or iscoroutinefunction(func): 

78 if "anyio_backend" in request.fixturenames: 

79 has_backend_arg = "anyio_backend" in fixturedef.argnames 

80 fixturedef.func = wrapper 

81 if not has_backend_arg: 

82 fixturedef.argnames += ("anyio_backend",) 

83 

84 

85@pytest.hookimpl(tryfirst=True) 

86def pytest_pycollect_makeitem(collector: Any, name: Any, obj: Any) -> None: 

87 if collector.istestfunction(obj, name): 

88 inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj 

89 if iscoroutinefunction(inner_func): 

90 marker = collector.get_closest_marker("anyio") 

91 own_markers = getattr(obj, "pytestmark", ()) 

92 if marker or any(marker.name == "anyio" for marker in own_markers): 

93 pytest.mark.usefixtures("anyio_backend")(obj) 

94 

95 

96@pytest.hookimpl(tryfirst=True) 

97def pytest_pyfunc_call(pyfuncitem: Any) -> Optional[bool]: 

98 def run_with_hypothesis(**kwargs: Any) -> None: 

99 with get_runner(backend_name, backend_options) as runner: 

100 runner.run_test(original_func, kwargs) 

101 

102 backend = pyfuncitem.funcargs.get("anyio_backend") 

103 if backend: 

104 backend_name, backend_options = extract_backend_and_options(backend) 

105 

106 if hasattr(pyfuncitem.obj, "hypothesis"): 

107 # Wrap the inner test function unless it's already wrapped 

108 original_func = pyfuncitem.obj.hypothesis.inner_test 

109 if original_func.__qualname__ != run_with_hypothesis.__qualname__: 

110 if iscoroutinefunction(original_func): 

111 pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis 

112 

113 return None 

114 

115 if iscoroutinefunction(pyfuncitem.obj): 

116 funcargs = pyfuncitem.funcargs 

117 testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} 

118 with get_runner(backend_name, backend_options) as runner: 

119 runner.run_test(pyfuncitem.obj, testargs) 

120 

121 return True 

122 

123 return None 

124 

125 

126@pytest.fixture(params=get_all_backends()) 

127def anyio_backend(request: Any) -> Any: 

128 return request.param 

129 

130 

131@pytest.fixture 

132def anyio_backend_name(anyio_backend: Any) -> str: 

133 if isinstance(anyio_backend, str): 

134 return anyio_backend 

135 else: 

136 return anyio_backend[0] 

137 

138 

139@pytest.fixture 

140def anyio_backend_options(anyio_backend: Any) -> Dict[str, Any]: 

141 if isinstance(anyio_backend, str): 

142 return {} 

143 else: 

144 return anyio_backend[1]