-
Notifications
You must be signed in to change notification settings - Fork 3
Add daphne to conformance test #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -415,22 +415,64 @@ def _server_env(request: ServerCompatRequest) -> dict[str, str]: | |
|
|
||
|
|
||
| async def _tee_to_stderr(stream: asyncio.StreamReader) -> AsyncIterator[bytes]: | ||
| try: | ||
| while True: | ||
| line = await stream.readline() | ||
| if not line: | ||
| break | ||
| print(line.decode("utf-8"), end="", file=sys.stderr) # noqa: T201 | ||
| yield line | ||
| except asyncio.CancelledError: | ||
| pass | ||
| while True: | ||
| line = await stream.readline() | ||
| if not line: | ||
| break | ||
| print(line.decode("utf-8"), end="", file=sys.stderr) # noqa: T201 | ||
| yield line | ||
|
|
||
|
|
||
| async def _consume_log(stream: AsyncIterator[bytes]) -> None: | ||
| async for _ in stream: | ||
| pass | ||
|
|
||
|
|
||
| async def serve_daphne( | ||
| request: ServerCompatRequest, | ||
| certfile: str | None, | ||
| keyfile: str | None, | ||
| cafile: str | None, | ||
| port_future: asyncio.Future[int], | ||
| ): | ||
| args = [] | ||
| ssl_endpoint_parts = [] | ||
| if certfile: | ||
| ssl_endpoint_parts.append(f"certKey={certfile}") | ||
| if keyfile: | ||
| ssl_endpoint_parts.append(f"privateKey={keyfile}") | ||
| if cafile: | ||
| ssl_endpoint_parts.append(f"extraCertChain={cafile}") | ||
| if ssl_endpoint_parts: | ||
| args.append("-e") | ||
| args.append(f"ssl:port=0:{':'.join(ssl_endpoint_parts)}") | ||
| else: | ||
| args.append("-p=0") | ||
|
|
||
| args.append("server:asgi_app") | ||
|
|
||
| proc = await asyncio.create_subprocess_exec( | ||
| "daphne", | ||
| *args, | ||
| stderr=asyncio.subprocess.STDOUT, | ||
| stdout=asyncio.subprocess.PIPE, | ||
| env=_server_env(request), | ||
| ) | ||
| stdout = proc.stdout | ||
| assert stdout is not None | ||
| stdout = _tee_to_stderr(stdout) | ||
| try: | ||
| async for line in stdout: | ||
| if b"Listening on TCP address" in line: | ||
| port = line.decode("utf-8").strip().rsplit(":", 1)[1] | ||
| port_future.set_result(int(port)) | ||
| break | ||
| await _consume_log(stdout) | ||
| except asyncio.CancelledError: | ||
| proc.terminate() | ||
| await proc.wait() | ||
|
|
||
|
|
||
| async def serve_granian( | ||
| request: ServerCompatRequest, | ||
| mode: Literal["sync", "async"], | ||
|
|
@@ -465,7 +507,6 @@ async def serve_granian( | |
| *args, | ||
| stderr=asyncio.subprocess.STDOUT, | ||
| stdout=asyncio.subprocess.PIPE, | ||
| limit=1024, | ||
| env=_server_env(request), | ||
| ) | ||
| stdout = proc.stdout | ||
|
|
@@ -506,7 +547,6 @@ async def serve_gunicorn( | |
| *args, | ||
| stderr=asyncio.subprocess.STDOUT, | ||
| stdout=asyncio.subprocess.PIPE, | ||
| limit=1024, | ||
| env=_server_env(request), | ||
| ) | ||
| stdout = proc.stdout | ||
|
|
@@ -551,7 +591,6 @@ async def serve_hypercorn( | |
| *args, | ||
| stderr=asyncio.subprocess.STDOUT, | ||
| stdout=asyncio.subprocess.PIPE, | ||
| limit=1024, | ||
| env=_server_env(request), | ||
| ) | ||
| stdout = proc.stdout | ||
|
|
@@ -592,7 +631,6 @@ async def serve_uvicorn( | |
| *args, | ||
| stderr=asyncio.subprocess.STDOUT, | ||
| stdout=asyncio.subprocess.PIPE, | ||
| limit=1024, | ||
| env=_server_env(request), | ||
| ) | ||
| stdout = proc.stdout | ||
|
|
@@ -619,14 +657,14 @@ def _find_free_port(): | |
|
|
||
| class Args(argparse.Namespace): | ||
| mode: Literal["sync", "async"] | ||
| server: Literal["granian", "hypercorn", "uvicorn"] | ||
| server: Literal["daphne", "granian", "hypercorn", "uvicorn"] | ||
|
||
|
|
||
|
|
||
| async def main() -> None: | ||
| parser = argparse.ArgumentParser(description="Conformance server") | ||
| parser.add_argument("--mode", choices=["sync", "async"]) | ||
| parser.add_argument( | ||
| "--server", choices=["granian", "gunicorn", "hypercorn", "uvicorn"] | ||
| "--server", choices=["daphne", "granian", "gunicorn", "hypercorn", "uvicorn"] | ||
| ) | ||
| args = parser.parse_args(namespace=Args()) | ||
|
|
||
|
|
@@ -663,6 +701,13 @@ async def main() -> None: | |
| with cleanup: | ||
| port_future: asyncio.Future[int] = asyncio.get_event_loop().create_future() | ||
| match args.server: | ||
| case "daphne": | ||
| if args.mode == "sync": | ||
| msg = "daphne does not support sync mode" | ||
| raise ValueError(msg) | ||
| serve_task = asyncio.create_task( | ||
| serve_daphne(request, certfile, keyfile, cafile, port_future) | ||
| ) | ||
| case "granian": | ||
| serve_task = asyncio.create_task( | ||
| serve_granian( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,13 +87,23 @@ def test_server_sync(server: str) -> None: | |
| pytest.fail(f"\n{result.stdout}\n{result.stderr}") | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("server", ["granian", "hypercorn", "uvicorn"]) | ||
| @pytest.mark.parametrize("server", ["daphne", "granian", "hypercorn", "uvicorn"]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Daphne doesn't support wsgi so only async. |
||
| def test_server_async(server: str) -> None: | ||
| args = maybe_patch_args_with_debug( | ||
| [sys.executable, _server_py_path, "--mode", "async", "--server", server] | ||
| ) | ||
| opts = [] | ||
| match server: | ||
| case "daphne": | ||
| opts = [ | ||
| # daphne doesn't support h2c | ||
| "--skip", | ||
| "**/HTTPVersion:2/**/TLS:false/**", | ||
| # daphne seems to block on the request body so can't do full duplex even with h2, | ||
| # it only works with websockets | ||
| "--skip", | ||
| "**/full-duplex/**", | ||
| ] | ||
| case "granian" | "hypercorn": | ||
| # granian and hypercorn seem to have issues with concurrency | ||
| opts = ["--parallel", "1"] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ dev = [ | |
| "asgiref==3.9.1", | ||
| "brotli==1.1.0", | ||
| "connect-python-example", | ||
| "daphne==4.2.1", | ||
| "httpx[http2]==0.28.1", | ||
| "hypercorn==0.17.3", | ||
| "granian==2.5.5", | ||
|
|
@@ -53,6 +54,7 @@ dev = [ | |
| "pytest-cov==7.0.0", | ||
| "ruff~=0.13.2", | ||
| "uvicorn==0.37.0", | ||
| "Twisted[tls,http2]==25.5.0", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will add a comment |
||
| "typing_extensions==4.15.0", | ||
| "zstandard==0.25.0", | ||
| ] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had accidentally added this before I think. It ends up swallowing the error preventing
terminatefrom being called on the subprocesses