Skip to content

Commit c4b2f0a

Browse files
authored
[test:improvement] improved fixtures scopes and test performance (#2)
- improved fixture scopes - improved test performance (30s to 13s) - use unique test data across test classes
1 parent 13610eb commit c4b2f0a

File tree

3 files changed

+325
-327
lines changed

3 files changed

+325
-327
lines changed

tests/conftest.py

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,71 @@
11
from collections.abc import AsyncIterator, Iterator
2+
import uuid
3+
from typing import Any
24

35
import pytest
46
import singlestoredb
57
from singlestoredb.connection import Connection
8+
from langchain_core.runnables import RunnableConfig
9+
from langgraph.checkpoint.base import (
10+
Checkpoint,
11+
CheckpointMetadata,
12+
create_checkpoint,
13+
empty_checkpoint,
14+
)
615

716
DEFAULT_URI_WITHOUT_DB = "root:test@127.0.0.1:33071"
8-
DEFAULT_URI_WITH_DB = "root:test@127.0.0.1:33071/test_db"
17+
TEST_DB_NAME = f"test_db_{uuid.uuid4().hex[:8]}"
18+
DEFAULT_URI_WITH_DB = f"root:test@127.0.0.1:33071/{TEST_DB_NAME}"
919

1020

11-
@pytest.fixture(scope="function")
21+
@pytest.fixture(scope="session", autouse=True)
22+
def setup_test_database():
23+
"""Create test database once for the entire test session."""
24+
# Create unique test database
25+
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
26+
with conn.cursor() as cursor:
27+
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {TEST_DB_NAME}")
28+
29+
yield
30+
31+
# Clean up test database after all tests
32+
with singlestoredb.connect(DEFAULT_URI_WITHOUT_DB, autocommit=True, results_type="dict") as conn:
33+
with conn.cursor() as cursor:
34+
cursor.execute(f"DROP DATABASE IF EXISTS {TEST_DB_NAME}")
35+
36+
37+
@pytest.fixture(scope="class")
1238
def conn() -> Iterator[Connection]:
13-
"""Sync connection fixture for SingleStore."""
39+
"""Class-scoped sync connection fixture for SingleStore."""
1440
with singlestoredb.connect(DEFAULT_URI_WITH_DB, autocommit=True, results_type="dict") as conn:
1541
yield conn
1642

1743

18-
@pytest.fixture(scope="function")
44+
@pytest.fixture(scope="class")
1945
async def aconn() -> AsyncIterator[Connection]:
20-
"""Async connection fixture for SingleStore."""
21-
async with singlestoredb.connect(DEFAULT_URI_WITH_DB, autocommit=True, results_type="dict") as conn:
46+
"""Class-scoped async connection fixture for SingleStore."""
47+
# SingleStore doesn't support async context managers, so we use sync connection
48+
with singlestoredb.connect(DEFAULT_URI_WITH_DB, autocommit=True, results_type="dict") as conn:
2249
yield conn
2350

2451

25-
@pytest.fixture(scope="function", autouse=True)
52+
@pytest.fixture(scope="class", autouse=True)
2653
def clear_test_db(conn: Connection) -> None:
27-
"""Delete all tables before each test."""
54+
"""Delete all tables before each test class."""
55+
try:
56+
with conn.cursor() as cursor:
57+
cursor.execute("DELETE FROM checkpoints")
58+
cursor.execute("DELETE FROM checkpoint_blobs")
59+
cursor.execute("DELETE FROM checkpoint_writes")
60+
cursor.execute("DELETE FROM checkpoint_migrations")
61+
except Exception:
62+
# Tables might not exist yet
63+
pass
64+
65+
66+
@pytest.fixture(scope="function", autouse=True)
67+
def clear_test_data(conn: Connection) -> None:
68+
"""Clear test data before each test to ensure isolation."""
2869
try:
2970
with conn.cursor() as cursor:
3071
cursor.execute("DELETE FROM checkpoints")
@@ -34,3 +75,88 @@ def clear_test_db(conn: Connection) -> None:
3475
except Exception:
3576
# Tables might not exist yet
3677
pass
78+
79+
80+
@pytest.fixture(scope="class")
81+
def sync_saver(conn: Connection):
82+
"""Class-scoped sync saver fixture."""
83+
from langgraph.checkpoint.singlestore import SingleStoreSaver
84+
85+
saver = SingleStoreSaver(conn)
86+
try:
87+
saver.setup()
88+
except Exception as e:
89+
# Ignore duplicate index errors since we're reusing the database
90+
if "Duplicate key name" not in str(e):
91+
raise
92+
return saver
93+
94+
95+
@pytest.fixture(scope="class")
96+
async def async_saver(aconn: Connection):
97+
"""Class-scoped async saver fixture."""
98+
from langgraph.checkpoint.singlestore.aio import AsyncSingleStoreSaver
99+
100+
saver = AsyncSingleStoreSaver(aconn)
101+
try:
102+
await saver.setup()
103+
except Exception as e:
104+
# Ignore duplicate index errors since we're reusing the database
105+
if "Duplicate key name" not in str(e):
106+
raise
107+
return saver
108+
109+
110+
@pytest.fixture(scope="function")
111+
def test_data():
112+
"""Function-scoped fixture providing test data for checkpoint tests."""
113+
import uuid
114+
115+
# Generate unique identifiers to prevent test conflicts
116+
unique_id = uuid.uuid4().hex[:8]
117+
118+
config_1: RunnableConfig = {
119+
"configurable": {
120+
"thread_id": f"thread-1-{unique_id}",
121+
"checkpoint_id": "1",
122+
"checkpoint_ns": "",
123+
}
124+
}
125+
config_2: RunnableConfig = {
126+
"configurable": {
127+
"thread_id": f"thread-2-{unique_id}",
128+
"checkpoint_id": "2",
129+
"checkpoint_ns": "",
130+
}
131+
}
132+
config_3: RunnableConfig = {
133+
"configurable": {
134+
"thread_id": f"thread-2-{unique_id}",
135+
"checkpoint_id": "2-inner",
136+
"checkpoint_ns": "inner",
137+
}
138+
}
139+
140+
chkpnt_1: Checkpoint = empty_checkpoint()
141+
chkpnt_2: Checkpoint = create_checkpoint(chkpnt_1, {}, 1)
142+
chkpnt_3: Checkpoint = empty_checkpoint()
143+
144+
metadata_1: CheckpointMetadata = {
145+
"source": "input",
146+
"step": 2,
147+
"writes": {},
148+
"score": 1,
149+
}
150+
metadata_2: CheckpointMetadata = {
151+
"source": "loop",
152+
"step": 1,
153+
"writes": {"foo": "bar"},
154+
"score": None,
155+
}
156+
metadata_3: CheckpointMetadata = {}
157+
158+
return {
159+
"configs": [config_1, config_2, config_3],
160+
"checkpoints": [chkpnt_1, chkpnt_2, chkpnt_3],
161+
"metadata": [metadata_1, metadata_2, metadata_3],
162+
}

0 commit comments

Comments
 (0)