Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/runtime-sdk/tests/test_in_workerd.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest
from conftest import (
COMPAT_CONFIGS,
TESTLIB,
CompatConfig,
configure_compatibility,
)
Expand Down Expand Up @@ -91,6 +92,8 @@ def test_in_workerd( # noqa: PLR0913, PLR0917 (too-many-arguments)
target = tmp_path / test_dir.name
disk_service_dir = target / DISK_SERVICE_NAME
shutil.copytree(test_dir, target, ignore=shutil.ignore_patterns(".venv"))
# Worker projects depend on testlib via a relative wheel path (../testlib).
shutil.copytree(TESTLIB, tmp_path / "testlib")
disk_service_dir.mkdir(exist_ok=True)

configure_compatibility(target / "wrangler.jsonc", compat_config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,12 @@
from fastapi.responses import Response as FastAPIResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from pyodide.webloop import WebLoop
from starlette.background import BackgroundTask
from starlette.middleware.gzip import GZipMiddleware
from testlib.entrypoint import TestRunner

import asgi


async def _noop(*args):
pass


WebLoop.shutdown_asyncgens = _noop
WebLoop.shutdown_default_executor = _noop

STATIC_DIR = Path(__file__).parent / "static"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
name = "test"
version = "0.0.0"
requires-python = ">=3.12"
dependencies = ["pytest", "pytest-asyncio<1.2.0"]
dependencies = ["pytest", "pytest-asyncio<1.2.0", "testlib"]

[tool.uv.sources]
testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
import asyncio
import os
import sys
from urllib.parse import urlsplit

import pytest
from pyodide.webloop import WebLoop
from testlib.entrypoint import run_pytest

import asgi
from workers import WorkerEntrypoint


async def _noop(*args):
pass


# pytest-asyncio relies on these but in Pyodide < 0.29 WebLoop does not implement them
WebLoop.shutdown_asyncgens = _noop
WebLoop.shutdown_default_executor = _noop

# Pyodide 0.26.0a2's _cancel_all_tasks calls task.exception() on pending tasks,
# which raises InvalidStateError under Pyodide's WebLoop.
if sys.version_info < (3, 13):
asyncio.runners._cancel_all_tasks = lambda loop: None # type: ignore[attr-defined]


async def _drain_lifespan(receive, send):
message = await receive()
if message["type"] == "lifespan.startup":
Expand Down Expand Up @@ -180,4 +163,4 @@ async def fetch(self, request):
async def test(self):
os.chdir("/session/metadata/tests")
args = [".", "-vv"]
assert pytest.main(args) == 0
run_pytest(args)
5 changes: 4 additions & 1 deletion packages/runtime-sdk/tests/workerd-test/asgi/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
name = "test"
version = "0.0.0"
requires-python = ">=3.12"
dependencies = ["pytest", "pytest-asyncio<1.2.0"]
dependencies = ["pytest", "pytest-asyncio<1.2.0", "testlib"]

[tool.uv.sources]
testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
22 changes: 2 additions & 20 deletions packages/runtime-sdk/tests/workerd-test/asgi/worker.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
import asyncio
import os
import sys

import pytest
from pyodide.webloop import WebLoop
from testlib.entrypoint import run_pytest

import asgi
from workers import WorkerEntrypoint


async def noop(*args):
pass


# pytest-asyncio relies on these but in Pyodide < 0.29 WebLoop does not implement them
WebLoop.shutdown_asyncgens = noop
WebLoop.shutdown_default_executor = noop

# Pyodide 0.26.0a2's _cancel_all_tasks calls task.exception() on pending tasks,
# which raises InvalidStateError under Pyodide's WebLoop.
if sys.version_info < (3, 13):
asyncio.runners._cancel_all_tasks = lambda loop: None # type: ignore[attr-defined]


# ---------------------------------------------------------------------------
# ASGI apps
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -291,4 +273,4 @@ async def fetch(self, request):
async def test(self):
os.chdir("/session/metadata/tests")
args = [".", "-vv"]
assert pytest.main(args) == 0
run_pytest(args)
5 changes: 4 additions & 1 deletion packages/runtime-sdk/tests/workerd-test/sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
name = "test"
version = "0.0.0"
requires-python = ">=3.12"
dependencies = ["pytest", "pytest-asyncio<1.2.0"]
dependencies = ["pytest", "pytest-asyncio<1.2.0", "testlib"]

[tool.uv.sources]
testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
23 changes: 2 additions & 21 deletions packages/runtime-sdk/tests/workerd-test/sdk/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,15 @@
# behaviour doesn't need to strictly be held consistent. In fact it uses the JS fetch, so it's not
# going to follow the SDK at all.

import asyncio
import os
import sys
from functools import wraps

import pytest
from pyodide.webloop import WebLoop
from testlib.entrypoint import run_pytest

from workers import (
WorkerEntrypoint,
)


async def noop(*args):
pass


# pytest-asyncio relies on these but in Pyodide < 0.29 WebLoop does not implement them
WebLoop.shutdown_asyncgens = noop
WebLoop.shutdown_default_executor = noop

# Pyodide 0.26.0a2's _cancel_all_tasks calls task.exception() on pending tasks,
# which raises InvalidStateError under Pyodide's WebLoop.
# Ignore this error to prevent pytest-asyncio from crashing.
if sys.version_info < (3, 13):
asyncio.runners._cancel_all_tasks = lambda loop: None # type: ignore[attr-defined]


RESPONSE_HANDLER = None


Expand Down Expand Up @@ -68,4 +49,4 @@ async def test(self):
args = [".", "-vv"]
if self.env.color:
args.append("--color=yes")
assert pytest.main(args) == 0
run_pytest(args)
5 changes: 4 additions & 1 deletion packages/runtime-sdk/tests/workerd-test/wsgi/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
name = "test"
version = "0.0.0"
requires-python = ">=3.12"
dependencies = ["pytest", "pytest-asyncio<1.2.0"]
dependencies = ["pytest", "pytest-asyncio<1.2.0", "testlib"]

[tool.uv.sources]
testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
22 changes: 2 additions & 20 deletions packages/runtime-sdk/tests/workerd-test/wsgi/worker.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
import asyncio
import os
import sys

import pytest
from pyodide.ffi import run_sync
from pyodide.webloop import WebLoop
from testlib.entrypoint import run_pytest

from workers import WorkerEntrypoint, wsgi


async def noop(*args):
pass


# pytest-asyncio relies on these but in Pyodide < 0.29 WebLoop does not implement them
WebLoop.shutdown_asyncgens = noop
WebLoop.shutdown_default_executor = noop

# Pyodide 0.26.0a2's _cancel_all_tasks calls task.exception() on pending tasks,
# which raises InvalidStateError under Pyodide's WebLoop.
# Ignore this error to prevent pytest-asyncio from crashing.
if sys.version_info < (3, 13):
asyncio.runners._cancel_all_tasks = lambda loop: None # type: ignore[attr-defined]


# ---------------------------------------------------------------------------
# WSGI apps
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -140,4 +122,4 @@ async def test(self, ctrl):
args = [".", "-vv"]
if self.env.color:
args.append("--color=yes")
assert pytest.main(args) == 0
run_pytest(args)
11 changes: 11 additions & 0 deletions packages/testlib/testlib/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ def patch_asyncio():
asyncio.runners._cancel_all_tasks = orig_cancel_all_tasks # type: ignore[attr-defined]


def run_pytest(pytest_args):
"""Run pytest inside a worker with the asyncio patches applied.

Intended for workerd `test()` handlers that invoke pytest directly rather
than via `TestRunner`. Fails the calling test if pytest reports failures.
"""
with restore_loop(), patch_asyncio():
exit_code = pytest.main(pytest_args)
assert exit_code == 0, f"pytest exit code {exit_code}"


@dataclass
class TestRunnerResult:
payload: Any
Expand Down
Loading