Skip to content

Commit c88eb95

Browse files
committed
Run conformance test with pyvoy
Signed-off-by: Anuraag Agrawal <[email protected]>
1 parent 6be07d9 commit c88eb95

File tree

6 files changed

+244
-118
lines changed

6 files changed

+244
-118
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
- name: run conformance tests
8585
# TODO: Debug stdin/stdout issues on Windows
8686
if: ${{ !startsWith(matrix.os, 'windows-') }}
87-
run: uv run pytest -rfEP ${{ matrix.coverage == 'cov' && '--cov=connectrpc --cov-report=xml' || '' }}
87+
run: uv run pytest ${{ matrix.coverage == 'cov' && '--cov=connectrpc --cov-report=xml' || '' }}
8888
working-directory: conformance
8989

9090
- name: run tests with minimal dependencies

conformance/test/_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import sys
33

4-
VERSION_CONFORMANCE = "v1.0.4"
4+
VERSION_CONFORMANCE = "0bed9ca203aeda4e344edc442a4b2ede91726db5"
55

66

77
async def create_standard_streams():

conformance/test/server.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,50 @@ async def serve_hypercorn(
608608
await proc.wait()
609609

610610

611+
async def serve_pyvoy(
612+
request: ServerCompatRequest,
613+
mode: Literal["sync", "async"],
614+
certfile: str | None,
615+
keyfile: str | None,
616+
cafile: str | None,
617+
port_future: asyncio.Future[int],
618+
):
619+
args = ["--port=0"]
620+
if certfile:
621+
args.append(f"--tls-cert={certfile}")
622+
if keyfile:
623+
args.append(f"--tls-key={keyfile}")
624+
if cafile:
625+
args.append(f"--tls-ca-cert={cafile}")
626+
627+
if mode == "sync":
628+
args.append("--interface=wsgi")
629+
args.append("server:wsgi_app")
630+
else:
631+
args.append("server:asgi_app")
632+
633+
proc = await asyncio.create_subprocess_exec(
634+
"pyvoy",
635+
*args,
636+
stderr=asyncio.subprocess.STDOUT,
637+
stdout=asyncio.subprocess.PIPE,
638+
env=_server_env(request),
639+
)
640+
stdout = proc.stdout
641+
assert stdout is not None
642+
stdout = _tee_to_stderr(stdout)
643+
try:
644+
async for line in stdout:
645+
if b"listening on" in line:
646+
port = int(line.strip().split(b"127.0.0.1:")[1])
647+
port_future.set_result(port)
648+
break
649+
await _consume_log(stdout)
650+
except asyncio.CancelledError:
651+
proc.terminate()
652+
await proc.wait()
653+
654+
611655
async def serve_uvicorn(
612656
request: ServerCompatRequest,
613657
certfile: str | None,
@@ -728,14 +772,22 @@ async def main() -> None:
728772
request, args.mode, certfile, keyfile, cafile, port_future
729773
)
730774
)
775+
case "pyvoy":
776+
serve_task = asyncio.create_task(
777+
serve_pyvoy(
778+
request, args.mode, certfile, keyfile, cafile, port_future
779+
)
780+
)
731781
case "uvicorn":
732782
if args.mode == "sync":
733783
msg = "uvicorn does not support sync mode"
734784
raise ValueError(msg)
735785
serve_task = asyncio.create_task(
736786
serve_uvicorn(request, certfile, keyfile, cafile, port_future)
737787
)
788+
738789
asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, serve_task.cancel)
790+
739791
port = await port_future
740792
response = ServerCompatResponse()
741793
response.host = "127.0.0.1"

conformance/test/test_server.py

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def macos_raise_ulimit():
2727

2828
# There is a relatively low time limit for the server to respond with a resource error
2929
# for this test. In resource limited environments such as CI, it doesn't seem to be enough,
30-
# notably it is the first request that will take the longest to process as it also sets up
30+
# notably it is the first message that will take the longest to process as it also sets up
3131
# the request. We can consider raising this delay in the runner to see if it helps.
3232
#
3333
# https://github.com/connectrpc/conformance/blob/main/internal/app/connectconformance/testsuites/data/server_message_size.yaml#L46
@@ -37,24 +37,24 @@ def macos_raise_ulimit():
3737
]
3838

3939

