Skip to content

Commit 12b040e

Browse files
authored
Re-enable Graph tests (#3287)
Although the Graph module was deprecated, we want to have tests running as long as we keep the client code. Therefore re-enable the Graph tests, by adding to the docker-compose stack an older redis-stack-server image. The Graph tests run only with RESP2. Tweak the CI so that it runs against the latest RC release for now. This way we get all the features in RC release of server, so we can execute all tests, including the ones for hash field expiration. Some test failures brought to light issues with the RESP3 push message invalidation handler, align a bit the code in that area. Some more housekeeping around tests, here and there.
1 parent d288a29 commit 12b040e

File tree

9 files changed

+102
-65
lines changed

9 files changed

+102
-65
lines changed

.github/workflows/integration.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ permissions:
2424
contents: read # to fetch code (actions/checkout)
2525

2626
env:
27-
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc1
2827
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
28+
REDIS_IMAGE: redis/redis-stack-server:7.4.0-rc1
29+
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc1
2930

3031
jobs:
3132
dependency-audit:

docker-compose.yml

+14-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
redis:
88
image: ${REDIS_IMAGE:-redis:latest}
99
container_name: redis-standalone
10-
command: redis-server --enable-debug-command yes
10+
command: redis-server --enable-debug-command yes --protected-mode no
1111
ports:
1212
- 6379:6379
1313
profiles:
@@ -21,7 +21,7 @@ services:
2121
container_name: redis-replica
2222
depends_on:
2323
- redis
24-
command: redis-server --replicaof redis 6379
24+
command: redis-server --replicaof redis 6379 --protected-mode no
2525
ports:
2626
- 6380:6379
2727
profiles:
@@ -67,7 +67,7 @@ services:
6767
container_name: redis-sentinel
6868
depends_on:
6969
- redis
70-
entrypoint: "/usr/local/bin/redis-sentinel /redis.conf --port 26379"
70+
entrypoint: "redis-sentinel /redis.conf --port 26379"
7171
ports:
7272
- 26379:26379
7373
volumes:
@@ -81,7 +81,7 @@ services:
8181
container_name: redis-sentinel2
8282
depends_on:
8383
- redis
84-
entrypoint: "/usr/local/bin/redis-sentinel /redis.conf --port 26380"
84+
entrypoint: "redis-sentinel /redis.conf --port 26380"
8585
ports:
8686
- 26380:26380
8787
volumes:
@@ -95,7 +95,7 @@ services:
9595
container_name: redis-sentinel3
9696
depends_on:
9797
- redis
98-
entrypoint: "/usr/local/bin/redis-sentinel /redis.conf --port 26381"
98+
entrypoint: "redis-sentinel /redis.conf --port 26381"
9999
ports:
100100
- 26381:26381
101101
volumes:
@@ -114,3 +114,12 @@ services:
114114
profiles:
115115
- standalone
116116
- all
117+
118+
redis-stack-graph:
119+
image: redis/redis-stack-server:6.2.6-v15
120+
container_name: redis-stack-graph
121+
ports:
122+
- 6480:6379
123+
profiles:
124+
- standalone
125+
- all

dockers/create_cluster.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ dir /nodes/$PORT
3131
EOF
3232

3333
set -x
34-
/usr/local/bin/redis-server /nodes/$PORT/redis.conf
34+
redis-server /nodes/$PORT/redis.conf
3535
sleep 1
3636
if [ $? -ne 0 ]; then
3737
echo "Redis failed to start, exiting."
@@ -40,8 +40,8 @@ EOF
4040
echo 127.0.0.1:$PORT >> /nodes/nodemap
4141
done
4242
if [ -z "${REDIS_PASSWORD}" ]; then
43-
echo yes | /usr/local/bin/redis-cli --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1
43+
echo yes | redis-cli --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1
4444
else
45-
echo yes | /usr/local/bin/redis-cli -a ${REDIS_PASSWORD} --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1
45+
echo yes | redis-cli -a ${REDIS_PASSWORD} --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1
4646
fi
4747
tail -f /redis.log

redis/_parsers/resp3.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class _RESP3Parser(_RESPBase):
1515
def __init__(self, socket_read_size):
1616
super().__init__(socket_read_size)
1717
self.pubsub_push_handler_func = self.handle_pubsub_push_response
18-
self.invalidations_push_handler_func = None
18+
self.invalidation_push_handler_func = None
1919

2020
def handle_pubsub_push_response(self, response):
2121
logger = getLogger("push_response")
@@ -129,7 +129,10 @@ def _read_response(self, disable_decoding=False, push_request=False):
129129

130130
def handle_push_response(self, response, disable_decoding, push_request):
131131
if response[0] in _INVALIDATION_MESSAGE:
132-
res = self.invalidation_push_handler_func(response)
132+
if self.invalidation_push_handler_func:
133+
res = self.invalidation_push_handler_func(response)
134+
else:
135+
res = None
133136
else:
134137
res = self.pubsub_push_handler_func(response)
135138
if not push_request:
@@ -142,15 +145,15 @@ def handle_push_response(self, response, disable_decoding, push_request):
142145
def set_pubsub_push_handler(self, pubsub_push_handler_func):
143146
self.pubsub_push_handler_func = pubsub_push_handler_func
144147

145-
def set_invalidation_push_handler(self, invalidations_push_handler_func):
146-
self.invalidation_push_handler_func = invalidations_push_handler_func
148+
def set_invalidation_push_handler(self, invalidation_push_handler_func):
149+
self.invalidation_push_handler_func = invalidation_push_handler_func
147150

148151

149152
class _AsyncRESP3Parser(_AsyncRESPBase):
150153
def __init__(self, socket_read_size):
151154
super().__init__(socket_read_size)
152155
self.pubsub_push_handler_func = self.handle_pubsub_push_response
153-
self.invalidations_push_handler_func = None
156+
self.invalidation_push_handler_func = None
154157

155158
def handle_pubsub_push_response(self, response):
156159
logger = getLogger("push_response")
@@ -273,7 +276,10 @@ async def _read_response(
273276

274277
async def handle_push_response(self, response, disable_decoding, push_request):
275278
if response[0] in _INVALIDATION_MESSAGE:
276-
res = self.invalidation_push_handler_func(response)
279+
if self.invalidation_push_handler_func:
280+
res = self.invalidation_push_handler_func(response)
281+
else:
282+
res = None
277283
else:
278284
res = self.pubsub_push_handler_func(response)
279285
if not push_request:
@@ -286,5 +292,5 @@ async def handle_push_response(self, response, disable_decoding, push_request):
286292
def set_pubsub_push_handler(self, pubsub_push_handler_func):
287293
self.pubsub_push_handler_func = pubsub_push_handler_func
288294

289-
def set_invalidation_push_handler(self, invalidations_push_handler_func):
290-
self.invalidation_push_handler_func = invalidations_push_handler_func
295+
def set_invalidation_push_handler(self, invalidation_push_handler_func):
296+
self.invalidation_push_handler_func = invalidation_push_handler_func

tests/conftest.py

+7
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ def _get_info(redis_url):
135135
def pytest_sessionstart(session):
136136
# during test discovery, e.g. with VS Code, we may not
137137
# have a server running.
138+
protocol = session.config.getoption("--protocol")
139+
REDIS_INFO["resp_version"] = int(protocol) if protocol else None
138140
redis_url = session.config.getoption("--redis-url")
139141
try:
140142
info = _get_info(redis_url)
@@ -279,6 +281,11 @@ def skip_if_cryptography() -> _TestDecorator:
279281
return pytest.mark.skipif(False, reason="No cryptography dependency")
280282

281283

284+
def skip_if_resp_version(resp_version) -> _TestDecorator:
285+
check = REDIS_INFO.get("resp_version", None) == resp_version
286+
return pytest.mark.skipif(check, reason=f"RESP version required != {resp_version}")
287+
288+
282289
def _get_client(
283290
cls, request, single_connection_client=True, flushdb=True, from_url=None, **kwargs
284291
):

tests/test_asyncio/test_graph.py

+22-21
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,24 @@
44
from redis.commands.graph import Edge, Node, Path
55
from redis.commands.graph.execution_plan import Operation
66
from redis.exceptions import ResponseError
7-
from tests.conftest import skip_if_redis_enterprise
7+
from tests.conftest import skip_if_redis_enterprise, skip_if_resp_version
88

99

1010
@pytest_asyncio.fixture()
1111
async def decoded_r(create_redis, stack_url):
12-
return await create_redis(decode_responses=True, url=stack_url)
12+
return await create_redis(decode_responses=True, url="redis://localhost:6480")
1313

1414

1515
@pytest.mark.redismod
16+
@skip_if_resp_version(3)
1617
async def test_bulk(decoded_r):
1718
with pytest.raises(NotImplementedError):
1819
await decoded_r.graph().bulk()
1920
await decoded_r.graph().bulk(foo="bar!")
2021

2122

2223
@pytest.mark.redismod
23-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
24+
@skip_if_resp_version(3)
2425
async def test_graph_creation(decoded_r: redis.Redis):
2526
graph = decoded_r.graph()
2627

@@ -66,7 +67,7 @@ async def test_graph_creation(decoded_r: redis.Redis):
6667

6768

6869
@pytest.mark.redismod
69-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
70+
@skip_if_resp_version(3)
7071
async def test_array_functions(decoded_r: redis.Redis):
7172
graph = decoded_r.graph()
7273

@@ -90,7 +91,7 @@ async def test_array_functions(decoded_r: redis.Redis):
9091

9192

9293
@pytest.mark.redismod
93-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
94+
@skip_if_resp_version(3)
9495
async def test_path(decoded_r: redis.Redis):
9596
node0 = Node(node_id=0, label="L1")
9697
node1 = Node(node_id=1, label="L1")
@@ -111,7 +112,7 @@ async def test_path(decoded_r: redis.Redis):
111112

112113

113114
@pytest.mark.redismod
114-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
115+
@skip_if_resp_version(3)
115116
async def test_param(decoded_r: redis.Redis):
116117
params = [1, 2.3, "str", True, False, None, [0, 1, 2]]
117118
query = "RETURN $param"
@@ -122,7 +123,7 @@ async def test_param(decoded_r: redis.Redis):
122123

123124

124125
@pytest.mark.redismod
125-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
126+
@skip_if_resp_version(3)
126127
async def test_map(decoded_r: redis.Redis):
127128
query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}"
128129

@@ -140,7 +141,7 @@ async def test_map(decoded_r: redis.Redis):
140141

141142

142143
@pytest.mark.redismod
143-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
144+
@skip_if_resp_version(3)
144145
async def test_point(decoded_r: redis.Redis):
145146
query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})"
146147
expected_lat = 32.070794860
@@ -158,7 +159,7 @@ async def test_point(decoded_r: redis.Redis):
158159

