Skip to content

Commit 6818b74

Browse files
authored
test: Add valkey-search integration test infrastructure (#5981)
* test: Add valkey-search integration test infrastructure * fix: ignore valkey_search tests for CI
1 parent 50e1fbf commit 6818b74

File tree

9 files changed

+627
-1
lines changed

9 files changed

+627
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ cmake-build-debug
2323
.venv/
2424
fuzz/artifacts/
2525
fuzz/corpus/
26+
27+
# Valkey-search integration tests (synced from external repo)
28+
tests/dragonfly/valkey_search/integration/

tests/dragonfly/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ fakeredis[json]==2.26.2
2929
hiredis==2.4.0
3030
PyYAML>=6.0
3131
testcontainers>=3.7.1
32+
valkey==6.0.2
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Valkey-Search Integration Tests for Dragonfly
2+
3+
Integration tests from [valkey-search](https://github.com/valkey-io/valkey-search) project, adapted to run on Dragonfly without modifying the original test code.
4+
5+
## Prerequisites
6+
7+
1. Build Dragonfly
8+
9+
2. Install Python dependencies:
10+
```bash
11+
pip install -r tests/dragonfly/requirements.txt
12+
```
13+
14+
## Setup
15+
16+
1. Sync tests from valkey-search:
17+
```bash
18+
cd tests/dragonfly/valkey_search
19+
./sync-valkey-search-tests.sh
20+
```
21+
22+
2. Set environment variables:
23+
```bash
24+
export DRAGONFLY_PATH="/path/to/dragonfly/build-dbg/dragonfly"
25+
export ROOT_DIR="/path/to/dragonfly/tests/dragonfly/valkey_search"
26+
```
27+
28+
## Running Tests
29+
30+
```bash
31+
# All tests
32+
pytest tests/dragonfly/valkey_search/integration/ -v
33+
34+
# Specific test file
35+
pytest tests/dragonfly/valkey_search/integration/test_ft_create.py -v
36+
37+
# Specific test
38+
pytest tests/dragonfly/valkey_search/integration/test_ft_create.py::TestSearchFTCreateCMD::test_ft_create_fails_on_replica_cmd -v
39+
```
40+
41+
## Structure
42+
43+
```
44+
tests/dragonfly/valkey_search/
45+
__init__.py # Mock framework for valkey-search imports
46+
conftest.py # Pytest configuration
47+
util.py # Utility functions (waiters)
48+
valkey_search_test_case_dragonfly.py # Dragonfly adapter (real replicas, clusters)
49+
sync-valkey-search-tests.sh # Script to sync tests
50+
integration/ # Synced from valkey-search (not in git)
51+
```
52+
53+
## How It Works
54+
55+
1. **Infrastructure files** (committed to git) provide compatibility layer
56+
2. **Test files** (in `integration/`, not in git) are synced from valkey-search
57+
3. **Mock framework** (`__init__.py`) replaces valkey-search imports with Dragonfly equivalents
58+
4. **Adapter** (`valkey_search_test_case_dragonfly.py`) creates real Dragonfly instances with replicas
59+
5. **Original tests run unchanged** - all adaptation happens in infrastructure layer
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Valkey-search integration tests for Dragonfly
3+
4+
This module automatically adapts original valkey-search tests to run on Dragonfly
5+
by replacing valkeytestframework imports with Dragonfly equivalents.
6+
"""
7+
8+
import sys
9+
import types
10+
import os
11+
from . import util
12+
from .integration import compatibility
13+
14+
# Add current directory to path for imports
15+
current_dir = os.path.dirname(os.path.abspath(__file__))
16+
if current_dir not in sys.path:
17+
sys.path.insert(0, current_dir)
18+
19+
# Import the Dragonfly-specific test case classes
20+
exec(open(os.path.join(current_dir, "valkey_search_test_case_dragonfly.py")).read())
21+
22+
# Create a mock module for valkey_search_test_case
23+
mock_module = types.ModuleType("valkey_search_test_case")
24+
mock_module.ValkeySearchTestCaseBase = ValkeySearchTestCaseBase
25+
mock_module.ValkeySearchTestCaseDebugMode = ValkeySearchTestCaseDebugMode
26+
mock_module.ValkeySearchClusterTestCase = ValkeySearchClusterTestCase
27+
mock_module.ValkeySearchClusterTestCaseDebugMode = ValkeySearchClusterTestCaseDebugMode
28+
mock_module.Node = Node
29+
mock_module.ReplicationGroup = ReplicationGroup
30+
31+
# Replace the module in sys.modules
32+
sys.modules["valkey_search_test_case"] = mock_module
33+
34+
# Also need to provide valkeytestframework modules
35+
valkey_test_framework = types.ModuleType("valkeytestframework")
36+
37+
valkey_test_case = types.ModuleType("valkeytestframework.valkey_test_case")
38+
valkey_test_case.ValkeyTestCase = ValkeyTestCase
39+
valkey_test_case.ReplicationTestCase = ReplicationTestCase
40+
valkey_test_case.ValkeyServerHandle = ValkeyServerHandle
41+
42+
util_module = types.ModuleType("valkeytestframework.util")
43+
waiters_module = types.ModuleType("valkeytestframework.util.waiters")
44+
45+
waiters_module.wait_for_true = util.waiters.wait_for_true
46+
waiters_module.wait_for_equal = util.waiters.wait_for_equal
47+
waiters_module.wait_for_not_equal = util.waiters.wait_for_not_equal
48+
waiters_module.wait_for_condition = util.waiters.wait_for_condition
49+
util_module.waiters = waiters_module
50+
51+
# Also add direct util module access
52+
sys.modules["util"] = util_module
53+
sys.modules["util.waiters"] = waiters_module
54+
55+
conftest_module = types.ModuleType("valkeytestframework.conftest")
56+
conftest_module.resource_port_tracker = types.ModuleType("resource_port_tracker")
57+
58+
# Setup compatibility as a module in sys.modules
59+
sys.modules["compatibility"] = compatibility
60+
61+
# Also set up the submodules
62+
if hasattr(compatibility, "data_sets"):
63+
sys.modules["compatibility.data_sets"] = compatibility.data_sets
64+
65+
# Add all modules to sys.modules
66+
sys.modules["valkeytestframework"] = valkey_test_framework
67+
sys.modules["valkeytestframework.valkey_test_case"] = valkey_test_case
68+
sys.modules["valkeytestframework.util"] = util_module
69+
sys.modules["valkeytestframework.util.waiters"] = waiters_module
70+
sys.modules["valkeytestframework.conftest"] = conftest_module
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
Pytest configuration for valkey-search tests on Dragonfly
3+
"""
4+
5+
from .. import dfly_args
6+
7+
8+
# Apply dfly_args to all test classes in this directory
9+
def pytest_collection_modifyitems(items):
10+
"""Apply dfly_args decorator to all test classes"""
11+
for item in items:
12+
if item.cls and not hasattr(item.cls, "_dfly_args_applied"):
13+
# Apply the decorator to the class
14+
decorated_class = dfly_args({"proactor_threads": 4})(item.cls)
15+
item.cls._dfly_args_applied = True
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
set -e
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
INTEGRATION_DIR="$SCRIPT_DIR/integration"
6+
VALKEY_SEARCH_REPO="https://github.com/valkey-io/valkey-search.git"
7+
TEMP_DIR=$(mktemp -d)
8+
9+
echo "Syncing valkey-search tests..."
10+
11+
# Remove old integration directory
12+
rm -rf "$INTEGRATION_DIR"
13+
14+
# Clone to temp directory
15+
git clone --depth=1 "$VALKEY_SEARCH_REPO" "$TEMP_DIR" >/dev/null 2>&1
16+
17+
# Copy integration directory
18+
cp -r "$TEMP_DIR/integration" "$INTEGRATION_DIR"
19+
20+
# Cleanup
21+
rm -rf "$TEMP_DIR"
22+
23+
echo "Done. Synced $(find "$INTEGRATION_DIR" -name '*test*.py' | wc -l) test files."
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""
2+
Utility module for valkey-search tests running on Dragonfly
3+
Provides waiters functionality compatible with valkeytestframework.util.waiters
4+
"""
5+
6+
import time
7+
8+
9+
class waiters:
10+
"""Waiters utility class for test synchronization"""
11+
12+
@staticmethod
13+
def wait_for_true(func, timeout=30, interval=0.1):
14+
"""
15+
Wait for a function to return True
16+
17+
Args:
18+
func: Function to call repeatedly until it returns True
19+
timeout: Maximum time to wait in seconds (default: 30)
20+
interval: Time between checks in seconds (default: 0.1)
21+
22+
Returns:
23+
True if function returned True within timeout, False otherwise
24+
"""
25+
start_time = time.time()
26+
while time.time() - start_time < timeout:
27+
try:
28+
if func():
29+
return True
30+
except Exception:
31+
# Ignore exceptions during polling
32+
pass
33+
time.sleep(interval)
34+
return False
35+
36+
@staticmethod
37+
def wait_for_equal(func, value, timeout=30, interval=0.1):
38+
"""
39+
Wait for a function to return a specific value
40+
41+
Args:
42+
func: Function to call repeatedly
43+
value: Expected return value
44+
timeout: Maximum time to wait in seconds (default: 30)
45+
interval: Time between checks in seconds (default: 0.1)
46+
47+
Returns:
48+
True if function returned expected value within timeout, False otherwise
49+
"""
50+
start_time = time.time()
51+
while time.time() - start_time < timeout:
52+
try:
53+
if func() == value:
54+
return True
55+
except Exception:
56+
# Ignore exceptions during polling
57+
pass
58+
time.sleep(interval)
59+
return False
60+
61+
@staticmethod
62+
def wait_for_not_equal(func, value, timeout=30, interval=0.1):
63+
"""
64+
Wait for a function to return a value different from the specified one
65+
66+
Args:
67+
func: Function to call repeatedly
68+
value: Value that should NOT be returned
69+
timeout: Maximum time to wait in seconds (default: 30)
70+
interval: Time between checks in seconds (default: 0.1)
71+
72+
Returns:
73+
True if function returned different value within timeout, False otherwise
74+
"""
75+
start_time = time.time()
76+
while time.time() - start_time < timeout:
77+
try:
78+
if func() != value:
79+
return True
80+
except Exception:
81+
# Ignore exceptions during polling
82+
pass
83+
time.sleep(interval)
84+
return False
85+
86+
@staticmethod
87+
def wait_for_condition(condition_func, timeout=30, interval=0.1):
88+
"""
89+
Wait for a condition function to return True
90+
Alias for wait_for_true for compatibility
91+
"""
92+
return waiters.wait_for_true(condition_func, timeout, interval)
93+
94+
95+
# For backward compatibility with direct import style
96+
wait_for_true = waiters.wait_for_true
97+
wait_for_equal = waiters.wait_for_equal
98+
wait_for_not_equal = waiters.wait_for_not_equal
99+
wait_for_condition = waiters.wait_for_condition

0 commit comments

Comments
 (0)