Skip to content

Commit f993ce8

Browse files
authored
Add daphne to conformance test (#40)
Signed-off-by: Anuraag Agrawal <[email protected]>
1 parent cbb7ce3 commit f993ce8

File tree

4 files changed

+490
-22
lines changed

4 files changed

+490
-22
lines changed

conformance/test/server.py

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from collections.abc import AsyncIterator, Iterator
1111
from contextlib import ExitStack, closing
1212
from tempfile import NamedTemporaryFile
13-
from typing import TYPE_CHECKING, Literal, TypeVar
13+
from typing import TYPE_CHECKING, Literal, TypeVar, get_args
1414

1515
from _util import create_standard_streams
1616
from gen.connectrpc.conformance.v1.config_pb2 import Code as ConformanceCode
@@ -415,22 +415,64 @@ def _server_env(request: ServerCompatRequest) -> dict[str, str]:
415415

416416

417417
async def _tee_to_stderr(stream: asyncio.StreamReader) -> AsyncIterator[bytes]:
418-
try:
419-
while True:
420-
line = await stream.readline()
421-
if not line:
422-
break
423-
print(line.decode("utf-8"), end="", file=sys.stderr) # noqa: T201
424-
yield line
425-
except asyncio.CancelledError:
426-
pass
418+
while True:
419+
line = await stream.readline()
420+
if not line:
421+
break
422+
print(line.decode("utf-8"), end="", file=sys.stderr) # noqa: T201
423+
yield line
427424

428425

429426
async def _consume_log(stream: AsyncIterator[bytes]) -> None:
430427
async for _ in stream:
431428
pass
432429

433430

431+
async def serve_daphne(
432+
request: ServerCompatRequest,
433+
certfile: str | None,
434+
keyfile: str | None,
435+
cafile: str | None,
436+
port_future: asyncio.Future[int],
437+
):
438+
args = []
439+
ssl_endpoint_parts = []
440+
if certfile:
441+
ssl_endpoint_parts.append(f"certKey={certfile}")
442+
if keyfile:
443+
ssl_endpoint_parts.append(f"privateKey={keyfile}")
444+
if cafile:
445+
ssl_endpoint_parts.append(f"extraCertChain={cafile}")
446+
if ssl_endpoint_parts:
447+
args.append("-e")
448+
args.append(f"ssl:port=0:{':'.join(ssl_endpoint_parts)}")
449+
else:
450+
args.append("-p=0")
451+
452+
args.append("server:asgi_app")
453+
454+
proc = await asyncio.create_subprocess_exec(
455+
"daphne",
456+
*args,
457+
stderr=asyncio.subprocess.STDOUT,
458+
stdout=asyncio.subprocess.PIPE,
459+
env=_server_env(request),
460+
)
461+
stdout = proc.stdout
462+
assert stdout is not None
463+
stdout = _tee_to_stderr(stdout)
464+
try:
465+
async for line in stdout:
466+
if b"Listening on TCP address" in line:
467+
port = line.decode("utf-8").strip().rsplit(":", 1)[1]
468+
port_future.set_result(int(port))
469+
break
470+
await _consume_log(stdout)
471+
except asyncio.CancelledError:
472+
proc.terminate()
473+
await proc.wait()
474+
475+
434476
async def serve_granian(
435477
request: ServerCompatRequest,
436478
mode: Literal["sync", "async"],
@@ -465,7 +507,6 @@ async def serve_granian(
465507
*args,
466508
stderr=asyncio.subprocess.STDOUT,
467509
stdout=asyncio.subprocess.PIPE,
468-
limit=1024,
469510
env=_server_env(request),
470511
)
471512
stdout = proc.stdout
@@ -506,7 +547,6 @@ async def serve_gunicorn(
506547
*args,
507548
stderr=asyncio.subprocess.STDOUT,
508549
stdout=asyncio.subprocess.PIPE,
509-
limit=1024,
510550
env=_server_env(request),
511551
)
512552
stdout = proc.stdout
@@ -551,7 +591,6 @@ async def serve_hypercorn(
551591
*args,
552592
stderr=asyncio.subprocess.STDOUT,
553593
stdout=asyncio.subprocess.PIPE,
554-
limit=1024,
555594
env=_server_env(request),
556595
)
557596
stdout = proc.stdout
@@ -592,7 +631,6 @@ async def serve_uvicorn(
592631
*args,
593632
stderr=asyncio.subprocess.STDOUT,
594633
stdout=asyncio.subprocess.PIPE,
595-
limit=1024,
596634
env=_server_env(request),
597635
)
598636
stdout = proc.stdout
@@ -617,17 +655,18 @@ def _find_free_port():
617655
return s.getsockname()[1]
618656

619657

658+
Server = Literal["daphne", "granian", "gunicorn", "hypercorn", "uvicorn"]
659+
660+
620661
class Args(argparse.Namespace):
621662
mode: Literal["sync", "async"]
622-
server: Literal["granian", "hypercorn", "uvicorn"]
663+
server: Server
623664

624665

625666
async def main() -> None:
626667
parser = argparse.ArgumentParser(description="Conformance server")
627668
parser.add_argument("--mode", choices=["sync", "async"])
628-
parser.add_argument(
629-
"--server", choices=["granian", "gunicorn", "hypercorn", "uvicorn"]
630-
)
669+
parser.add_argument("--server", choices=get_args(Server))
631670
args = parser.parse_args(namespace=Args())
632671

633672
stdin, stdout = await create_standard_streams()
@@ -663,6 +702,13 @@ async def main() -> None:
663702
with cleanup:
664703
port_future: asyncio.Future[int] = asyncio.get_event_loop().create_future()
665704
match args.server:
705+
case "daphne":
706+
if args.mode == "sync":
707+
msg = "daphne does not support sync mode"
708+
raise ValueError(msg)
709+
serve_task = asyncio.create_task(
710+
serve_daphne(request, certfile, keyfile, cafile, port_future)
711+
)
666712
case "granian":
667713
serve_task = asyncio.create_task(
668714
serve_granian(

conformance/test/test_server.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,23 @@ def test_server_sync(server: str) -> None:
8787
pytest.fail(f"\n{result.stdout}\n{result.stderr}")
8888

8989

90-
@pytest.mark.parametrize("server", ["granian", "hypercorn", "uvicorn"])
90+
@pytest.mark.parametrize("server", ["daphne", "granian", "hypercorn", "uvicorn"])
9191
def test_server_async(server: str) -> None:
9292
args = maybe_patch_args_with_debug(
9393
[sys.executable, _server_py_path, "--mode", "async", "--server", server]
9494
)
9595
opts = []
9696
match server:
97+
case "daphne":
98+
opts = [
99+
# daphne doesn't support h2c
100+
"--skip",
101+
"**/HTTPVersion:2/**/TLS:false/**",
102+
# daphne seems to block on the request body so can't do full duplex even with h2,
103+
# it only works with websockets
104+
"--skip",
105+
"**/full-duplex/**",
106+
]
97107
case "granian" | "hypercorn":
98108
# granian and hypercorn seem to have issues with concurrency
99109
opts = ["--parallel", "1"]

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dev = [
3939
"asgiref==3.9.1",
4040
"brotli==1.1.0",
4141
"connect-python-example",
42+
"daphne==4.2.1",
4243
"httpx[http2]==0.28.1",
4344
"hypercorn==0.17.3",
4445
"granian==2.5.5",
@@ -53,6 +54,8 @@ dev = [
5354
"pytest-cov==7.0.0",
5455
"ruff~=0.13.2",
5556
"uvicorn==0.37.0",
57+
# Needed to enable HTTP/2 in daphne
58+
"Twisted[tls,http2]==25.5.0",
5659
"typing_extensions==4.15.0",
5760
"zstandard==0.25.0",
5861
]

0 commit comments

Comments
 (0)