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
50 changes: 50 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,56 @@ jobs:
run: |
uv run --frozen pytest -v --color=yes tests

sdk-hyperdrive-test:
# Hyperdrive talks to real databases, so this job is separate from sdk-test:
# service containers only run on Linux runners.
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
POSTGRES_HOST_AUTH_METHOD: md5
POSTGRES_INITDB_ARGS: "--auth-host=md5"
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U testuser -d testdb"
--health-interval=10s
--health-timeout=5s
--health-retries=5

mysql:
image: mysql:8.4
env:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_USER: testuser
MYSQL_PASSWORD: testpass
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1"
--health-interval=10s
--health-timeout=5s
--health-retries=5

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: '3.13'

- name: Run Hyperdrive binding tests
working-directory: packages/runtime-sdk
run: |
uv run --frozen pytest -v --color=yes -m hyperdrive tests

django-test:
strategy:
fail-fast: false
Expand Down
3 changes: 3 additions & 0 deletions packages/runtime-sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ lint.per-file-ignores."tests/web-frameworks-test/**" = ["C901", "PLR0911", "PLR0

[tool.pytest.ini_options]
addopts = ["--ignore=tests/workerd-test", "--ignore=tests/bindings-test", "--ignore=tests/web-frameworks-test"]
markers = [
"hyperdrive: needs MySQL and PostgreSQL on localhost; skipped unless run with -m hyperdrive",
]

[tool.mypy]
packages = ["workers_runtime_sdk"]
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-sdk/tests/bindings-test/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
name = "bindings-test"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pytest", "pytest-asyncio<1.2.0"]
dependencies = ["pytest", "pytest-asyncio<1.2.0", "pg8000", "pymysql", "cryptography"]
6 changes: 6 additions & 0 deletions packages/runtime-sdk/tests/bindings-test/src/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# pyright: reportMissingImports=false
import uuid

import pytest

Expand All @@ -8,3 +9,8 @@
@pytest.fixture
def env():
return _env


def unique_table_name() -> str:
"""Unique per call"""
return f"test_{uuid.uuid4().hex[:10]}"
208 changes: 208 additions & 0 deletions packages/runtime-sdk/tests/bindings-test/src/test_hyperdrive_mysql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# pyright: reportMissingImports=false

"""
This test requires a MySQL server to be running on localhost.

Run mysql with docker:

docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_USER=testuser \
-e MYSQL_PASSWORD=testpass \
-e MYSQL_DATABASE=testdb \
-p 3306:3306 \
--health-cmd="mysqladmin ping -h 127.0.0.1" \
--health-interval=10s \
--health-timeout=5s \
--health-retries=5 \
mysql:8.4

Then run the test:

uv run pytest tests/test_bindings.py -m hyperdrive -k mysql
"""

import sys

import pymysql
import pytest
from conftest import unique_table_name


@pytest.fixture(autouse=True)
def skip_if_no_socket_support():
if sys.version_info.minor < 14:
pytest.skip("Socket support requires Python 3.14+")


def _connect(env):
hd = env.HYPERDRIVE_MYSQL
return pymysql.connect(
host=hd.host,
port=int(hd.port),
user=hd.user,
password=hd.password,
database=hd.database,
unix_socket=False,
# Hyperdrive terminates TLS to the origin, so this hop is plaintext.
ssl_disabled=True,
)


@pytest.mark.asyncio
async def test_connect(env):
conn = _connect(env)
cur = conn.cursor()
cur.execute("SELECT 1")
assert cur.fetchone() == (1,)
conn.close()


@pytest.mark.asyncio
async def test_create_insert_select(env):
conn = _connect(env)
table = unique_table_name()
cur = conn.cursor()
try:
cur.execute(
f"CREATE TABLE {table} "
"(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), value INT)"
)
cur.execute(f"INSERT INTO {table} (name, value) VALUES (%s, %s)", ("alpha", 1))
cur.execute(f"INSERT INTO {table} (name, value) VALUES (%s, %s)", ("beta", 2))
conn.commit()

cur.execute(f"SELECT name, value FROM {table} ORDER BY name")
assert cur.fetchall() == (("alpha", 1), ("beta", 2))
finally:
cur.execute(f"DROP TABLE IF EXISTS {table}")
conn.commit()
conn.close()


@pytest.mark.asyncio
async def test_update(env):
conn = _connect(env)
table = unique_table_name()
cur = conn.cursor()
try:
cur.execute(
f"CREATE TABLE {table} "
"(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), value INT)"
)
cur.execute(f"INSERT INTO {table} (name, value) VALUES (%s, %s)", ("alpha", 1))
conn.commit()

cur.execute(
f"UPDATE {table} SET value = value + 10 WHERE name = %s", ("alpha",)
)
conn.commit()

cur.execute(f"SELECT value FROM {table} WHERE name = %s", ("alpha",))
assert cur.fetchone() == (11,)
finally:
cur.execute(f"DROP TABLE IF EXISTS {table}")
conn.commit()
conn.close()


@pytest.mark.asyncio
async def test_delete(env):
conn = _connect(env)
table = unique_table_name()
cur = conn.cursor()
try:
cur.execute(
f"CREATE TABLE {table} (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))"
)
cur.execute(f"INSERT INTO {table} (name) VALUES (%s)", ("alpha",))
cur.execute(f"INSERT INTO {table} (name) VALUES (%s)", ("beta",))
conn.commit()

cur.execute(f"DELETE FROM {table} WHERE name = %s", ("alpha",))
conn.commit()

cur.execute(f"SELECT name FROM {table}")
assert cur.fetchall() == (("beta",),)
finally:
cur.execute(f"DROP TABLE IF EXISTS {table}")
conn.commit()
conn.close()


@pytest.mark.asyncio
async def test_transaction_rollback(env):
conn = _connect(env)
table = unique_table_name()
cur = conn.cursor()
try:
cur.execute(
f"CREATE TABLE {table} "
"(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100)) ENGINE=InnoDB"
)
conn.commit()

cur.execute(f"INSERT INTO {table} (name) VALUES (%s)", ("should_disappear",))
conn.rollback()

cur.execute(f"SELECT COUNT(*) FROM {table}")
assert cur.fetchone() == (0,)
finally:
cur.execute(f"DROP TABLE IF EXISTS {table}")
conn.commit()
conn.close()


@pytest.mark.asyncio
async def test_multiple_data_types(env):
conn = _connect(env)
table = unique_table_name()
cur = conn.cursor()
try:
cur.execute(
f"CREATE TABLE {table} ("
"id INT AUTO_INCREMENT PRIMARY KEY, "
"text_col VARCHAR(255), "
"int_col INT, "
"float_col DOUBLE, "
"bool_col BOOLEAN)"
)
cur.execute(
f"INSERT INTO {table} (text_col, int_col, float_col, bool_col) "
"VALUES (%s, %s, %s, %s)",
("hello", 42, 3.14, True),
)
conn.commit()

cur.execute(f"SELECT text_col, int_col, float_col, bool_col FROM {table}")
row = cur.fetchone()
assert row[0] == "hello"
assert row[1] == 42
assert abs(row[2] - 3.14) < 0.001
assert row[3] == 1
finally:
cur.execute(f"DROP TABLE IF EXISTS {table}")
conn.commit()
conn.close()


@pytest.mark.asyncio
async def test_executemany(env):
conn = _connect(env)
table = unique_table_name()
cur = conn.cursor()
try:
cur.execute(
f"CREATE TABLE {table} (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))"
)
cur.executemany(
f"INSERT INTO {table} (name) VALUES (%s)", [("a",), ("b",), ("c",)]
)
conn.commit()

cur.execute(f"SELECT name FROM {table} ORDER BY name")
assert cur.fetchall() == (("a",), ("b",), ("c",))
finally:
cur.execute(f"DROP TABLE IF EXISTS {table}")
conn.commit()
conn.close()
Loading
Loading