159160

160161
@pytest.mark.redismod
161-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
162+
@skip_if_resp_version(3)
162163
async def test_index_response(decoded_r: redis.Redis):
163164
result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)")
164165
assert 1 == result_set.indices_created
@@ -174,7 +175,7 @@ async def test_index_response(decoded_r: redis.Redis):
174175

175176

176177
@pytest.mark.redismod
177-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
178+
@skip_if_resp_version(3)
178179
async def test_stringify_query_result(decoded_r: redis.Redis):
179180
graph = decoded_r.graph()
180181

@@ -229,7 +230,7 @@ async def test_stringify_query_result(decoded_r: redis.Redis):
229230

230231

231232
@pytest.mark.redismod
232-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
233+
@skip_if_resp_version(3)
233234
async def test_optional_match(decoded_r: redis.Redis):
234235
# Build a graph of form (a)-[R]->(b)
235236
node0 = Node(node_id=0, label="L1", properties={"value": "a"})
@@ -255,7 +256,7 @@ async def test_optional_match(decoded_r: redis.Redis):
255256

256257

257258
@pytest.mark.redismod
258-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
259+
@skip_if_resp_version(3)
259260
async def test_cached_execution(decoded_r: redis.Redis):
260261
await decoded_r.graph().query("CREATE ()")
261262