40-
@pytest.mark.parametrize("server", ["granian", "gunicorn", "hypercorn"])
40+
@pytest.mark.parametrize("server", ["granian", "gunicorn", "hypercorn", "pyvoy"])
4141
def test_server_sync(server: str) -> None:
42+
if server == "pyvoy" and sys.platform == "win32":
43+
pytest.skip("pyvoy not supported on Windows")
44+
4245
args = maybe_patch_args_with_debug(
4346
[sys.executable, _server_py_path, "--mode", "sync", "--server", server]
4447
)
45-
opts = [
46-
# While Hypercorn and Granian supports HTTP/2 and WSGI, they both have simple wrappers
47-
# that reads the entire request body before running the application, which does not work for
48-
# full duplex. There are no other popular WSGI servers that support HTTP/2, so in practice
49-
# it cannot be supported. It is possible in theory following hyper-h2's example code in
50-
# https://python-hyper.org/projects/hyper-h2/en/stable/wsgi-example.html though.
51-
"--skip",
52-
"**/bidi-stream/full-duplex/**",
53-
]
48+
opts = []
5449
match server:
5550
case "granian" | "hypercorn":
56-
# granian and hypercorn seem to have issues with concurrency
57-
opts += ["--parallel", "1"]
51+
opts += [
52+
# While Hypercorn and Granian supports HTTP/2 and WSGI, they both have simple wrappers
53+
# that reads the entire request body before running the application, which does not work for
54+
# full duplex.
55+
"--skip",
56+
"**/bidi-stream/full-duplex/**",
57+
]
5858
case "gunicorn":
5959
# gunicorn doesn't support HTTP/2
6060
opts = ["--skip", "**/HTTPVersion:2/**"]
@@ -78,17 +78,16 @@ def test_server_sync(server: str) -> None:
7878
check=False,
7979
)
8080
if result.returncode != 0:
81-
if server == "granian":
82-
# Even with low parallelism, some tests are flaky. We'll need to investigate further.
83-
print( # noqa: T201
84-
f"Granian server tests failed, see output below, not treating as failure:\n{result.stdout}\n{result.stderr}"
85-
)
86-
return
8781
pytest.fail(f"\n{result.stdout}\n{result.stderr}")
8882

8983

90-
@pytest.mark.parametrize("server", ["daphne", "granian", "hypercorn", "uvicorn"])
84+
@pytest.mark.parametrize(
85+
"server", ["daphne", "granian", "hypercorn", "pyvoy", "uvicorn"]
86+
)
9187
def test_server_async(server: str) -> None:
88+
if server == "pyvoy" and sys.platform == "win32":
89+
pytest.skip("pyvoy not supported on Windows")
90+
9291
args = maybe_patch_args_with_debug(
9392
[sys.executable, _server_py_path, "--mode", "async", "--server", server]
9493
)
@@ -104,9 +103,6 @@ def test_server_async(server: str) -> None:
104103
"--skip",
105104
"**/full-duplex/**",
106105
]
107-
case "granian" | "hypercorn":
108-
# granian and hypercorn seem to have issues with concurrency
109-
opts = ["--parallel", "1"]
110106
case "uvicorn":
111107
# uvicorn doesn't support HTTP/2
112108
opts = ["--skip", "**/HTTPVersion:2/**"]
@@ -115,6 +111,7 @@ def test_server_async(server: str) -> None:
115111
"go",
116112
"run",
117113
f"connectrpc.com/conformance/cmd/connectconformance@{VERSION_CONFORMANCE}",
114+
"-v",
118115
"--conf",
119116
_config_path,
120117
"--mode",
@@ -129,10 +126,4 @@ def test_server_async(server: str) -> None:
129126
check=False,
130127
)
131128
if result.returncode != 0:
132-
if server == "granian":
133-
# Even with low parallelism, some tests are flaky. We'll need to investigate further.
134-
print( # noqa: T201
135-
f"Granian server tests failed, see output below, not treating as failure:\n{result.stdout}\n{result.stderr}"
136-
)
137-
return
138129
pytest.fail(f"\n{result.stdout}\n{result.stderr}")

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ dev = [
4242
"daphne==4.2.1",
4343
"httpx[http2]==0.28.1",
4444
"hypercorn==0.17.3",
45-
"granian==2.5.5",
45+
"granian==2.5.7",
4646
"gunicorn==23.0.0",
4747
"just-bin==1.42.4; sys_platform != 'win32'",
4848
"mkdocs==1.6.1",
@@ -52,6 +52,7 @@ dev = [
5252
"pytest==8.4.2",
5353
"pytest-asyncio==1.2.0",
5454
"pytest-cov==7.0.0",
55+
"pyvoy==0.1.0; sys_platform != 'win32'",
5556
"ruff~=0.13.2",
5657
"uvicorn==0.37.0",
5758
# Needed to enable HTTP/2 in daphne

0 commit comments

Comments
 (0)