10from collections.abc import AsyncGenerator, Awaitable, Callable, Sequence
23PING_REQUEST_TIMEOUT = 1.0
24PING_RESPONSE_CODES = (200,)
26HealthCheckType = Callable[..., Awaitable[bool]]
28ClientSessionFactory = Callable[..., aiohttp.ClientSession]
31@contextlib.asynccontextmanager
35 health_check: HealthCheckType,
36 session_factory: ClientSessionFactory = aiohttp.ClientSession,
37 env: dict[str, str] |
None =
None,
38 shutdown_signal: int = signal.SIGINT,
39 shutdown_timeout: float = 120,
40 poll_retries: int = POLL_RETRIES,
41 subprocess_options=
None,
43 subprocess_spawner=
None,
46) -> AsyncGenerator[subprocess.Popen |
None,
None]:
47 async with session_factory()
as session:
48 async with _service_daemon(
51 shutdown_signal=shutdown_signal,
52 shutdown_timeout=shutdown_timeout,
53 poll_retries=poll_retries,
54 subprocess_options=subprocess_options,
55 setup_service=setup_service,
56 subprocess_spawner=subprocess_spawner,
57 health_check=health_check,
59 stdout_handler=stdout_handler,
60 stderr_handler=stderr_handler,
65async def service_wait(
68 health_check: HealthCheckType,
69 session_factory: ClientSessionFactory = aiohttp.ClientSession,
73 flush_supported = hasattr(reporter,
'flush')
74 async with session_factory()
as session:
75 if not await _run_health_check(
80 command =
' '.join(args)
81 reporter.write_line(
'')
83 'Service is not running yet you may want to start it from '
84 'outside of testsuite, e.g. using gdb:',
87 reporter.write_line(
'')
88 reporter.write_line(f
'gdb --args {command}', green=
True)
89 reporter.write_line(
'')
90 reporter.write(
'Waiting for service to start...')
91 while not await _run_health_check(
100 reporter.write_line(
'')
103@contextlib.asynccontextmanager
104async def start_dummy_process():
108def make_health_check(
110 health_check: HealthCheckType |
None =
None,
111 ping_url: str |
None,
112 ping_request_timeout: float = PING_REQUEST_TIMEOUT,
113 ping_response_codes: tuple[int] = PING_RESPONSE_CODES,
116 return _make_ping_health_check(
118 ping_request_timeout=ping_request_timeout,
119 ping_response_codes=ping_response_codes,
124 raise RuntimeError(
'Either `ping_url` or `health_check` must be set')
127async def _run_health_check(
128 health_check: HealthCheckType,
130 session: aiohttp.ClientSession,
131 process: subprocess.Popen |
None,
134 if process
and process.poll()
is not None:
135 raise spawn.HealthCheckError(
'Process already finished')
137 begin = time.perf_counter()
138 if await health_check(session=session, process=process):
140 end = time.perf_counter()
141 to_sleep = begin + sleep - end
143 await asyncio.sleep(to_sleep)
147def _make_ping_health_check(
150 ping_request_timeout: float,
151 ping_response_codes: tuple[int],
153 async def ping_health_check(
154 session: aiohttp.ClientSession,
155 process: subprocess.Popen |
None,
158 response = await session.get(
160 timeout=ping_request_timeout,
162 if response.status
in ping_response_codes:
164 except asyncio.TimeoutError:
166 except aiohttp.ClientConnectorError:
170 return ping_health_check
173async def _service_wait(
174 process: subprocess.Popen |
None,
177 health_check: HealthCheckType,
178 session: aiohttp.ClientSession,
180 for _
in range(poll_retries):
181 if await _run_health_check(
187 raise spawn.HealthCheckError(
'service daemon is not ready')
190def _prepare_env(*envs: dict[str, str] |
None) -> dict[str, str]:
191 result = os.environ.copy()
195 asan_preload = os.getenv(
'ASAN_PRELOAD')
196 if asan_preload
is not None:
197 result[
'LD_PRELOAD'] = asan_preload
201@contextlib.asynccontextmanager
202async def _service_daemon(
205 env: dict[str, str] |
None,
206 shutdown_signal: int,
207 shutdown_timeout: float,
209 subprocess_options=
None,
211 subprocess_spawner=
None,
213 session: aiohttp.ClientSession,
216) -> AsyncGenerator[subprocess.Popen,
None]:
217 options = subprocess_options.copy()
if subprocess_options
else {}
218 options[
'env'] = _prepare_env(env, options.get(
'env'))
219 async with spawn.spawned(
221 shutdown_signal=shutdown_signal,
222 shutdown_timeout=shutdown_timeout,
223 subprocess_spawner=subprocess_spawner,
224 stdout_handler=stdout_handler,
225 stderr_handler=stderr_handler,
228 if setup_service
is not None:
229 setup_service(process)
232 poll_retries=poll_retries,
233 health_check=health_check,