@@ -276,7 +277,7 @@ async def test_cached_execution(decoded_r: redis.Redis):
276277

277278

278279
@pytest.mark.redismod
279-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
280+
@skip_if_resp_version(3)
280281
async def test_slowlog(decoded_r: redis.Redis):
281282
create_query = """CREATE
282283
(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}),
@@ -291,7 +292,7 @@ async def test_slowlog(decoded_r: redis.Redis):
291292

292293
@pytest.mark.xfail(strict=False)
293294
@pytest.mark.redismod
294-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
295+
@skip_if_resp_version(3)
295296
async def test_query_timeout(decoded_r: redis.Redis):
296297
# Build a sample graph with 1000 nodes.
297298
await decoded_r.graph().query("UNWIND range(0,1000) as val CREATE ({v: val})")
@@ -306,7 +307,7 @@ async def test_query_timeout(decoded_r: redis.Redis):
306307

307308

308309
@pytest.mark.redismod
309-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
310+
@skip_if_resp_version(3)
310311
async def test_read_only_query(decoded_r: redis.Redis):
311312
with pytest.raises(Exception):
312313
# Issue a write query, specifying read-only true,
@@ -316,7 +317,7 @@ async def test_read_only_query(decoded_r: redis.Redis):
316317

317318

318319
@pytest.mark.redismod
319-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
320+
@skip_if_resp_version(3)
320321
async def test_profile(decoded_r: redis.Redis):
321322
q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})"""
322323
profile = (await decoded_r.graph().profile(q)).result_set
@@ -333,7 +334,7 @@ async def test_profile(decoded_r: redis.Redis):
333334

