0
# Key-Value Store Testing
1
2
Generic testing suites for key-value store implementations with comprehensive CRUD operations, bulk operations, and both synchronous and asynchronous support. These tests ensure that key-value stores meet LangChain's storage interface requirements.
3
4
## Capabilities
5
6
### Synchronous Key-Value Store Testing
7
8
Comprehensive testing suite for synchronous key-value store implementations.
9
10
```python { .api }
11
from typing import Generic, TypeVar, List
12
from langchain_tests.integration_tests import BaseStoreSyncTests
13
14
V = TypeVar('V')
15
16
class BaseStoreSyncTests(BaseStandardTests, Generic[V]):
17
"""Synchronous key-value store testing suite."""
18
19
# Required fixtures
20
@pytest.fixture
21
def kv_store(self):
22
"""Empty key-value store instance for testing. Must be implemented by test class."""
23
24
@pytest.fixture
25
def three_values(self) -> List[V]:
26
"""Three example values for testing. Must be implemented by test class."""
27
28
# Fixture validation
29
def test_three_values(self) -> None:
30
"""Validate that the three_values fixture provides correct test data."""
31
32
# Store state tests
33
def test_kv_store_is_empty(self) -> None:
34
"""Verify that the key-value store starts empty."""
35
36
def test_store_still_empty(self) -> None:
37
"""Verify that the store is properly cleaned up after tests."""
38
39
# Basic CRUD operations
40
def test_set_and_get_values(self) -> None:
41
"""Test setting and getting values from the store."""
42
43
def test_delete_values(self) -> None:
44
"""Test deleting individual values from the store."""
45
46
def test_delete_bulk_values(self) -> None:
47
"""Test bulk deletion of multiple values."""
48
49
def test_delete_missing_keys(self) -> None:
50
"""Test deletion behavior when keys don't exist."""
51
52
# Idempotency and consistency tests
53
def test_set_values_is_idempotent(self) -> None:
54
"""Test that setting the same key multiple times is idempotent."""
55
56
def test_get_can_get_same_value(self) -> None:
57
"""Test that getting the same key returns consistent values."""
58
59
def test_overwrite_values_by_key(self) -> None:
60
"""Test overwriting existing values with new ones."""
61
62
# Key iteration
63
def test_yield_keys(self) -> None:
64
"""Test iterating over all keys in the store."""
65
```
66
67
#### Usage Example
68
69
```python
70
from typing import List
71
import pytest
72
from langchain_tests.integration_tests import BaseStoreSyncTests
73
from my_integration import MyKeyValueStore
74
75
class TestMyKeyValueStore(BaseStoreSyncTests[str]):
76
@pytest.fixture
77
def kv_store(self):
78
# Create a fresh store instance for each test
79
store = MyKeyValueStore(
80
connection_url="redis://localhost:6379/1",
81
namespace="test_kv"
82
)
83
yield store
84
# Cleanup after test
85
store.clear()
86
87
@pytest.fixture
88
def three_values(self) -> List[str]:
89
return ["value1", "value2", "value3"]
90
```
91
92
### Asynchronous Key-Value Store Testing
93
94
Comprehensive testing suite for asynchronous key-value store implementations.
95
96
```python { .api }
97
from typing import Generic, TypeVar, List
98
from langchain_tests.integration_tests import BaseStoreAsyncTests
99
100
V = TypeVar('V')
101
102
class BaseStoreAsyncTests(BaseStandardTests, Generic[V]):
103
"""Asynchronous key-value store testing suite."""
104
105
# Required fixtures
106
@pytest.fixture
107
async def kv_store(self):
108
"""Empty async key-value store instance for testing. Must be implemented by test class."""
109
110
@pytest.fixture
111
def three_values(self) -> List[V]:
112
"""Three example values for testing. Must be implemented by test class."""
113
114
# Async fixture validation
115
async def test_three_values(self) -> None:
116
"""Validate that the three_values fixture provides correct test data."""
117
118
# Async store state tests
119
async def test_kv_store_is_empty(self) -> None:
120
"""Verify that the async key-value store starts empty."""
121
122
async def test_store_still_empty(self) -> None:
123
"""Verify that the async store is properly cleaned up after tests."""
124
125
# Async CRUD operations
126
async def test_set_and_get_values(self) -> None:
127
"""Test async setting and getting values from the store."""
128
129
async def test_delete_values(self) -> None:
130
"""Test async deletion of individual values from the store."""
131
132
async def test_delete_bulk_values(self) -> None:
133
"""Test async bulk deletion of multiple values."""
134
135
async def test_delete_missing_keys(self) -> None:
136
"""Test async deletion behavior when keys don't exist."""
137
138
# Async idempotency and consistency tests
139
async def test_set_values_is_idempotent(self) -> None:
140
"""Test that async setting the same key multiple times is idempotent."""
141
142
async def test_get_can_get_same_value(self) -> None:
143
"""Test that async getting the same key returns consistent values."""
144
145
async def test_overwrite_values_by_key(self) -> None:
146
"""Test async overwriting existing values with new ones."""
147
148
# Async key iteration
149
async def test_yield_keys(self) -> None:
150
"""Test async iteration over all keys in the store."""
151
```
152
153
#### Usage Example
154
155
```python
156
from typing import List, Dict, Any
157
import pytest
158
from langchain_tests.integration_tests import BaseStoreAsyncTests
159
from my_integration import MyAsyncKeyValueStore
160
161
class TestMyAsyncKeyValueStore(BaseStoreAsyncTests[Dict[str, Any]]):
162
@pytest.fixture
163
async def kv_store(self):
164
# Create a fresh async store instance for each test
165
store = await MyAsyncKeyValueStore.create(
166
connection_url="redis://localhost:6379/1",
167
namespace="test_async_kv"
168
)
169
yield store
170
# Cleanup after test
171
await store.clear()
172
await store.close()
173
174
@pytest.fixture
175
def three_values(self) -> List[Dict[str, Any]]:
176
return [
177
{"id": 1, "name": "Alice", "role": "admin"},
178
{"id": 2, "name": "Bob", "role": "user"},
179
{"id": 3, "name": "Charlie", "role": "guest"}
180
]
181
```
182
183
## Generic Type Support
184
185
The key-value store testing framework supports generic types, allowing you to test stores with any value type:
186
187
### String Values
188
189
```python
190
class TestStringStore(BaseStoreSyncTests[str]):
191
@pytest.fixture
192
def three_values(self) -> List[str]:
193
return ["hello", "world", "test"]
194
```
195
196
### JSON/Dictionary Values
197
198
```python
199
class TestJSONStore(BaseStoreSyncTests[Dict[str, Any]]):
200
@pytest.fixture
201
def three_values(self) -> List[Dict[str, Any]]:
202
return [
203
{"key": "value1", "count": 1},
204
{"key": "value2", "count": 2},
205
{"key": "value3", "count": 3}
206
]
207
```
208
209
### Custom Object Values
210
211
```python
212
from dataclasses import dataclass
213
214
@dataclass
215
class CustomObject:
216
name: str
217
value: int
218
tags: List[str]
219
220
class TestCustomObjectStore(BaseStoreSyncTests[CustomObject]):
221
@pytest.fixture
222
def three_values(self) -> List[CustomObject]:
223
return [
224
CustomObject("obj1", 100, ["tag1", "tag2"]),
225
CustomObject("obj2", 200, ["tag2", "tag3"]),
226
CustomObject("obj3", 300, ["tag1", "tag3"])
227
]
228
```
229
230
## Key Generation Patterns
231
232
Key-value store implementations typically use string keys. The testing framework validates various key patterns:
233
234
### Simple String Keys
235
236
```python
237
def test_simple_string_keys(self):
238
"""Test with simple alphanumeric keys."""
239
keys = ["key1", "key2", "key3"]
240
```
241
242
### UUID Keys
243
244
```python
245
import uuid
246
247
def test_uuid_keys(self):
248
"""Test with UUID-based keys."""
249
keys = [str(uuid.uuid4()) for _ in range(3)]
250
```
251
252
### Hierarchical Keys
253
254
```python
255
def test_hierarchical_keys(self):
256
"""Test with hierarchical key structures."""
257
keys = ["user:123:profile", "user:123:settings", "user:456:profile"]
258
```
259
260
## Bulk Operations
261
262
The testing framework validates bulk operations for performance:
263
264
### Bulk Set Operations
265
266
```python { .api }
267
def test_bulk_set_operations(self) -> None:
268
"""Test setting multiple key-value pairs in bulk."""
269
270
def test_bulk_get_operations(self) -> None:
271
"""Test getting multiple values in bulk."""
272
273
def test_bulk_delete_operations(self) -> None:
274
"""Test deleting multiple keys in bulk."""
275
```
276
277
## Error Handling
278
279
Key-value store tests verify proper error handling:
280
281
### Connection Errors
282
283
```python { .api }
284
def test_connection_error_handling(self) -> None:
285
"""Test behavior when store backend is unavailable."""
286
287
def test_timeout_handling(self) -> None:
288
"""Test handling of operation timeouts."""
289
```
290
291
### Serialization Errors
292
293
```python { .api }
294
def test_serialization_error_handling(self) -> None:
295
"""Test handling of values that cannot be serialized."""
296
297
def test_deserialization_error_handling(self) -> None:
298
"""Test handling of corrupted data during retrieval."""
299
```
300
301
### Key Validation
302
303
```python { .api }
304
def test_invalid_key_handling(self) -> None:
305
"""Test handling of invalid key formats."""
306
307
def test_empty_key_handling(self) -> None:
308
"""Test handling of empty or null keys."""
309
```
310
311
## Performance Testing
312
313
Key-value store tests include performance benchmarks:
314
315
### Latency Tests
316
317
```python { .api }
318
def test_get_latency(self) -> None:
319
"""Benchmark get operation latency."""
320
321
def test_set_latency(self) -> None:
322
"""Benchmark set operation latency."""
323
324
def test_delete_latency(self) -> None:
325
"""Benchmark delete operation latency."""
326
```
327
328
### Throughput Tests
329
330
```python { .api }
331
def test_bulk_operation_throughput(self) -> None:
332
"""Benchmark bulk operation throughput."""
333
334
def test_concurrent_access_performance(self) -> None:
335
"""Test performance under concurrent access."""
336
```
337
338
## Memory Management
339
340
Tests for memory-efficient implementations:
341
342
```python { .api }
343
def test_memory_usage_patterns(self) -> None:
344
"""Test memory usage with large datasets."""
345
346
def test_garbage_collection_behavior(self) -> None:
347
"""Test proper cleanup of resources."""
348
```
349
350
## Concurrency Testing
351
352
For thread-safe implementations:
353
354
### Thread Safety
355
356
```python { .api }
357
def test_concurrent_reads(self) -> None:
358
"""Test concurrent read operations."""
359
360
def test_concurrent_writes(self) -> None:
361
"""Test concurrent write operations."""
362
363
def test_read_write_consistency(self) -> None:
364
"""Test consistency between concurrent reads and writes."""
365
```
366
367
### Lock-Free Operations
368
369
```python { .api }
370
def test_lock_free_operations(self) -> None:
371
"""Test lock-free operation patterns."""
372
373
def test_atomic_operations(self) -> None:
374
"""Test atomic read-modify-write operations."""
375
```
376
377
## Persistence and Durability
378
379
For persistent store implementations:
380
381
### Data Persistence
382
383
```python { .api }
384
def test_data_persistence(self) -> None:
385
"""Test that data survives store restart."""
386
387
def test_crash_recovery(self) -> None:
388
"""Test recovery from unexpected shutdown."""
389
```
390
391
### Backup and Restore
392
393
```python { .api }
394
def test_backup_functionality(self) -> None:
395
"""Test data backup capabilities."""
396
397
def test_restore_functionality(self) -> None:
398
"""Test data restore capabilities."""
399
```
400
401
## Namespace and Isolation
402
403
For multi-tenant implementations:
404
405
### Namespace Separation
406
407
```python { .api }
408
def test_namespace_isolation(self) -> None:
409
"""Test that different namespaces are isolated."""
410
411
def test_namespace_cleanup(self) -> None:
412
"""Test proper cleanup of namespace data."""
413
```
414
415
## Configuration Testing
416
417
Validate that stores respect configuration parameters:
418
419
### Connection Parameters
420
421
```python { .api }
422
def test_connection_string_parsing(self) -> None:
423
"""Test parsing of connection strings."""
424
425
def test_connection_pool_management(self) -> None:
426
"""Test connection pool behavior."""
427
```
428
429
### Serialization Options
430
431
```python { .api }
432
def test_json_serialization(self) -> None:
433
"""Test JSON serialization of values."""
434
435
def test_pickle_serialization(self) -> None:
436
"""Test pickle serialization of Python objects."""
437
438
def test_custom_serialization(self) -> None:
439
"""Test custom serialization schemes."""
440
```
441
442
The key-value store testing framework provides comprehensive validation of storage implementations, ensuring they meet LangChain's requirements for reliability, performance, and consistency across both synchronous and asynchronous usage patterns.