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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,26 @@ on:
pull_request:
branches:
- master

jobs:
validation:
runs-on: ubuntu-latest
env:
SINGLESTORE_HOST: 127.0.0.1
SINGLESTORE_PORT: 33071
SINGLESTORE_USER: root
SINGLESTORE_PASSWORD: test
SINGLESTORE_DATABASE: test_db
services:
singlestoredb:
image: ghcr.io/singlestore-labs/singlestoredb-dev:latest
ports:
- 33071:3306 # MySQL port
- 18091:8080 # Web UI port
- 19191:9000 # Management port
env:
ROOT_PASSWORD: test

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -28,6 +45,14 @@ jobs:
- name: Formatting Check
run: make format-check

- name: Sanity check using mysql client
run: |
mysql -u root -ptest -e "SELECT 1" -h 127.0.0.1 -P 33071

- name: Create test databases
run: |
mysql -u root -ptest -h 127.0.0.1 -P 33071 -e "CREATE DATABASE IF NOT EXISTS test_db; CREATE DATABASE IF NOT EXISTS test_example; CREATE DATABASE IF NOT EXISTS test_example_async; SHOW DATABASES;"

- name: Run Tests
run: make tests

Expand Down
52 changes: 36 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: lint-check lint-fix format-check format-fix type-check run-checks deps install tests uninstall clean release setup-db teardown-db reset-db test-connection
.PHONY: lint-check lint-fix format-check format-fix type-check run-checks deps install tests uninstall clean release setup-db teardown-db reset-db status-db test-connection

lint-check:
uv run ruff check
Expand Down Expand Up @@ -42,24 +42,44 @@ release:
uv run scripts/release.py

setup-db: ## Start SingleStore database
@echo "Starting SingleStore database..."
@echo "Checking for existing containers..."
@docker compose -f tests/compose-singlestore.yml down -v 2>/dev/null || true
@echo "Starting fresh container..."
docker compose -f tests/compose-singlestore.yml up -d
@echo "Waiting for database to be ready and initialized..."
@echo "Waiting for database..."
@until docker compose -f tests/compose-singlestore.yml exec -T singlestore-test singlestore -u root -ptest_password_123 -e "SELECT 1" >/dev/null 2>&1; do echo "Waiting for database..."; sleep 5; done
@echo "Creating test databases..."
@docker compose -f tests/compose-singlestore.yml exec -T singlestore-test singlestore -u root -ptest_password_123 -e "CREATE DATABASE IF NOT EXISTS test_db; CREATE DATABASE IF NOT EXISTS test_example; CREATE DATABASE IF NOT EXISTS test_example_async;"
@echo "Verifying database initialization..."
@docker compose -f tests/compose-singlestore.yml exec -T singlestore-test singlestore -u root -ptest_password_123 -e "SHOW DATABASES;" | grep test_db || echo "Database initialization pending..."
@echo "🚀 Starting SingleStore database..."
@docker compose -f tests/compose-singlestore.yml down -v >/dev/null 2>&1 || true
@docker compose -f tests/compose-singlestore.yml up -d >/dev/null 2>&1
@echo "⏳ Waiting for database to be ready..."
@timeout 30 bash -c 'i=0; until docker compose -f tests/compose-singlestore.yml ps | grep -q "healthy"; do i=$$((i+3)); echo "⏳ Still waiting for database... ($$i/30s)"; sleep 3; done; echo "✅ Database is ready!"' || (echo "❌ Database failed to start within 30 seconds"; exit 1)
@echo "📝 Initializing database schema..."
@docker compose -f tests/compose-singlestore.yml exec -T singlestore-test singlestore -u root -ptest < tests/init.sql >/dev/null 2>&1 && echo "✅ Schema initialized successfully"
@echo "✅ Database ready and initialized"
@echo " • Port: 33071"
@echo " • Web UI: http://localhost:18091"
@echo " • Test databases:"
@docker compose -f tests/compose-singlestore.yml exec -T singlestore-test singlestore -u root -ptest -e "SHOW DATABASES;" 2>/dev/null | grep -E "test" | sed 's/^/ /'

teardown-db: ## Stop SingleStore database
@echo "Stopping SingleStore database..."
docker compose -f tests/compose-singlestore.yml down -v
@echo "🛑 Stopping SingleStore database..."
@docker compose -f tests/compose-singlestore.yml down -v >/dev/null 2>&1
@echo "✅ Database stopped"

reset-db: teardown-db setup-db ## Reset SingleStore database (stop and start fresh)

status-db: ## Check SingleStore database status
@echo "🔍 Checking database status..."
@if docker compose -f tests/compose-singlestore.yml ps | grep -q "singlestore-test.*Up"; then \
echo "✅ SingleStore database is running"; \
echo " • Port: 33071"; \
echo " • Web UI: http://localhost:18091"; \
echo " • Management: http://localhost:19191"; \
else \
echo "❌ SingleStore database is not running"; \
echo " Run 'make setup-db' to start it"; \
fi

