chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""Kernel-synchronized liveness probes for the real-subprocess stdio lifecycle suite.
|
||||
|
||||
A spawned (grand)child connects back to a test-owned TCP listener and sends
|
||||
`b'alive'`; the kernel then provides every signal a test needs, with no sleeps or
|
||||
polling. The kernel closes all of a process's file descriptors on exit, so EOF
|
||||
(clean close / FIN) or `BrokenResourceError` (abrupt close / RST, typical of
|
||||
SIGKILL and Windows job termination) proves death; only a running process can
|
||||
answer an echo, so a reply proves liveness without racing a kill.
|
||||
|
||||
Extracted from the real-process section of tests/client/test_stdio.py; the two
|
||||
copies on this branch are deliberate -- consolidating them is follow-up work.
|
||||
"""
|
||||
|
||||
import anyio
|
||||
import anyio.abc
|
||||
import pytest
|
||||
|
||||
|
||||
def connect_back_script(port: int, *, echo: bool = False) -> str:
|
||||
"""Return a `python -c` script body that connects to 127.0.0.1:`port` and sends `b'alive'`.
|
||||
|
||||
After the banner the script blocks forever -- or, with `echo=True`, echoes every
|
||||
received chunk back so `assert_peer_echoes` can prove the process still runs.
|
||||
"""
|
||||
# lax no cover: echo mode is used only by POSIX-gated tests; Windows runners enforce 100% per job.
|
||||
if echo: # pragma: lax no cover
|
||||
tail = "while True:\n data = s.recv(65536)\n if not data:\n break\n s.sendall(data)\n"
|
||||
else:
|
||||
tail = "time.sleep(3600)\n"
|
||||
return f"import socket, time\ns = socket.create_connection(('127.0.0.1', {port}))\ns.sendall(b'alive')\n" + tail
|
||||
|
||||
|
||||
async def open_liveness_listener() -> tuple[anyio.abc.SocketListener, int]:
|
||||
"""Open a TCP listener on localhost and return it along with its port."""
|
||||
multi = await anyio.create_tcp_listener(local_host="127.0.0.1")
|
||||
sock = multi.listeners[0]
|
||||
assert isinstance(sock, anyio.abc.SocketListener)
|
||||
addr = sock.extra(anyio.abc.SocketAttribute.local_address)
|
||||
# IPv4 local_address is (host: str, port: int)
|
||||
assert isinstance(addr, tuple) and len(addr) >= 2 and isinstance(addr[1], int)
|
||||
return sock, addr[1]
|
||||
|
||||
|
||||
async def accept_alive(sock: anyio.abc.SocketListener) -> anyio.abc.SocketStream:
|
||||
"""Accept one connection and assert the peer sent `b'alive'`.
|
||||
|
||||
Reads until the full 5-byte banner arrives (TCP may legally split even a tiny
|
||||
send). Callers bound this with `anyio.fail_after` to catch a subprocess that
|
||||
never started.
|
||||
"""
|
||||
stream = await sock.accept()
|
||||
msg = b""
|
||||
while len(msg) < 5:
|
||||
msg += await stream.receive(5 - len(msg))
|
||||
assert msg == b"alive", f"expected b'alive', got {msg!r}"
|
||||
return stream
|
||||
|
||||
|
||||
async def assert_stream_closed(stream: anyio.abc.SocketStream) -> None:
|
||||
"""Assert the peer holding the other end of `stream` has terminated."""
|
||||
with anyio.fail_after(5.0), pytest.raises((anyio.EndOfStream, anyio.BrokenResourceError)):
|
||||
await stream.receive(1)
|
||||
|
||||
|
||||
async def assert_peer_echoes(stream: anyio.abc.SocketStream) -> None: # pragma: lax no cover
|
||||
"""Assert the peer holding the other end of `stream` is still running.
|
||||
|
||||
Round-trips one echo through the stream (the peer must use `echo=True`); a dead
|
||||
process can never answer, so this cannot pass spuriously.
|
||||
|
||||
lax no cover: only POSIX-gated survival tests call this; Windows runners
|
||||
enforce 100% coverage per job.
|
||||
"""
|
||||
with anyio.fail_after(5.0):
|
||||
await stream.send(b"ping")
|
||||
# Read until the full echo has arrived: TCP may legally split even a tiny send.
|
||||
echoed = b""
|
||||
while len(echoed) < 4:
|
||||
echoed += await stream.receive(4 - len(echoed))
|
||||
assert echoed == b"ping", f"expected b'ping', got {echoed!r}"
|
||||
Reference in New Issue
Block a user