334335
@skip_if_redis_enterprise()
335336
@pytest.mark.redismod
336-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
337+
@skip_if_resp_version(3)
337338
async def test_config(decoded_r: redis.Redis):
338339
config_name = "RESULTSET_SIZE"
339340
config_value = 3
@@ -366,7 +367,7 @@ async def test_config(decoded_r: redis.Redis):
366367

367368
@pytest.mark.onlynoncluster
368369
@pytest.mark.redismod
369-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
370+
@skip_if_resp_version(3)
370371
async def test_list_keys(decoded_r: redis.Redis):
371372
result = await decoded_r.graph().list_keys()
372373
assert result == []
@@ -390,7 +391,7 @@ async def test_list_keys(decoded_r: redis.Redis):
390391

391392

392393
@pytest.mark.redismod
393-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
394+
@skip_if_resp_version(3)
394395
async def test_multi_label(decoded_r: redis.Redis):
395396
redis_graph = decoded_r.graph("g")
396397

@@ -417,7 +418,7 @@ async def test_multi_label(decoded_r: redis.Redis):
417418

418419

419420
@pytest.mark.redismod
420-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
421+
@skip_if_resp_version(3)
421422
async def test_execution_plan(decoded_r: redis.Redis):
422423
redis_graph = decoded_r.graph("execution_plan")
423424
create_query = """CREATE
@@ -437,7 +438,7 @@ async def test_execution_plan(decoded_r: redis.Redis):
437438

438439

439440
@pytest.mark.redismod
440-
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
441+
@skip_if_resp_version(3)
441442
async def test_explain(decoded_r: redis.Redis):
442443
redis_graph = decoded_r.graph("execution_plan")
443444
# graph creation / population

tests/test_commands.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -712,11 +712,15 @@ def test_client_kill_filter_by_user(self, r, request):
712712
@skip_if_redis_enterprise()
713713
@pytest.mark.onlynoncluster
714714
def test_client_kill_filter_by_maxage(self, r, request):
715-
_get_client(redis.Redis, request, flushdb=False)
715+
r2 = _get_client(redis.Redis, request, flushdb=False)
716+
name = "target-foobar"
717+
r2.client_setname(name)
716718
time.sleep(4)
717-
assert len(r.client_list()) >= 2
719+
initial_clients = [c["name"] for c in r.client_list()]
720+
assert name in initial_clients
718721
r.client_kill_filter(maxage=2)
719-
assert len(r.client_list()) == 1
722+
final_clients = [c["name"] for c in r.client_list()]
723+
assert name not in final_clients
720724

721725
@pytest.mark.onlynoncluster
722726
@skip_if_server_version_lt("2.9.50")

0 commit comments

Comments
 (0)