test-connection: ## Test connection from host using custom port
mysql -h localhost -P 33071 -u root -ptest_password_123 -e "SHOW DATABASES;" 2>/dev/null || echo "❌ Cannot connect from host - ensure SingleStore is running"
@echo "🔍 Testing database connection..."
@echo "⏳ Attempting to connect..."
@if docker compose -f tests/compose-singlestore.yml exec -T singlestore-test singlestore -u root -ptest -e "SHOW DATABASES;" >/dev/null 2>&1; then \
echo "✅ Connection successful - database is responding"; \
else \
echo "❌ Connection failed - ensure SingleStore is running"; \
echo " Try: make setup-db"; \
fi
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ addopts = [
"--cov=langgraph.checkpoint.singlestore",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-fail-under=80",
"--cov-fail-under=75",
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
Expand Down
12 changes: 6 additions & 6 deletions tests/compose-singlestore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ services:
- "18091:8080" # Web UI port
- "19191:9000" # Management port
environment:
ROOT_PASSWORD: test_password_123
ROOT_PASSWORD: test
volumes:
- singlestore_data:/var/lib/singlestore
healthcheck:
test: ["CMD", "singlestore", "-u", "root", "-ptest_password_123", "-e", "SELECT 1"]
start_period: 60s
timeout: 10s
retries: 15
interval: 5s
test: ["CMD", "singlestore", "-u", "root", "-ptest", "-e", "SELECT 1"]
start_period: 20s
timeout: 5s
retries: 8
interval: 3s
deploy:
resources:
limits:
Expand Down
8 changes: 4 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
import singlestoredb
from singlestoredb.connection import Connection

DEFAULT_SINGLESTORE_URI = "root:test_password_123@localhost:33071"
DEFAULT_URI = "root:test_password_123@localhost:33071/test_db"
DEFAULT_URI_WITHOUT_DB = "root:test@127.0.0.1:33071"
DEFAULT_URI_WITH_DB = "root:test@127.0.0.1:33071/test_db"


@pytest.fixture(scope="function")
def conn() -> Iterator[Connection]:
"""Sync connection fixture for SingleStore."""
with singlestoredb.connect(DEFAULT_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITH_DB, autocommit=True, results_type="dict") as conn:
yield conn


@pytest.fixture(scope="function")
async def aconn() -> AsyncIterator[Connection]:
"""Async connection fixture for SingleStore."""
with singlestoredb.connect(DEFAULT_URI, autocommit=True, results_type="dict") as conn:
async with singlestoredb.connect(DEFAULT_URI_WITH_DB, autocommit=True, results_type="dict") as conn:
yield conn


Expand Down
14 changes: 7 additions & 7 deletions tests/test_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from langgraph.checkpoint.serde.types import TASKS
from langgraph.checkpoint.singlestore.aio import AsyncSingleStoreSaver
from tests.conftest import DEFAULT_SINGLESTORE_URI
from tests.conftest import DEFAULT_URI_WITHOUT_DB


def _exclude_keys(config: dict[str, Any]) -> dict[str, Any]:
Expand All @@ -29,12 +29,12 @@ async def _base_saver():
"""Fixture for regular connection mode testing."""
database = f"test_{uuid4().hex[:16]}"
# create unique db
with singlestoredb.connect(DEFAULT_SINGLESTORE_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
with conn.cursor() as cursor:
cursor.execute(f"CREATE DATABASE {database}")
try:
with singlestoredb.connect(
f"{DEFAULT_SINGLESTORE_URI}/{database}",
f"{DEFAULT_URI_WITHOUT_DB}/{database}",
autocommit=True,
results_type="dict",
) as conn:
Expand All @@ -43,7 +43,7 @@ async def _base_saver():
yield checkpointer
finally:
# drop unique db
with singlestoredb.connect(DEFAULT_SINGLESTORE_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
with conn.cursor() as cursor:
cursor.execute(f"DROP DATABASE {database}")

Expand Down Expand Up @@ -314,12 +314,12 @@ async def test_from_conn_string() -> None:
database = f"test_{uuid4().hex[:16]}"

# Create the database first
with singlestoredb.connect(DEFAULT_SINGLESTORE_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
with conn.cursor() as cursor:
cursor.execute(f"CREATE DATABASE {database}")

try:
conn_string = f"{DEFAULT_SINGLESTORE_URI}/{database}"
conn_string = f"{DEFAULT_URI_WITHOUT_DB}/{database}"
async with AsyncSingleStoreSaver.from_conn_string(conn_string) as saver:
await saver.setup()

Expand All @@ -339,7 +339,7 @@ async def test_from_conn_string() -> None:
assert retrieved.metadata["test"] == "data"
finally:
# Clean up
with singlestoredb.connect(DEFAULT_SINGLESTORE_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
with conn.cursor() as cursor:
cursor.execute(f"DROP DATABASE {database}")

Expand Down
8 changes: 4 additions & 4 deletions tests/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from langgraph.checkpoint.serde.types import TASKS
from langgraph.checkpoint.singlestore import SingleStoreSaver
from tests.conftest import DEFAULT_SINGLESTORE_URI
from tests.conftest import DEFAULT_URI_WITHOUT_DB


def _exclude_keys(config: dict[str, Any]) -> dict[str, Any]:
Expand All @@ -29,12 +29,12 @@ def _base_saver():
"""Fixture for regular connection mode testing."""
database = f"test_{uuid4().hex[:16]}"
# create unique db
with singlestoredb.connect(DEFAULT_SINGLESTORE_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
with conn.cursor() as cursor:
cursor.execute(f"CREATE DATABASE {database}")
try:
with singlestoredb.connect(
f"{DEFAULT_SINGLESTORE_URI}/{database}",
f"{DEFAULT_URI_WITHOUT_DB}/{database}",
autocommit=True,
results_type="dict",
) as conn:
Expand All @@ -43,7 +43,7 @@ def _base_saver():
yield checkpointer
finally:
# drop unique db
with singlestoredb.connect(DEFAULT_SINGLESTORE_URI, autocommit=True, results_type="dict") as conn:
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
with conn.cursor() as cursor:
cursor.execute(f"DROP DATABASE {database}")

Expand Down