From 663a0d0d4f41fe541229fb9475adceaaf60fb073 Mon Sep 17 00:00:00 2001 From: vladvildanov Date: Tue, 24 Sep 2024 09:08:02 +0300 Subject: [PATCH 1/5] Change test infrastructure to CE instead of Stack --- .github/workflows/integration.yaml | 3 +- docker-compose.yml | 23 +- redis/commands/search/commands.py | 10 +- tests/conftest.py | 29 +- tests/test_asyncio/test_bloom.py | 27 +- tests/test_asyncio/test_connection.py | 1 - tests/test_asyncio/test_graph.py | 527 --------------------- tests/test_asyncio/test_json.py | 47 +- tests/test_asyncio/test_search.py | 34 +- tests/test_asyncio/test_timeseries.py | 26 +- tests/test_bloom.py | 28 +- tests/test_cluster.py | 3 - tests/test_commands.py | 3 - tests/test_connection.py | 1 - tests/test_graph.py | 657 -------------------------- tests/test_graph_utils/test_edge.py | 4 - tests/test_graph_utils/test_node.py | 3 - tests/test_graph_utils/test_path.py | 5 - tests/test_json.py | 56 +-- tests/test_search.py | 74 +-- tests/test_timeseries.py | 45 +- 21 files changed, 40 insertions(+), 1566 deletions(-) delete mode 100644 tests/test_asyncio/test_graph.py delete mode 100644 tests/test_graph.py diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index c4da3bf3aa..9a0c155043 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -27,8 +27,7 @@ env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # this speeds up coverage with Python 3.12: https://github.com/nedbat/coveragepy/issues/1665 COVERAGE_CORE: sysmon - REDIS_IMAGE: redis:7.4-rc2 - REDIS_STACK_IMAGE: redis/redis-stack-server:latest + REDIS_IMAGE: redis:8.0-M01 jobs: dependency-audit: diff --git a/docker-compose.yml b/docker-compose.yml index c8528a7d58..32c9e8b434 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ version: "3.8" services: redis: - image: ${REDIS_IMAGE:-redis:latest} + image: redis:8.0-M01 container_name: redis-standalone command: redis-server --enable-debug-command yes --protected-mode no ports: @@ -17,7 +17,7 @@ services: - all replica: - image: ${REDIS_IMAGE:-redis:latest} + image: redis:8.0-M01 container_name: redis-replica depends_on: - redis @@ -34,7 +34,7 @@ services: context: . dockerfile: dockers/Dockerfile.cluster args: - REDIS_IMAGE: ${REDIS_IMAGE:-redis:latest} + REDIS_IMAGE: redis:8.0-M01 ports: - 16379:16379 - 16380:16380 @@ -63,7 +63,7 @@ services: - "./dockers/stunnel/keys:/etc/stunnel/keys:ro" sentinel: - image: ${REDIS_IMAGE:-redis:latest} + image: redis:8.0-M01 container_name: redis-sentinel depends_on: - redis @@ -77,7 +77,7 @@ services: - all sentinel2: - image: ${REDIS_IMAGE:-redis:latest} + image: redis:8.0-M01 container_name: redis-sentinel2 depends_on: - redis @@ -91,7 +91,7 @@ services: - all sentinel3: - image: ${REDIS_IMAGE:-redis:latest} + image: redis:8.0-M01 container_name: redis-sentinel3 depends_on: - redis @@ -104,17 +104,6 @@ services: - sentinel - all - redis-stack: - image: ${REDIS_STACK_IMAGE:-redis/redis-stack-server:edge} - container_name: redis-stack - ports: - - 6479:6379 - environment: - - "REDIS_ARGS=--enable-debug-command yes --enable-module-command yes" - profiles: - - standalone - - all - redis-stack-graph: image: redis/redis-stack-server:6.2.6-v15 container_name: redis-stack-graph diff --git a/redis/commands/search/commands.py b/redis/commands/search/commands.py index da79016ad4..f203a0260c 100644 --- a/redis/commands/search/commands.py +++ b/redis/commands/search/commands.py @@ -101,7 +101,15 @@ def _parse_profile(self, res, **kwargs): with_scores=query._with_scores, ) - return result, parse_to_dict(res[1]) + docs = {} + for i in range(0, len(res[1]), 2): + if isinstance(res[1][i + 1], list): + for item in res[1][i + 1]: + res[1][i + 1] = parse_to_dict(item) + + docs[res[1][i]] = res[1][i + 1] + + return result, docs def _parse_spellcheck(self, res, **kwargs): corrections = {} diff --git a/tests/conftest.py b/tests/conftest.py index dd78bb6a2c..a65803e346 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,6 @@ REDIS_INFO = {} default_redis_url = "redis://localhost:6379/0" default_protocol = "2" -default_redismod_url = "redis://localhost:6479" # default ssl client ignores verification for the purpose of testing default_redis_ssl_url = "rediss://localhost:6666" @@ -159,6 +158,7 @@ def pytest_sessionstart(session): arch_bits = info["arch_bits"] cluster_enabled = info["cluster_enabled"] enterprise = info["enterprise"] + modules = info["modules"] except redis.ConnectionError: # provide optimistic defaults info = {} @@ -166,23 +166,15 @@ def pytest_sessionstart(session): arch_bits = 64 cluster_enabled = False enterprise = False + modules = {} REDIS_INFO["version"] = version REDIS_INFO["arch_bits"] = arch_bits REDIS_INFO["cluster_enabled"] = cluster_enabled REDIS_INFO["enterprise"] = enterprise + REDIS_INFO["modules"] = modules # store REDIS_INFO in config so that it is available from "condition strings" session.config.REDIS_INFO = REDIS_INFO - # module info - stack_url = redis_url - if stack_url == default_redis_url: - stack_url = default_redismod_url - try: - stack_info = _get_info(stack_url) - REDIS_INFO["modules"] = stack_info["modules"] - except (KeyError, redis.exceptions.ConnectionError): - pass - if cluster_enabled: cluster_nodes = session.config.getoption("--redis-cluster-nodes") wait_for_cluster_creation(redis_url, cluster_nodes) @@ -369,21 +361,6 @@ def r(request): yield client -@pytest.fixture() -def stack_url(request): - stack_url = request.config.getoption("--redis-url", default=default_redismod_url) - if stack_url == default_redis_url: - return default_redismod_url - else: - return stack_url - - -@pytest.fixture() -def stack_r(request, stack_url): - with _get_client(redis.Redis, request, from_url=stack_url) as client: - yield client - - @pytest.fixture() def decoded_r(request): with _get_client(redis.Redis, request, decode_responses=True) as client: diff --git a/tests/test_asyncio/test_bloom.py b/tests/test_asyncio/test_bloom.py index 031e8364d7..ebf270e782 100644 --- a/tests/test_asyncio/test_bloom.py +++ b/tests/test_asyncio/test_bloom.py @@ -12,15 +12,14 @@ @pytest_asyncio.fixture() -async def decoded_r(create_redis, stack_url): - return await create_redis(decode_responses=True, url=stack_url) +async def decoded_r(create_redis): + return await create_redis(decode_responses=True) def intlist(obj): return [int(v) for v in obj] -@pytest.mark.redismod async def test_create(decoded_r: redis.Redis): """Test CREATE/RESERVE calls""" assert await decoded_r.bf().create("bloom", 0.01, 1000) @@ -36,12 +35,10 @@ async def test_create(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_create(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("tDigest", 100) -@pytest.mark.redismod async def test_bf_add(decoded_r: redis.Redis): assert await decoded_r.bf().create("bloom", 0.01, 1000) assert 1 == await decoded_r.bf().add("bloom", "foo") @@ -54,7 +51,6 @@ async def test_bf_add(decoded_r: redis.Redis): assert [1, 0] == intlist(await decoded_r.bf().mexists("bloom", "foo", "noexist")) -@pytest.mark.redismod async def test_bf_insert(decoded_r: redis.Redis): assert await decoded_r.bf().create("bloom", 0.01, 1000) assert [1] == intlist(await decoded_r.bf().insert("bloom", ["foo"])) @@ -85,7 +81,6 @@ async def test_bf_insert(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_bf_scandump_and_loadchunk(decoded_r: redis.Redis): # Store a filter await decoded_r.bf().create("myBloom", "0.0001", "1000") @@ -133,7 +128,6 @@ async def do_verify(): await decoded_r.bf().create("myBloom", "0.0001", "10000000") -@pytest.mark.redismod async def test_bf_info(decoded_r: redis.Redis): expansion = 4 # Store a filter @@ -165,7 +159,6 @@ async def test_bf_info(decoded_r: redis.Redis): assert True -@pytest.mark.redismod async def test_bf_card(decoded_r: redis.Redis): # return 0 if the key does not exist assert await decoded_r.bf().card("not_exist") == 0 @@ -180,7 +173,6 @@ async def test_bf_card(decoded_r: redis.Redis): await decoded_r.bf().card("setKey") -@pytest.mark.redismod async def test_cf_add_and_insert(decoded_r: redis.Redis): assert await decoded_r.cf().create("cuckoo", 1000) assert await decoded_r.cf().add("cuckoo", "filter") @@ -206,7 +198,6 @@ async def test_cf_add_and_insert(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_cf_exists_and_del(decoded_r: redis.Redis): assert await decoded_r.cf().create("cuckoo", 1000) assert await decoded_r.cf().add("cuckoo", "filter") @@ -218,7 +209,6 @@ async def test_cf_exists_and_del(decoded_r: redis.Redis): assert 0 == await decoded_r.cf().count("cuckoo", "filter") -@pytest.mark.redismod async def test_cms(decoded_r: redis.Redis): assert await decoded_r.cms().initbydim("dim", 1000, 5) assert await decoded_r.cms().initbyprob("prob", 0.01, 0.01) @@ -235,7 +225,6 @@ async def test_cms(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod async def test_cms_merge(decoded_r: redis.Redis): assert await decoded_r.cms().initbydim("A", 1000, 5) assert await decoded_r.cms().initbydim("B", 1000, 5) @@ -252,7 +241,6 @@ async def test_cms_merge(decoded_r: redis.Redis): assert [16, 15, 21] == await decoded_r.cms().query("C", "foo", "bar", "baz") -@pytest.mark.redismod async def test_topk(decoded_r: redis.Redis): # test list with empty buckets assert await decoded_r.topk().reserve("topk", 3, 50, 4, 0.9) @@ -333,7 +321,6 @@ async def test_topk(decoded_r: redis.Redis): assert 0.9 == round(float(info["decay"]), 1) -@pytest.mark.redismod async def test_topk_incrby(decoded_r: redis.Redis): await decoded_r.flushdb() assert await decoded_r.topk().reserve("topk", 3, 10, 3, 1) @@ -349,7 +336,6 @@ async def test_topk_incrby(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_reset(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("tDigest", 10) # reset on empty histogram @@ -366,7 +352,6 @@ async def test_tdigest_reset(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod async def test_tdigest_merge(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("to-tDigest", 10) assert await decoded_r.tdigest().create("from-tDigest", 10) @@ -394,7 +379,6 @@ async def test_tdigest_merge(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_min_and_max(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("tDigest", 100) # insert data-points into sketch @@ -405,7 +389,6 @@ async def test_tdigest_min_and_max(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.0", "bf") async def test_tdigest_quantile(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("tDigest", 500) @@ -434,7 +417,6 @@ async def test_tdigest_quantile(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_cdf(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("tDigest", 100) # insert data-points into sketch @@ -446,7 +428,6 @@ async def test_tdigest_cdf(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.0", "bf") async def test_tdigest_trimmed_mean(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("tDigest", 100) @@ -457,7 +438,6 @@ async def test_tdigest_trimmed_mean(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_rank(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("t-digest", 500) assert await decoded_r.tdigest().add("t-digest", list(range(0, 20))) @@ -468,7 +448,6 @@ async def test_tdigest_rank(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_revrank(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("t-digest", 500) assert await decoded_r.tdigest().add("t-digest", list(range(0, 20))) @@ -478,7 +457,6 @@ async def test_tdigest_revrank(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_byrank(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("t-digest", 500) assert await decoded_r.tdigest().add("t-digest", list(range(1, 11))) @@ -490,7 +468,6 @@ async def test_tdigest_byrank(decoded_r: redis.Redis): @pytest.mark.experimental -@pytest.mark.redismod async def test_tdigest_byrevrank(decoded_r: redis.Redis): assert await decoded_r.tdigest().create("t-digest", 500) assert await decoded_r.tdigest().add("t-digest", list(range(1, 11))) diff --git a/tests/test_asyncio/test_connection.py b/tests/test_asyncio/test_connection.py index 8f79f7d947..1e50f84a52 100644 --- a/tests/test_asyncio/test_connection.py +++ b/tests/test_asyncio/test_connection.py @@ -96,7 +96,6 @@ async def get_conn(_): @skip_if_server_version_lt("4.0.0") -@pytest.mark.redismod @pytest.mark.onlynoncluster async def test_loading_external_modules(r): def inner(): diff --git a/tests/test_asyncio/test_graph.py b/tests/test_asyncio/test_graph.py deleted file mode 100644 index 2014ea38b6..0000000000 --- a/tests/test_asyncio/test_graph.py +++ /dev/null @@ -1,527 +0,0 @@ -import pytest -import pytest_asyncio -import redis.asyncio as redis -from redis.commands.graph import Edge, Node, Path -from redis.commands.graph.execution_plan import Operation -from redis.exceptions import ResponseError -from tests.conftest import skip_if_redis_enterprise, skip_if_resp_version - - -@pytest_asyncio.fixture() -async def decoded_r(create_redis, stack_url): - return await create_redis(decode_responses=True, url="redis://localhost:6480") - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_bulk(decoded_r): - with pytest.raises(NotImplementedError): - await decoded_r.graph().bulk() - await decoded_r.graph().bulk(foo="bar!") - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_graph_creation(decoded_r: redis.Redis): - graph = decoded_r.graph() - - john = Node( - label="person", - properties={ - "name": "John Doe", - "age": 33, - "gender": "male", - "status": "single", - }, - ) - graph.add_node(john) - japan = Node(label="country", properties={"name": "Japan"}) - - graph.add_node(japan) - edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) - graph.add_edge(edge) - - await graph.commit() - - query = ( - 'MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) ' - "RETURN p, v, c" - ) - - result = await graph.query(query) - - person = result.result_set[0][0] - visit = result.result_set[0][1] - country = result.result_set[0][2] - - assert person == john - assert visit.properties == edge.properties - assert country == japan - - query = """RETURN [1, 2.3, "4", true, false, null]""" - result = await graph.query(query) - assert [1, 2.3, "4", True, False, None] == result.result_set[0][0] - - # All done, remove graph. - await graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_array_functions(decoded_r: redis.Redis): - graph = decoded_r.graph() - - query = """CREATE (p:person{name:'a',age:32, array:[0,1,2]})""" - await graph.query(query) - - query = """WITH [0,1,2] as x return x""" - result = await graph.query(query) - assert [0, 1, 2] == result.result_set[0][0] - - query = """MATCH(n) return collect(n)""" - result = await graph.query(query) - - a = Node( - node_id=0, - label="person", - properties={"name": "a", "age": 32, "array": [0, 1, 2]}, - ) - - assert [a] == result.result_set[0][0] - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_path(decoded_r: redis.Redis): - node0 = Node(node_id=0, label="L1") - node1 = Node(node_id=1, label="L1") - edge01 = Edge(node0, "R1", node1, edge_id=0, properties={"value": 1}) - - graph = decoded_r.graph() - graph.add_node(node0) - graph.add_node(node1) - graph.add_edge(edge01) - await graph.flush() - - path01 = Path.new_empty_path().add_node(node0).add_edge(edge01).add_node(node1) - expected_results = [[path01]] - - query = "MATCH p=(:L1)-[:R1]->(:L1) RETURN p ORDER BY p" - result = await graph.query(query) - assert expected_results == result.result_set - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_param(decoded_r: redis.Redis): - params = [1, 2.3, "str", True, False, None, [0, 1, 2]] - query = "RETURN $param" - for param in params: - result = await decoded_r.graph().query(query, {"param": param}) - expected_results = [[param]] - assert expected_results == result.result_set - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_map(decoded_r: redis.Redis): - query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}" - - actual = (await decoded_r.graph().query(query)).result_set[0][0] - expected = { - "a": 1, - "b": "str", - "c": None, - "d": [1, 2, 3], - "e": True, - "f": {"x": 1, "y": 2}, - } - - assert actual == expected - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_point(decoded_r: redis.Redis): - query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})" - expected_lat = 32.070794860 - expected_lon = 34.820751118 - actual = (await decoded_r.graph().query(query)).result_set[0][0] - assert abs(actual["latitude"] - expected_lat) < 0.001 - assert abs(actual["longitude"] - expected_lon) < 0.001 - - query = "RETURN point({latitude: 32, longitude: 34.0})" - expected_lat = 32 - expected_lon = 34 - actual = (await decoded_r.graph().query(query)).result_set[0][0] - assert abs(actual["latitude"] - expected_lat) < 0.001 - assert abs(actual["longitude"] - expected_lon) < 0.001 - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_index_response(decoded_r: redis.Redis): - result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)") - assert 1 == result_set.indices_created - - result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)") - assert 0 == result_set.indices_created - - result_set = await decoded_r.graph().query("DROP INDEX ON :person(age)") - assert 1 == result_set.indices_deleted - - with pytest.raises(ResponseError): - await decoded_r.graph().query("DROP INDEX ON :person(age)") - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_stringify_query_result(decoded_r: redis.Redis): - graph = decoded_r.graph() - - john = Node( - alias="a", - label="person", - properties={ - "name": "John Doe", - "age": 33, - "gender": "male", - "status": "single", - }, - ) - graph.add_node(john) - - japan = Node(alias="b", label="country", properties={"name": "Japan"}) - graph.add_node(japan) - - edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) - graph.add_edge(edge) - - assert ( - str(john) - == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa - ) - assert ( - str(edge) - == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa - + """-[:visited{purpose:"pleasure"}]->""" - + """(b:country{name:"Japan"})""" - ) - assert str(japan) == """(b:country{name:"Japan"})""" - - await graph.commit() - - query = """MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) - RETURN p, v, c""" - - result = await graph.query(query) - person = result.result_set[0][0] - visit = result.result_set[0][1] - country = result.result_set[0][2] - - assert ( - str(person) - == """(:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa - ) - assert str(visit) == """()-[:visited{purpose:"pleasure"}]->()""" - assert str(country) == """(:country{name:"Japan"})""" - - await graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_optional_match(decoded_r: redis.Redis): - # Build a graph of form (a)-[R]->(b) - node0 = Node(node_id=0, label="L1", properties={"value": "a"}) - node1 = Node(node_id=1, label="L1", properties={"value": "b"}) - - edge01 = Edge(node0, "R", node1, edge_id=0) - - graph = decoded_r.graph() - graph.add_node(node0) - graph.add_node(node1) - graph.add_edge(edge01) - await graph.flush() - - # Issue a query that collects all outgoing edges from both nodes - # (the second has none) - query = """MATCH (a) OPTIONAL MATCH (a)-[e]->(b) RETURN a, e, b ORDER BY a.value""" # noqa - expected_results = [[node0, edge01, node1], [node1, None, None]] - - result = await graph.query(query) - assert expected_results == result.result_set - - await graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_cached_execution(decoded_r: redis.Redis): - await decoded_r.graph().query("CREATE ()") - - uncached_result = await decoded_r.graph().query( - "MATCH (n) RETURN n, $param", {"param": [0]} - ) - assert uncached_result.cached_execution is False - - # loop to make sure the query is cached on each thread on server - for x in range(0, 64): - cached_result = await decoded_r.graph().query( - "MATCH (n) RETURN n, $param", {"param": [0]} - ) - assert uncached_result.result_set == cached_result.result_set - - # should be cached on all threads by now - assert cached_result.cached_execution - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_slowlog(decoded_r: redis.Redis): - create_query = """CREATE - (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), - (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), - (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" - await decoded_r.graph().query(create_query) - - results = await decoded_r.graph().slowlog() - assert results[0][1] == "GRAPH.QUERY" - assert results[0][2] == create_query - - -@pytest.mark.xfail(strict=False) -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_query_timeout(decoded_r: redis.Redis): - # Build a sample graph with 1000 nodes. - await decoded_r.graph().query("UNWIND range(0,1000) as val CREATE ({v: val})") - # Issue a long-running query with a 1-millisecond timeout. - with pytest.raises(ResponseError): - await decoded_r.graph().query("MATCH (a), (b), (c), (d) RETURN *", timeout=1) - assert False is False - - with pytest.raises(Exception): - await decoded_r.graph().query("RETURN 1", timeout="str") - assert False is False - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_read_only_query(decoded_r: redis.Redis): - with pytest.raises(Exception): - # Issue a write query, specifying read-only true, - # this call should fail. - await decoded_r.graph().query("CREATE (p:person {name:'a'})", read_only=True) - assert False is False - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_profile(decoded_r: redis.Redis): - q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})""" - profile = (await decoded_r.graph().profile(q)).result_set - assert "Create | Records produced: 3" in profile - assert "Unwind | Records produced: 3" in profile - - q = "MATCH (p:Person) WHERE p.v > 1 RETURN p" - profile = (await decoded_r.graph().profile(q)).result_set - assert "Results | Records produced: 2" in profile - assert "Project | Records produced: 2" in profile - assert "Filter | Records produced: 2" in profile - assert "Node By Label Scan | (p:Person) | Records produced: 3" in profile - - -@skip_if_redis_enterprise() -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_config(decoded_r: redis.Redis): - config_name = "RESULTSET_SIZE" - config_value = 3 - - # Set configuration - response = await decoded_r.graph().config(config_name, config_value, set=True) - assert response == "OK" - - # Make sure config been updated. - response = await decoded_r.graph().config(config_name, set=False) - expected_response = [config_name, config_value] - assert response == expected_response - - config_name = "QUERY_MEM_CAPACITY" - config_value = 1 << 20 # 1MB - - # Set configuration - response = await decoded_r.graph().config(config_name, config_value, set=True) - assert response == "OK" - - # Make sure config been updated. - response = await decoded_r.graph().config(config_name, set=False) - expected_response = [config_name, config_value] - assert response == expected_response - - # reset to default - await decoded_r.graph().config("QUERY_MEM_CAPACITY", 0, set=True) - await decoded_r.graph().config("RESULTSET_SIZE", -100, set=True) - - -@pytest.mark.onlynoncluster -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_list_keys(decoded_r: redis.Redis): - result = await decoded_r.graph().list_keys() - assert result == [] - - await decoded_r.graph("G").query("CREATE (n)") - result = await decoded_r.graph().list_keys() - assert result == ["G"] - - await decoded_r.graph("X").query("CREATE (m)") - result = await decoded_r.graph().list_keys() - assert result == ["G", "X"] - - await decoded_r.delete("G") - await decoded_r.rename("X", "Z") - result = await decoded_r.graph().list_keys() - assert result == ["Z"] - - await decoded_r.delete("Z") - result = await decoded_r.graph().list_keys() - assert result == [] - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_multi_label(decoded_r: redis.Redis): - redis_graph = decoded_r.graph("g") - - node = Node(label=["l", "ll"]) - redis_graph.add_node(node) - await redis_graph.commit() - - query = "MATCH (n) RETURN n" - result = await redis_graph.query(query) - result_node = result.result_set[0][0] - assert result_node == node - - try: - Node(label=1) - assert False - except AssertionError: - assert True - - try: - Node(label=["l", 1]) - assert False - except AssertionError: - assert True - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_execution_plan(decoded_r: redis.Redis): - redis_graph = decoded_r.graph("execution_plan") - create_query = """CREATE - (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), - (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), - (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" - await redis_graph.query(create_query) - - result = await redis_graph.execution_plan( - "MATCH (r:Rider)-[:rides]->(t:Team) WHERE t.name = $name RETURN r.name, t.name, $params", # noqa - {"name": "Yehuda"}, - ) - expected = "Results\n Project\n Conditional Traverse | (t)->(r:Rider)\n Filter\n Node By Label Scan | (t:Team)" # noqa - assert result == expected - - await redis_graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -async def test_explain(decoded_r: redis.Redis): - redis_graph = decoded_r.graph("execution_plan") - # graph creation / population - create_query = """CREATE -(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), -(:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), -(:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" - await redis_graph.query(create_query) - - result = await redis_graph.explain( - """MATCH (r:Rider)-[:rides]->(t:Team) -WHERE t.name = $name -RETURN r.name, t.name -UNION -MATCH (r:Rider)-[:rides]->(t:Team) -WHERE t.name = $name -RETURN r.name, t.name""", - {"name": "Yamaha"}, - ) - expected = """\ -Results -Distinct - Join - Project - Conditional Traverse | (t)->(r:Rider) - Filter - Node By Label Scan | (t:Team) - Project - Conditional Traverse | (t)->(r:Rider) - Filter - Node By Label Scan | (t:Team)""" - assert str(result).replace(" ", "").replace("\n", "") == expected.replace( - " ", "" - ).replace("\n", "") - - expected = Operation("Results").append_child( - Operation("Distinct").append_child( - Operation("Join") - .append_child( - Operation("Project").append_child( - Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( - Operation("Filter").append_child( - Operation("Node By Label Scan", "(t:Team)") - ) - ) - ) - ) - .append_child( - Operation("Project").append_child( - Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( - Operation("Filter").append_child( - Operation("Node By Label Scan", "(t:Team)") - ) - ) - ) - ) - ) - ) - - assert result.structured_plan == expected - - result = await redis_graph.explain( - """MATCH (r:Rider), (t:Team) - RETURN r.name, t.name""" - ) - expected = """\ -Results -Project - Cartesian Product - Node By Label Scan | (r:Rider) - Node By Label Scan | (t:Team)""" - assert str(result).replace(" ", "").replace("\n", "") == expected.replace( - " ", "" - ).replace("\n", "") - - expected = Operation("Results").append_child( - Operation("Project").append_child( - Operation("Cartesian Product") - .append_child(Operation("Node By Label Scan")) - .append_child(Operation("Node By Label Scan")) - ) - ) - - assert result.structured_plan == expected - - await redis_graph.delete() diff --git a/tests/test_asyncio/test_json.py b/tests/test_asyncio/test_json.py index 852fd4aaa6..bd33b380d1 100644 --- a/tests/test_asyncio/test_json.py +++ b/tests/test_asyncio/test_json.py @@ -7,11 +7,10 @@ @pytest_asyncio.fixture() -async def decoded_r(create_redis, stack_url): - return await create_redis(decode_responses=True, url=stack_url) +async def decoded_r(create_redis): + return await create_redis(decode_responses=True) -@pytest.mark.redismod async def test_json_setbinarykey(decoded_r: redis.Redis): d = {"hello": "world", b"some": "value"} with pytest.raises(TypeError): @@ -19,7 +18,6 @@ async def test_json_setbinarykey(decoded_r: redis.Redis): assert await decoded_r.json().set("somekey", Path.root_path(), d, decode_keys=True) -@pytest.mark.redismod async def test_json_setgetdeleteforget(decoded_r: redis.Redis): assert await decoded_r.json().set("foo", Path.root_path(), "bar") assert await decoded_r.json().get("foo") == "bar" @@ -29,13 +27,11 @@ async def test_json_setgetdeleteforget(decoded_r: redis.Redis): assert await decoded_r.exists("foo") == 0 -@pytest.mark.redismod async def test_jsonget(decoded_r: redis.Redis): await decoded_r.json().set("foo", Path.root_path(), "bar") assert await decoded_r.json().get("foo") == "bar" -@pytest.mark.redismod async def test_json_get_jset(decoded_r: redis.Redis): assert await decoded_r.json().set("foo", Path.root_path(), "bar") assert await decoded_r.json().get("foo") == "bar" @@ -44,7 +40,6 @@ async def test_json_get_jset(decoded_r: redis.Redis): assert await decoded_r.exists("foo") == 0 -@pytest.mark.redismod async def test_nonascii_setgetdelete(decoded_r: redis.Redis): assert await decoded_r.json().set("notascii", Path.root_path(), "hyvää-élève") assert await decoded_r.json().get("notascii", no_escape=True) == "hyvää-élève" @@ -52,7 +47,6 @@ async def test_nonascii_setgetdelete(decoded_r: redis.Redis): assert await decoded_r.exists("notascii") == 0 -@pytest.mark.redismod @skip_ifmodversion_lt("2.6.0", "ReJSON") async def test_json_merge(decoded_r: redis.Redis): # Test with root path $ @@ -87,7 +81,6 @@ async def test_json_merge(decoded_r: redis.Redis): } -@pytest.mark.redismod async def test_jsonsetexistentialmodifiersshouldsucceed(decoded_r: redis.Redis): obj = {"foo": "bar"} assert await decoded_r.json().set("obj", Path.root_path(), obj) @@ -105,7 +98,6 @@ async def test_jsonsetexistentialmodifiersshouldsucceed(decoded_r: redis.Redis): await decoded_r.json().set("obj", Path("foo"), "baz", nx=True, xx=True) -@pytest.mark.redismod async def test_mgetshouldsucceed(decoded_r: redis.Redis): await decoded_r.json().set("1", Path.root_path(), 1) await decoded_r.json().set("2", Path.root_path(), 2) @@ -115,7 +107,6 @@ async def test_mgetshouldsucceed(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("2.6.0", "ReJSON") async def test_mset(decoded_r: redis.Redis): await decoded_r.json().mset( @@ -126,7 +117,6 @@ async def test_mset(decoded_r: redis.Redis): assert await decoded_r.json().mget(["1", "2"], Path.root_path()) == [1, 2] -@pytest.mark.redismod @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release async def test_clear(decoded_r: redis.Redis): await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) @@ -134,7 +124,6 @@ async def test_clear(decoded_r: redis.Redis): assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], []) -@pytest.mark.redismod async def test_type(decoded_r: redis.Redis): await decoded_r.json().set("1", Path.root_path(), 1) assert_resp_response( @@ -148,7 +137,6 @@ async def test_type(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_numincrby(decoded_r): await decoded_r.json().set("num", Path.root_path(), 1) assert_resp_response( @@ -160,7 +148,6 @@ async def test_numincrby(decoded_r): assert_resp_response(decoded_r, res, 1.25, [1.25]) -@pytest.mark.redismod async def test_nummultby(decoded_r: redis.Redis): await decoded_r.json().set("num", Path.root_path(), 1) @@ -173,7 +160,6 @@ async def test_nummultby(decoded_r: redis.Redis): assert_resp_response(decoded_r, res, 2.5, [2.5]) -@pytest.mark.redismod @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release async def test_toggle(decoded_r: redis.Redis): await decoded_r.json().set("bool", Path.root_path(), False) @@ -185,14 +171,12 @@ async def test_toggle(decoded_r: redis.Redis): await decoded_r.json().toggle("num", Path.root_path()) -@pytest.mark.redismod async def test_strappend(decoded_r: redis.Redis): await decoded_r.json().set("jsonkey", Path.root_path(), "foo") assert 6 == await decoded_r.json().strappend("jsonkey", "bar") assert "foobar" == await decoded_r.json().get("jsonkey", Path.root_path()) -@pytest.mark.redismod async def test_strlen(decoded_r: redis.Redis): await decoded_r.json().set("str", Path.root_path(), "foo") assert 3 == await decoded_r.json().strlen("str", Path.root_path()) @@ -201,7 +185,6 @@ async def test_strlen(decoded_r: redis.Redis): assert 6 == await decoded_r.json().strlen("str") -@pytest.mark.redismod async def test_arrappend(decoded_r: redis.Redis): await decoded_r.json().set("arr", Path.root_path(), [1]) assert 2 == await decoded_r.json().arrappend("arr", Path.root_path(), 2) @@ -209,7 +192,6 @@ async def test_arrappend(decoded_r: redis.Redis): assert 7 == await decoded_r.json().arrappend("arr", Path.root_path(), *[5, 6, 7]) -@pytest.mark.redismod async def test_arrindex(decoded_r: redis.Redis): r_path = Path.root_path() await decoded_r.json().set("arr", r_path, [0, 1, 2, 3, 4]) @@ -222,7 +204,6 @@ async def test_arrindex(decoded_r: redis.Redis): assert -1 == await decoded_r.json().arrindex("arr", r_path, 4, start=1, stop=3) -@pytest.mark.redismod async def test_arrinsert(decoded_r: redis.Redis): await decoded_r.json().set("arr", Path.root_path(), [0, 4]) assert 5 == await decoded_r.json().arrinsert("arr", Path.root_path(), 1, *[1, 2, 3]) @@ -234,7 +215,6 @@ async def test_arrinsert(decoded_r: redis.Redis): assert await decoded_r.json().get("val2") == [["some", "thing"], 5, 6, 7, 8, 9] -@pytest.mark.redismod async def test_arrlen(decoded_r: redis.Redis): await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 5 == await decoded_r.json().arrlen("arr", Path.root_path()) @@ -242,7 +222,6 @@ async def test_arrlen(decoded_r: redis.Redis): assert await decoded_r.json().arrlen("fakekey") is None -@pytest.mark.redismod async def test_arrpop(decoded_r: redis.Redis): await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 4 == await decoded_r.json().arrpop("arr", Path.root_path(), 4) @@ -260,7 +239,6 @@ async def test_arrpop(decoded_r: redis.Redis): assert await decoded_r.json().arrpop("arr") is None -@pytest.mark.redismod async def test_arrtrim(decoded_r: redis.Redis): await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 3 == await decoded_r.json().arrtrim("arr", Path.root_path(), 1, 3) @@ -283,7 +261,6 @@ async def test_arrtrim(decoded_r: redis.Redis): assert 0 == await decoded_r.json().arrtrim("arr", Path.root_path(), 9, 11) -@pytest.mark.redismod async def test_resp(decoded_r: redis.Redis): obj = {"foo": "bar", "baz": 1, "qaz": True} await decoded_r.json().set("obj", Path.root_path(), obj) @@ -293,7 +270,6 @@ async def test_resp(decoded_r: redis.Redis): assert isinstance(await decoded_r.json().resp("obj"), list) -@pytest.mark.redismod async def test_objkeys(decoded_r: redis.Redis): obj = {"foo": "bar", "baz": "qaz"} await decoded_r.json().set("obj", Path.root_path(), obj) @@ -310,7 +286,6 @@ async def test_objkeys(decoded_r: redis.Redis): assert await decoded_r.json().objkeys("fakekey") is None -@pytest.mark.redismod async def test_objlen(decoded_r: redis.Redis): obj = {"foo": "bar", "baz": "qaz"} await decoded_r.json().set("obj", Path.root_path(), obj) @@ -320,7 +295,7 @@ async def test_objlen(decoded_r: redis.Redis): assert len(obj) == await decoded_r.json().objlen("obj") -# @pytest.mark.redismod +# # async def test_json_commands_in_pipeline(decoded_r: redis.Redis): # async with decoded_r.json().pipeline() as p: # p.set("foo", Path.root_path(), "bar") @@ -344,7 +319,6 @@ async def test_objlen(decoded_r: redis.Redis): # assert await decoded_r.get("foo") is None -@pytest.mark.redismod async def test_json_delete_with_dollar(decoded_r: redis.Redis): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} assert await decoded_r.json().set("doc1", "$", doc1) @@ -396,7 +370,6 @@ async def test_json_delete_with_dollar(decoded_r: redis.Redis): await decoded_r.json().delete("not_a_document", "..a") -@pytest.mark.redismod async def test_json_forget_with_dollar(decoded_r: redis.Redis): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} assert await decoded_r.json().set("doc1", "$", doc1) @@ -449,7 +422,6 @@ async def test_json_forget_with_dollar(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod async def test_json_mget_dollar(decoded_r: redis.Redis): # Test mget with multi paths await decoded_r.json().set( @@ -479,7 +451,6 @@ async def test_json_mget_dollar(decoded_r: redis.Redis): assert res == [None, None] -@pytest.mark.redismod async def test_numby_commands_dollar(decoded_r: redis.Redis): # Test NUMINCRBY await decoded_r.json().set( @@ -538,7 +509,6 @@ async def test_numby_commands_dollar(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_strappend_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", "$", {"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}} @@ -569,7 +539,6 @@ async def test_strappend_dollar(decoded_r: redis.Redis): await decoded_r.json().strappend("doc1", "piu") -@pytest.mark.redismod async def test_strlen_dollar(decoded_r: redis.Redis): # Test multi await decoded_r.json().set( @@ -590,7 +559,6 @@ async def test_strlen_dollar(decoded_r: redis.Redis): await decoded_r.json().strlen("non_existing_doc", "$..a") -@pytest.mark.redismod async def test_arrappend_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -665,7 +633,6 @@ async def test_arrappend_dollar(decoded_r: redis.Redis): await decoded_r.json().arrappend("non_existing_doc", "$..a") -@pytest.mark.redismod async def test_arrinsert_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -704,7 +671,6 @@ async def test_arrinsert_dollar(decoded_r: redis.Redis): await decoded_r.json().arrappend("non_existing_doc", "$..a") -@pytest.mark.redismod async def test_arrlen_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -750,7 +716,6 @@ async def test_arrlen_dollar(decoded_r: redis.Redis): assert await decoded_r.json().arrlen("non_existing_doc", "..a") is None -@pytest.mark.redismod async def test_arrpop_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -792,7 +757,6 @@ async def test_arrpop_dollar(decoded_r: redis.Redis): await decoded_r.json().arrpop("non_existing_doc", "..a") -@pytest.mark.redismod async def test_arrtrim_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -844,7 +808,6 @@ async def test_arrtrim_dollar(decoded_r: redis.Redis): await decoded_r.json().arrtrim("non_existing_doc", "..a", 1, 1) -@pytest.mark.redismod async def test_objkeys_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -874,7 +837,6 @@ async def test_objkeys_dollar(decoded_r: redis.Redis): assert await decoded_r.json().objkeys("doc1", "$..nowhere") == [] -@pytest.mark.redismod async def test_objlen_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -929,7 +891,6 @@ def load_types_data(nested_key_name): return jdata, types -@pytest.mark.redismod async def test_type_dollar(decoded_r: redis.Redis): jdata, jtypes = load_types_data("a") await decoded_r.json().set("doc1", "$", jdata) @@ -948,7 +909,6 @@ async def test_type_dollar(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_clear_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", @@ -1000,7 +960,6 @@ async def test_clear_dollar(decoded_r: redis.Redis): await decoded_r.json().clear("non_existing_doc", "$..a") -@pytest.mark.redismod async def test_toggle_dollar(decoded_r: redis.Redis): await decoded_r.json().set( "doc1", diff --git a/tests/test_asyncio/test_search.py b/tests/test_asyncio/test_search.py index 0e6fe22131..6ed67a04d8 100644 --- a/tests/test_asyncio/test_search.py +++ b/tests/test_asyncio/test_search.py @@ -40,8 +40,8 @@ @pytest_asyncio.fixture() -async def decoded_r(create_redis, stack_url): - return await create_redis(decode_responses=True, url=stack_url) +async def decoded_r(create_redis): + return await create_redis(decode_responses=True) async def waitForIndex(env, idx, timeout=None): @@ -109,7 +109,6 @@ async def createIndex(decoded_r, num_docs=100, definition=None): await indexer.commit() -@pytest.mark.redismod async def test_client(decoded_r: redis.Redis): num_docs = 500 await createIndex(decoded_r.ft(), num_docs=num_docs) @@ -337,7 +336,6 @@ async def test_client(decoded_r: redis.Redis): await decoded_r.ft().delete_document("doc-5ghs2") -@pytest.mark.redismod @pytest.mark.onlynoncluster async def test_scores(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("txt"),)) @@ -359,7 +357,6 @@ async def test_scores(decoded_r: redis.Redis): assert "doc1" == res["results"][1]["id"] -@pytest.mark.redismod async def test_stopwords(decoded_r: redis.Redis): stopwords = ["foo", "bar", "baz"] await decoded_r.ft().create_index((TextField("txt"),), stopwords=stopwords) @@ -378,7 +375,6 @@ async def test_stopwords(decoded_r: redis.Redis): assert 1 == res2["total_results"] -@pytest.mark.redismod async def test_filters(decoded_r: redis.Redis): await decoded_r.ft().create_index( (TextField("txt"), NumericField("num"), GeoField("loc")) @@ -436,7 +432,6 @@ async def test_filters(decoded_r: redis.Redis): assert ["doc1", "doc2"] == res -@pytest.mark.redismod async def test_sort_by(decoded_r: redis.Redis): await decoded_r.ft().create_index( (TextField("txt"), NumericField("num", sortable=True)) @@ -470,7 +465,6 @@ async def test_sort_by(decoded_r: redis.Redis): assert "doc3" == res2["results"][0]["id"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") async def test_drop_index(decoded_r: redis.Redis): """ @@ -489,7 +483,6 @@ async def test_drop_index(decoded_r: redis.Redis): assert i == keep_docs[1] -@pytest.mark.redismod async def test_example(decoded_r: redis.Redis): # Creating the index definition and schema await decoded_r.ft().create_index( @@ -512,7 +505,6 @@ async def test_example(decoded_r: redis.Redis): assert res is not None -@pytest.mark.redismod async def test_auto_complete(decoded_r: redis.Redis): n = 0 with open(TITLES_CSV) as f: @@ -563,7 +555,6 @@ async def test_auto_complete(decoded_r: redis.Redis): assert sug.payload.startswith("pl") -@pytest.mark.redismod async def test_no_index(decoded_r: redis.Redis): await decoded_r.ft().create_index( ( @@ -641,7 +632,6 @@ async def test_no_index(decoded_r: redis.Redis): TagField("name", no_index=True, sortable=False) -@pytest.mark.redismod async def test_explain(decoded_r: redis.Redis): await decoded_r.ft().create_index( (TextField("f1"), TextField("f2"), TextField("f3")) @@ -650,13 +640,11 @@ async def test_explain(decoded_r: redis.Redis): assert res -@pytest.mark.redismod async def test_explaincli(decoded_r: redis.Redis): with pytest.raises(NotImplementedError): await decoded_r.ft().explain_cli("foo") -@pytest.mark.redismod async def test_summarize(decoded_r: redis.Redis): await createIndex(decoded_r.ft()) await waitForIndex(decoded_r, "idx") @@ -699,7 +687,6 @@ async def test_summarize(decoded_r: redis.Redis): ) -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") async def test_alias(decoded_r: redis.Redis): index1 = getClient(decoded_r) @@ -762,7 +749,6 @@ async def test_alias(decoded_r: redis.Redis): (await alias_client2.search("*")).docs[0] -@pytest.mark.redismod @pytest.mark.xfail(strict=False) async def test_alias_basic(decoded_r: redis.Redis): # Creating a client with one index @@ -815,7 +801,6 @@ async def test_alias_basic(decoded_r: redis.Redis): _ = (await alias_client2.search("*")).docs[0] -@pytest.mark.redismod async def test_tags(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("txt"), TagField("tags"))) tags = "foo,foo bar,hello;world" @@ -864,7 +849,6 @@ async def test_tags(decoded_r: redis.Redis): assert set(tags.split(",") + tags2.split(",")) == set(q2) -@pytest.mark.redismod async def test_textfield_sortable_nostem(decoded_r: redis.Redis): # Creating the index definition with sortable and no_stem await decoded_r.ft().create_index((TextField("txt", sortable=True, no_stem=True),)) @@ -879,7 +863,6 @@ async def test_textfield_sortable_nostem(decoded_r: redis.Redis): assert "NOSTEM" in response["attributes"][0]["flags"] -@pytest.mark.redismod async def test_alter_schema_add(decoded_r: redis.Redis): # Creating the index definition and schema await decoded_r.ft().create_index(TextField("title")) @@ -903,7 +886,6 @@ async def test_alter_schema_add(decoded_r: redis.Redis): assert 1 == res["total_results"] -@pytest.mark.redismod async def test_spell_check(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("f1"), TextField("f2"))) @@ -972,7 +954,6 @@ async def test_spell_check(decoded_r: redis.Redis): assert res == {"results": {}} -@pytest.mark.redismod async def test_dict_operations(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("f1"), TextField("f2"))) # Add three items @@ -991,7 +972,6 @@ async def test_dict_operations(decoded_r: redis.Redis): await decoded_r.ft().dict_del("custom_dict", *res) -@pytest.mark.redismod async def test_phonetic_matcher(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("name"),)) await decoded_r.hset("doc1", mapping={"name": "Jon"}) @@ -1023,7 +1003,6 @@ async def test_phonetic_matcher(decoded_r: redis.Redis): ) -@pytest.mark.redismod @pytest.mark.onlynoncluster async def test_scorer(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("description"),)) @@ -1083,7 +1062,6 @@ async def test_scorer(decoded_r: redis.Redis): assert 0.0 == res["results"][0]["score"] -@pytest.mark.redismod async def test_get(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("f1"), TextField("f2"))) @@ -1106,7 +1084,6 @@ async def test_get(decoded_r: redis.Redis): ] == await decoded_r.ft().get("doc1", "doc2") -@pytest.mark.redismod @pytest.mark.onlynoncluster @skip_ifmodversion_lt("2.2.0", "search") async def test_config(decoded_r: redis.Redis): @@ -1119,7 +1096,6 @@ async def test_config(decoded_r: redis.Redis): assert "100" == res["TIMEOUT"] -@pytest.mark.redismod @pytest.mark.onlynoncluster async def test_aggregations_groupby(decoded_r: redis.Redis): # Creating the index definition and schema @@ -1429,7 +1405,6 @@ async def test_aggregations_groupby(decoded_r: redis.Redis): ] -@pytest.mark.redismod async def test_aggregations_sort_by_and_limit(decoded_r: redis.Redis): await decoded_r.ft().create_index((TextField("t1"), TextField("t2"))) @@ -1488,7 +1463,6 @@ async def test_aggregations_sort_by_and_limit(decoded_r: redis.Redis): assert res["results"][0]["extra_attributes"] == {"t1": "b"} -@pytest.mark.redismod @pytest.mark.experimental async def test_withsuffixtrie(decoded_r: redis.Redis): # create index @@ -1530,7 +1504,6 @@ async def test_withsuffixtrie(decoded_r: redis.Redis): assert "WITHSUFFIXTRIE" in info["attributes"][0]["flags"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.10.05", "search") async def test_aggregations_add_scores(decoded_r: redis.Redis): assert await decoded_r.ft().create_index( @@ -1556,7 +1529,6 @@ async def test_aggregations_add_scores(decoded_r: redis.Redis): assert res.rows[1] == ["__score", "0.2"] -@pytest.mark.redismod @skip_if_redis_enterprise() async def test_search_commands_in_pipeline(decoded_r: redis.Redis): p = await decoded_r.ft().pipeline() @@ -1586,7 +1558,6 @@ async def test_search_commands_in_pipeline(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_query_timeout(decoded_r: redis.Redis): q1 = Query("foo").timeout(5000) assert q1.get_args() == ["foo", "TIMEOUT", 5000, "LIMIT", 0, 10] @@ -1595,7 +1566,6 @@ async def test_query_timeout(decoded_r: redis.Redis): await decoded_r.ft().search(q2) -@pytest.mark.redismod @skip_if_resp_version(3) async def test_binary_and_text_fields(decoded_r: redis.Redis): fake_vec = np.array([0.1, 0.2, 0.3, 0.4], dtype=np.float32) diff --git a/tests/test_asyncio/test_timeseries.py b/tests/test_asyncio/test_timeseries.py index 5a1597f2d0..f1d19b2ce9 100644 --- a/tests/test_asyncio/test_timeseries.py +++ b/tests/test_asyncio/test_timeseries.py @@ -12,11 +12,10 @@ @pytest_asyncio.fixture() -async def decoded_r(create_redis, stack_url): - return await create_redis(decode_responses=True, url=stack_url) +async def decoded_r(create_redis): + return await create_redis(decode_responses=True) -@pytest.mark.redismod async def test_create(decoded_r: redis.Redis): assert await decoded_r.ts().create(1) assert await decoded_r.ts().create(2, retention_msecs=5) @@ -34,7 +33,6 @@ async def test_create(decoded_r: redis.Redis): assert_resp_response(decoded_r, 128, info.get("chunk_size"), info.get("chunkSize")) -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") async def test_create_duplicate_policy(decoded_r: redis.Redis): # Test for duplicate policy @@ -50,7 +48,6 @@ async def test_create_duplicate_policy(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_alter(decoded_r: redis.Redis): assert await decoded_r.ts().create(1) res = await decoded_r.ts().info(1) @@ -73,7 +70,6 @@ async def test_alter(decoded_r: redis.Redis): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") async def test_alter_duplicate_policy(decoded_r: redis.Redis): assert await decoded_r.ts().create(1) @@ -88,7 +84,6 @@ async def test_alter_duplicate_policy(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_add(decoded_r: redis.Redis): assert 1 == await decoded_r.ts().add(1, 1, 1) assert 2 == await decoded_r.ts().add(2, 2, 3, retention_msecs=10) @@ -111,7 +106,6 @@ async def test_add(decoded_r: redis.Redis): assert_resp_response(decoded_r, 128, info.get("chunk_size"), info.get("chunkSize")) -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") async def test_add_duplicate_policy(decoded_r: redis.Redis): # Test for duplicate policy BLOCK @@ -154,7 +148,6 @@ async def test_add_duplicate_policy(decoded_r: redis.Redis): assert 5.0 == res[1] -@pytest.mark.redismod async def test_madd(decoded_r: redis.Redis): await decoded_r.ts().create("a") assert [1, 2, 3] == await decoded_r.ts().madd( @@ -162,7 +155,6 @@ async def test_madd(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_incrby_decrby(decoded_r: redis.Redis): for _ in range(100): assert await decoded_r.ts().incrby(1, 1) @@ -191,7 +183,6 @@ async def test_incrby_decrby(decoded_r: redis.Redis): assert_resp_response(decoded_r, 128, info.get("chunk_size"), info.get("chunkSize")) -@pytest.mark.redismod async def test_create_and_delete_rule(decoded_r: redis.Redis): # test rule creation time = 100 @@ -215,7 +206,6 @@ async def test_create_and_delete_rule(decoded_r: redis.Redis): assert not info["rules"] -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") async def test_del_range(decoded_r: redis.Redis): try: @@ -232,7 +222,6 @@ async def test_del_range(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_range(decoded_r: redis.Redis): for i in range(100): await decoded_r.ts().add(1, i, i % 7) @@ -249,7 +238,6 @@ async def test_range(decoded_r: redis.Redis): assert 10 == len(await decoded_r.ts().range(1, 0, 500, count=10)) -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") async def test_range_advanced(decoded_r: redis.Redis): for i in range(100): @@ -280,7 +268,6 @@ async def test_range_advanced(decoded_r: redis.Redis): assert_resp_response(decoded_r, res, [(0, 2.55), (10, 3.0)], [[0, 2.55], [10, 3.0]]) -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") async def test_rev_range(decoded_r: redis.Redis): for i in range(100): @@ -325,7 +312,6 @@ async def test_rev_range(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod async def test_multi_range(decoded_r: redis.Redis): await decoded_r.ts().create(1, labels={"Test": "This", "team": "ny"}) await decoded_r.ts().create( @@ -380,7 +366,6 @@ async def test_multi_range(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") async def test_multi_range_advanced(decoded_r: redis.Redis): await decoded_r.ts().create(1, labels={"Test": "This", "team": "ny"}) @@ -498,7 +483,6 @@ async def test_multi_range_advanced(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") async def test_multi_reverse_range(decoded_r: redis.Redis): await decoded_r.ts().create(1, labels={"Test": "This", "team": "ny"}) @@ -661,7 +645,6 @@ async def test_multi_reverse_range(decoded_r: redis.Redis): assert [[1, 10.0], [0, 1.0]] == res["1"][2] -@pytest.mark.redismod async def test_get(decoded_r: redis.Redis): name = "test" await decoded_r.ts().create(name) @@ -673,7 +656,6 @@ async def test_get(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod async def test_mget(decoded_r: redis.Redis): await decoded_r.ts().create(1, labels={"Test": "This"}) await decoded_r.ts().create(2, labels={"Test": "This", "Taste": "That"}) @@ -708,7 +690,6 @@ async def test_mget(decoded_r: redis.Redis): assert {"Taste": "That", "Test": "This"} == res["2"][0] -@pytest.mark.redismod async def test_info(decoded_r: redis.Redis): await decoded_r.ts().create( 1, retention_msecs=5, labels={"currentLabel": "currentData"} @@ -720,7 +701,6 @@ async def test_info(decoded_r: redis.Redis): assert info["labels"]["currentLabel"] == "currentData" -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") async def test_info_duplicate_policy(decoded_r: redis.Redis): await decoded_r.ts().create( @@ -739,7 +719,6 @@ async def test_info_duplicate_policy(decoded_r: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod async def test_query_index(decoded_r: redis.Redis): await decoded_r.ts().create(1, labels={"Test": "This"}) await decoded_r.ts().create(2, labels={"Test": "This", "Taste": "That"}) @@ -750,7 +729,6 @@ async def test_query_index(decoded_r: redis.Redis): ) -@pytest.mark.redismod async def test_uncompressed(decoded_r: redis.Redis): await decoded_r.ts().create("compressed") await decoded_r.ts().create("uncompressed", uncompressed=True) diff --git a/tests/test_bloom.py b/tests/test_bloom.py index e44c421634..0b74286f4f 100644 --- a/tests/test_bloom.py +++ b/tests/test_bloom.py @@ -13,9 +13,9 @@ @pytest.fixture() -def decoded_r(request, stack_url): +def decoded_r(request): with _get_client( - redis.Redis, request, decode_responses=True, from_url=stack_url + redis.Redis, request, decode_responses=True ) as client: yield client @@ -36,7 +36,6 @@ def client(decoded_r): return decoded_r -@pytest.mark.redismod def test_create(client): """Test CREATE/RESERVE calls""" assert client.bf().create("bloom", 0.01, 1000) @@ -51,7 +50,6 @@ def test_create(client): assert client.topk().reserve("topk", 5, 100, 5, 0.9) -@pytest.mark.redismod def test_bf_reserve(client): """Testing BF.RESERVE""" assert client.bf().reserve("bloom", 0.01, 1000) @@ -67,12 +65,10 @@ def test_bf_reserve(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_create(client): assert client.tdigest().create("tDigest", 100) -@pytest.mark.redismod def test_bf_add(client): assert client.bf().create("bloom", 0.01, 1000) assert 1 == client.bf().add("bloom", "foo") @@ -85,7 +81,6 @@ def test_bf_add(client): assert [1, 0] == intlist(client.bf().mexists("bloom", "foo", "noexist")) -@pytest.mark.redismod def test_bf_insert(client): assert client.bf().create("bloom", 0.01, 1000) assert [1] == intlist(client.bf().insert("bloom", ["foo"])) @@ -116,7 +111,6 @@ def test_bf_insert(client): ) -@pytest.mark.redismod def test_bf_scandump_and_loadchunk(client): # Store a filter client.bf().create("myBloom", "0.0001", "1000") @@ -164,7 +158,6 @@ def do_verify(): client.bf().create("myBloom", "0.0001", "10000000") -@pytest.mark.redismod def test_bf_info(client): expansion = 4 # Store a filter @@ -196,7 +189,6 @@ def test_bf_info(client): assert True -@pytest.mark.redismod def test_bf_card(client): # return 0 if the key does not exist assert client.bf().card("not_exist") == 0 @@ -211,7 +203,6 @@ def test_bf_card(client): client.bf().card("setKey") -@pytest.mark.redismod def test_cf_add_and_insert(client): assert client.cf().create("cuckoo", 1000) assert client.cf().add("cuckoo", "filter") @@ -237,7 +228,6 @@ def test_cf_add_and_insert(client): ) -@pytest.mark.redismod def test_cf_exists_and_del(client): assert client.cf().create("cuckoo", 1000) assert client.cf().add("cuckoo", "filter") @@ -250,7 +240,6 @@ def test_cf_exists_and_del(client): assert 0 == client.cf().count("cuckoo", "filter") -@pytest.mark.redismod def test_cms(client): assert client.cms().initbydim("dim", 1000, 5) assert client.cms().initbyprob("prob", 0.01, 0.01) @@ -267,7 +256,6 @@ def test_cms(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod def test_cms_merge(client): assert client.cms().initbydim("A", 1000, 5) assert client.cms().initbydim("B", 1000, 5) @@ -284,7 +272,6 @@ def test_cms_merge(client): assert [16, 15, 21] == client.cms().query("C", "foo", "bar", "baz") -@pytest.mark.redismod def test_topk(client): # test list with empty buckets assert client.topk().reserve("topk", 3, 50, 4, 0.9) @@ -364,7 +351,6 @@ def test_topk(client): assert 0.9 == round(float(info["decay"]), 1) -@pytest.mark.redismod def test_topk_incrby(client): client.flushdb() assert client.topk().reserve("topk", 3, 10, 3, 1) @@ -379,7 +365,6 @@ def test_topk_incrby(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_reset(client): assert client.tdigest().create("tDigest", 10) # reset on empty histogram @@ -396,7 +381,6 @@ def test_tdigest_reset(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod def test_tdigest_merge(client): assert client.tdigest().create("to-tDigest", 10) assert client.tdigest().create("from-tDigest", 10) @@ -424,7 +408,6 @@ def test_tdigest_merge(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_min_and_max(client): assert client.tdigest().create("tDigest", 100) # insert data-points into sketch @@ -435,7 +418,6 @@ def test_tdigest_min_and_max(client): @pytest.mark.experimental -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.0", "bf") def test_tdigest_quantile(client): assert client.tdigest().create("tDigest", 500) @@ -457,7 +439,6 @@ def test_tdigest_quantile(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_cdf(client): assert client.tdigest().create("tDigest", 100) # insert data-points into sketch @@ -469,7 +450,6 @@ def test_tdigest_cdf(client): @pytest.mark.experimental -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.0", "bf") def test_tdigest_trimmed_mean(client): assert client.tdigest().create("tDigest", 100) @@ -480,7 +460,6 @@ def test_tdigest_trimmed_mean(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_rank(client): assert client.tdigest().create("t-digest", 500) assert client.tdigest().add("t-digest", list(range(0, 20))) @@ -491,7 +470,6 @@ def test_tdigest_rank(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_revrank(client): assert client.tdigest().create("t-digest", 500) assert client.tdigest().add("t-digest", list(range(0, 20))) @@ -501,7 +479,6 @@ def test_tdigest_revrank(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_byrank(client): assert client.tdigest().create("t-digest", 500) assert client.tdigest().add("t-digest", list(range(1, 11))) @@ -513,7 +490,6 @@ def test_tdigest_byrank(client): @pytest.mark.experimental -@pytest.mark.redismod def test_tdigest_byrevrank(client): assert client.tdigest().create("t-digest", 500) assert client.tdigest().add("t-digest", list(range(1, 11))) diff --git a/tests/test_cluster.py b/tests/test_cluster.py index 5a28f4cde5..eb416b4810 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -2450,7 +2450,6 @@ def try_delete_libs(self, r, *lib_names): except Exception: pass - @pytest.mark.redismod @skip_if_server_version_lt("7.1.140") def test_tfunction_load_delete(self, r): r.gears_refresh_cluster() @@ -2459,7 +2458,6 @@ def test_tfunction_load_delete(self, r): assert r.tfunction_load(lib_code) assert r.tfunction_delete("lib1") - @pytest.mark.redismod @skip_if_server_version_lt("7.1.140") def test_tfunction_list(self, r): r.gears_refresh_cluster() @@ -2483,7 +2481,6 @@ def test_tfunction_list(self, r): assert r.tfunction_delete("lib2") assert r.tfunction_delete("lib3") - @pytest.mark.redismod @skip_if_server_version_lt("7.1.140") def test_tfcall(self, r): r.gears_refresh_cluster() diff --git a/tests/test_commands.py b/tests/test_commands.py index 74e9c1c88e..02afab3c40 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -4964,7 +4964,6 @@ def test_latency_latest(self, r: redis.Redis): def test_latency_reset(self, r: redis.Redis): assert r.latency_reset() == 0 - @pytest.mark.redismod @skip_if_server_version_lt("4.0.0") @skip_if_redis_enterprise() def test_module_list(self, r): @@ -5034,7 +5033,6 @@ def test_command_getkeysandflags(self, r: redis.Redis): [b"mylist2", [b"RW", b"insert"]], ] - @pytest.mark.redismod @pytest.mark.onlynoncluster @skip_if_server_version_lt("4.0.0") @skip_if_redis_enterprise() @@ -5047,7 +5045,6 @@ def test_module(self, stack_r): stack_r.module_load("/some/fake/path", "arg1", "arg2", "arg3", "arg4") assert "Error loading the extension." in str(excinfo.value) - @pytest.mark.redismod @pytest.mark.onlynoncluster @skip_if_server_version_lt("7.0.0") @skip_if_redis_enterprise() diff --git a/tests/test_connection.py b/tests/test_connection.py index 69275d58c0..0428fdc41c 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -34,7 +34,6 @@ def test_invalid_response(r): @skip_if_server_version_lt("4.0.0") -@pytest.mark.redismod def test_loading_external_modules(r): def inner(): pass diff --git a/tests/test_graph.py b/tests/test_graph.py deleted file mode 100644 index c82c5030c8..0000000000 --- a/tests/test_graph.py +++ /dev/null @@ -1,657 +0,0 @@ -from unittest.mock import patch - -import pytest -from redis import Redis -from redis.commands.graph import Edge, Node, Path -from redis.commands.graph.execution_plan import Operation -from redis.commands.graph.query_result import ( - CACHED_EXECUTION, - INDICES_CREATED, - INDICES_DELETED, - INTERNAL_EXECUTION_TIME, - LABELS_ADDED, - LABELS_REMOVED, - NODES_CREATED, - NODES_DELETED, - PROPERTIES_REMOVED, - PROPERTIES_SET, - RELATIONSHIPS_CREATED, - RELATIONSHIPS_DELETED, - QueryResult, -) -from redis.exceptions import ResponseError -from tests.conftest import _get_client, skip_if_redis_enterprise, skip_if_resp_version - - -@pytest.fixture -def client(request, stack_url): - r = _get_client( - Redis, request, decode_responses=True, from_url="redis://localhost:6480" - ) - r.flushdb() - return r - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_bulk(client): - with pytest.raises(NotImplementedError): - client.graph().bulk() - client.graph().bulk(foo="bar!") - - -@pytest.mark.redismod -def test_graph_creation_throws_deprecation_warning(client): - """Verify that a DeprecationWarning is raised when creating a Graph instance.""" - - match = "RedisGraph support is deprecated as of Redis Stack 7.2" - with pytest.warns(DeprecationWarning, match=match): - client.graph() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_graph_creation(client): - graph = client.graph() - - john = Node( - label="person", - properties={ - "name": "John Doe", - "age": 33, - "gender": "male", - "status": "single", - }, - ) - graph.add_node(john) - japan = Node(label="country", properties={"name": "Japan"}) - - graph.add_node(japan) - edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) - graph.add_edge(edge) - - graph.commit() - - query = ( - 'MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) ' - "RETURN p, v, c" - ) - - result = graph.query(query) - - person = result.result_set[0][0] - visit = result.result_set[0][1] - country = result.result_set[0][2] - - assert person == john - assert visit.properties == edge.properties - assert country == japan - - query = """RETURN [1, 2.3, "4", true, false, null]""" - result = graph.query(query) - assert [1, 2.3, "4", True, False, None] == result.result_set[0][0] - - # All done, remove graph. - graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_array_functions(client): - query = """CREATE (p:person{name:'a',age:32, array:[0,1,2]})""" - client.graph().query(query) - - query = """WITH [0,1,2] as x return x""" - result = client.graph().query(query) - assert [0, 1, 2] == result.result_set[0][0] - - query = """MATCH(n) return collect(n)""" - result = client.graph().query(query) - - a = Node( - node_id=0, - label="person", - properties={"name": "a", "age": 32, "array": [0, 1, 2]}, - ) - - assert [a] == result.result_set[0][0] - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_path(client): - node0 = Node(node_id=0, label="L1") - node1 = Node(node_id=1, label="L1") - edge01 = Edge(node0, "R1", node1, edge_id=0, properties={"value": 1}) - - graph = client.graph() - graph.add_node(node0) - graph.add_node(node1) - graph.add_edge(edge01) - graph.flush() - - path01 = Path.new_empty_path().add_node(node0).add_edge(edge01).add_node(node1) - expected_results = [[path01]] - - query = "MATCH p=(:L1)-[:R1]->(:L1) RETURN p ORDER BY p" - result = graph.query(query) - assert expected_results == result.result_set - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_param(client): - params = [1, 2.3, "str", True, False, None, [0, 1, 2], r"\" RETURN 1337 //"] - query = "RETURN $param" - for param in params: - result = client.graph().query(query, {"param": param}) - expected_results = [[param]] - assert expected_results == result.result_set - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_map(client): - query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}" - - actual = client.graph().query(query).result_set[0][0] - expected = { - "a": 1, - "b": "str", - "c": None, - "d": [1, 2, 3], - "e": True, - "f": {"x": 1, "y": 2}, - } - - assert actual == expected - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_point(client): - query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})" - expected_lat = 32.070794860 - expected_lon = 34.820751118 - actual = client.graph().query(query).result_set[0][0] - assert abs(actual["latitude"] - expected_lat) < 0.001 - assert abs(actual["longitude"] - expected_lon) < 0.001 - - query = "RETURN point({latitude: 32, longitude: 34.0})" - expected_lat = 32 - expected_lon = 34 - actual = client.graph().query(query).result_set[0][0] - assert abs(actual["latitude"] - expected_lat) < 0.001 - assert abs(actual["longitude"] - expected_lon) < 0.001 - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_index_response(client): - result_set = client.graph().query("CREATE INDEX ON :person(age)") - assert 1 == result_set.indices_created - - result_set = client.graph().query("CREATE INDEX ON :person(age)") - assert 0 == result_set.indices_created - - result_set = client.graph().query("DROP INDEX ON :person(age)") - assert 1 == result_set.indices_deleted - - with pytest.raises(ResponseError): - client.graph().query("DROP INDEX ON :person(age)") - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_stringify_query_result(client): - graph = client.graph() - - john = Node( - alias="a", - label="person", - properties={ - "name": "John Doe", - "age": 33, - "gender": "male", - "status": "single", - }, - ) - graph.add_node(john) - - japan = Node(alias="b", label="country", properties={"name": "Japan"}) - graph.add_node(japan) - - edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) - graph.add_edge(edge) - - assert ( - str(john) - == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa - ) - assert ( - str(edge) - == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa - + """-[:visited{purpose:"pleasure"}]->""" - + """(b:country{name:"Japan"})""" - ) - assert str(japan) == """(b:country{name:"Japan"})""" - - graph.commit() - - query = """MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) - RETURN p, v, c""" - - result = client.graph().query(query) - person = result.result_set[0][0] - visit = result.result_set[0][1] - country = result.result_set[0][2] - - assert ( - str(person) - == """(:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa - ) - assert str(visit) == """()-[:visited{purpose:"pleasure"}]->()""" - assert str(country) == """(:country{name:"Japan"})""" - - graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_optional_match(client): - # Build a graph of form (a)-[R]->(b) - node0 = Node(node_id=0, label="L1", properties={"value": "a"}) - node1 = Node(node_id=1, label="L1", properties={"value": "b"}) - - edge01 = Edge(node0, "R", node1, edge_id=0) - - graph = client.graph() - graph.add_node(node0) - graph.add_node(node1) - graph.add_edge(edge01) - graph.flush() - - # Issue a query that collects all outgoing edges from both nodes - # (the second has none) - query = """MATCH (a) OPTIONAL MATCH (a)-[e]->(b) RETURN a, e, b ORDER BY a.value""" # noqa - expected_results = [[node0, edge01, node1], [node1, None, None]] - - result = client.graph().query(query) - assert expected_results == result.result_set - - graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_cached_execution(client): - client.graph().query("CREATE ()") - - uncached_result = client.graph().query("MATCH (n) RETURN n, $param", {"param": [0]}) - assert uncached_result.cached_execution is False - - # loop to make sure the query is cached on each thread on server - for x in range(0, 64): - cached_result = client.graph().query( - "MATCH (n) RETURN n, $param", {"param": [0]} - ) - assert uncached_result.result_set == cached_result.result_set - - # should be cached on all threads by now - assert cached_result.cached_execution - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_slowlog(client): - create_query = """CREATE (:Rider - {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), - (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), - (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" - client.graph().query(create_query) - - results = client.graph().slowlog() - assert results[0][1] == "GRAPH.QUERY" - assert results[0][2] == create_query - - -@pytest.mark.redismod -@skip_if_resp_version(3) -@pytest.mark.xfail(strict=False) -def test_query_timeout(client): - # Build a sample graph with 1000 nodes. - client.graph().query("UNWIND range(0,1000) as val CREATE ({v: val})") - # Issue a long-running query with a 1-millisecond timeout. - with pytest.raises(ResponseError): - client.graph().query("MATCH (a), (b), (c), (d) RETURN *", timeout=1) - assert False is False - - with pytest.raises(Exception): - client.graph().query("RETURN 1", timeout="str") - assert False is False - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_read_only_query(client): - with pytest.raises(Exception): - # Issue a write query, specifying read-only true, - # this call should fail. - client.graph().query("CREATE (p:person {name:'a'})", read_only=True) - assert False is False - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_profile(client): - q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})""" - profile = client.graph().profile(q).result_set - assert "Create | Records produced: 3" in profile - assert "Unwind | Records produced: 3" in profile - - q = "MATCH (p:Person) WHERE p.v > 1 RETURN p" - profile = client.graph().profile(q).result_set - assert "Results | Records produced: 2" in profile - assert "Project | Records produced: 2" in profile - assert "Filter | Records produced: 2" in profile - assert "Node By Label Scan | (p:Person) | Records produced: 3" in profile - - -@pytest.mark.redismod -@skip_if_resp_version(3) -@skip_if_redis_enterprise() -def test_config(client): - config_name = "RESULTSET_SIZE" - config_value = 3 - - # Set configuration - response = client.graph().config(config_name, config_value, set=True) - assert response == "OK" - - # Make sure config been updated. - response = client.graph().config(config_name, set=False) - expected_response = [config_name, config_value] - assert response == expected_response - - config_name = "QUERY_MEM_CAPACITY" - config_value = 1 << 20 # 1MB - - # Set configuration - response = client.graph().config(config_name, config_value, set=True) - assert response == "OK" - - # Make sure config been updated. - response = client.graph().config(config_name, set=False) - expected_response = [config_name, config_value] - assert response == expected_response - - # reset to default - client.graph().config("QUERY_MEM_CAPACITY", 0, set=True) - client.graph().config("RESULTSET_SIZE", -100, set=True) - - -@pytest.mark.onlynoncluster -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_list_keys(client): - result = client.graph().list_keys() - assert result == [] - - client.graph("G").query("CREATE (n)") - result = client.graph().list_keys() - assert result == ["G"] - - client.graph("X").query("CREATE (m)") - result = client.graph().list_keys() - assert result == ["G", "X"] - - client.delete("G") - client.rename("X", "Z") - result = client.graph().list_keys() - assert result == ["Z"] - - client.delete("Z") - result = client.graph().list_keys() - assert result == [] - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_multi_label(client): - redis_graph = client.graph("g") - - node = Node(label=["l", "ll"]) - redis_graph.add_node(node) - redis_graph.commit() - - query = "MATCH (n) RETURN n" - result = redis_graph.query(query) - result_node = result.result_set[0][0] - assert result_node == node - - try: - Node(label=1) - assert False - except AssertionError: - assert True - - try: - Node(label=["l", 1]) - assert False - except AssertionError: - assert True - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_cache_sync(client): - pass - return - # This test verifies that client internal graph schema cache stays - # in sync with the graph schema - # - # Client B will try to get Client A out of sync by: - # 1. deleting the graph - # 2. reconstructing the graph in a different order, this will casuse - # a difference in the current mapping between string IDs and the - # mapping Client A is aware of - # - # Client A should pick up on the changes by comparing graph versions - # and resyncing its cache. - - A = client.graph("cache-sync") - B = client.graph("cache-sync") - - # Build order: - # 1. introduce label 'L' and 'K' - # 2. introduce attribute 'x' and 'q' - # 3. introduce relationship-type 'R' and 'S' - - A.query("CREATE (:L)") - B.query("CREATE (:K)") - A.query("MATCH (n) SET n.x = 1") - B.query("MATCH (n) SET n.q = 1") - A.query("MATCH (n) CREATE (n)-[:R]->()") - B.query("MATCH (n) CREATE (n)-[:S]->()") - - # Cause client A to populate its cache - A.query("MATCH (n)-[e]->() RETURN n, e") - - assert len(A._labels) == 2 - assert len(A._properties) == 2 - assert len(A._relationship_types) == 2 - assert A._labels[0] == "L" - assert A._labels[1] == "K" - assert A._properties[0] == "x" - assert A._properties[1] == "q" - assert A._relationship_types[0] == "R" - assert A._relationship_types[1] == "S" - - # Have client B reconstruct the graph in a different order. - B.delete() - - # Build order: - # 1. introduce relationship-type 'R' - # 2. introduce label 'L' - # 3. introduce attribute 'x' - B.query("CREATE ()-[:S]->()") - B.query("CREATE ()-[:R]->()") - B.query("CREATE (:K)") - B.query("CREATE (:L)") - B.query("MATCH (n) SET n.q = 1") - B.query("MATCH (n) SET n.x = 1") - - # A's internal cached mapping is now out of sync - # issue a query and make sure A's cache is synced. - A.query("MATCH (n)-[e]->() RETURN n, e") - - assert len(A._labels) == 2 - assert len(A._properties) == 2 - assert len(A._relationship_types) == 2 - assert A._labels[0] == "K" - assert A._labels[1] == "L" - assert A._properties[0] == "q" - assert A._properties[1] == "x" - assert A._relationship_types[0] == "S" - assert A._relationship_types[1] == "R" - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_execution_plan(client): - redis_graph = client.graph("execution_plan") - create_query = """CREATE - (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), - (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), - (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" - redis_graph.query(create_query) - - result = redis_graph.execution_plan( - "MATCH (r:Rider)-[:rides]->(t:Team) WHERE t.name = $name RETURN r.name, t.name, $params", # noqa - {"name": "Yehuda"}, - ) - expected = "Results\n Project\n Conditional Traverse | (t)->(r:Rider)\n Filter\n Node By Label Scan | (t:Team)" # noqa - assert result == expected - - redis_graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_explain(client): - redis_graph = client.graph("execution_plan") - # graph creation / population - create_query = """CREATE -(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), -(:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), -(:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" - redis_graph.query(create_query) - - result = redis_graph.explain( - """MATCH (r:Rider)-[:rides]->(t:Team) -WHERE t.name = $name -RETURN r.name, t.name -UNION -MATCH (r:Rider)-[:rides]->(t:Team) -WHERE t.name = $name -RETURN r.name, t.name""", - {"name": "Yamaha"}, - ) - expected = """\ -Results -Distinct - Join - Project - Conditional Traverse | (t)->(r:Rider) - Filter - Node By Label Scan | (t:Team) - Project - Conditional Traverse | (t)->(r:Rider) - Filter - Node By Label Scan | (t:Team)""" - assert str(result).replace(" ", "").replace("\n", "") == expected.replace( - " ", "" - ).replace("\n", "") - - expected = Operation("Results").append_child( - Operation("Distinct").append_child( - Operation("Join") - .append_child( - Operation("Project").append_child( - Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( - Operation("Filter").append_child( - Operation("Node By Label Scan", "(t:Team)") - ) - ) - ) - ) - .append_child( - Operation("Project").append_child( - Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( - Operation("Filter").append_child( - Operation("Node By Label Scan", "(t:Team)") - ) - ) - ) - ) - ) - ) - - assert result.structured_plan == expected - - result = redis_graph.explain( - """MATCH (r:Rider), (t:Team) - RETURN r.name, t.name""" - ) - expected = """\ -Results -Project - Cartesian Product - Node By Label Scan | (r:Rider) - Node By Label Scan | (t:Team)""" - assert str(result).replace(" ", "").replace("\n", "") == expected.replace( - " ", "" - ).replace("\n", "") - - expected = Operation("Results").append_child( - Operation("Project").append_child( - Operation("Cartesian Product") - .append_child(Operation("Node By Label Scan")) - .append_child(Operation("Node By Label Scan")) - ) - ) - - assert result.structured_plan == expected - - redis_graph.delete() - - -@pytest.mark.redismod -@skip_if_resp_version(3) -def test_resultset_statistics(client): - with patch.object(target=QueryResult, attribute="_get_stat") as mock_get_stats: - result = client.graph().query("RETURN 1") - result.labels_added - mock_get_stats.assert_called_with(LABELS_ADDED) - result.labels_removed - mock_get_stats.assert_called_with(LABELS_REMOVED) - result.nodes_created - mock_get_stats.assert_called_with(NODES_CREATED) - result.nodes_deleted - mock_get_stats.assert_called_with(NODES_DELETED) - result.properties_set - mock_get_stats.assert_called_with(PROPERTIES_SET) - result.properties_removed - mock_get_stats.assert_called_with(PROPERTIES_REMOVED) - result.relationships_created - mock_get_stats.assert_called_with(RELATIONSHIPS_CREATED) - result.relationships_deleted - mock_get_stats.assert_called_with(RELATIONSHIPS_DELETED) - result.indices_created - mock_get_stats.assert_called_with(INDICES_CREATED) - result.indices_deleted - mock_get_stats.assert_called_with(INDICES_DELETED) - result.cached_execution - mock_get_stats.assert_called_with(CACHED_EXECUTION) - result.run_time_ms - mock_get_stats.assert_called_with(INTERNAL_EXECUTION_TIME) diff --git a/tests/test_graph_utils/test_edge.py b/tests/test_graph_utils/test_edge.py index 1918a6ff44..ccf38b7b70 100644 --- a/tests/test_graph_utils/test_edge.py +++ b/tests/test_graph_utils/test_edge.py @@ -2,7 +2,6 @@ from redis.commands.graph import edge, node -@pytest.mark.redismod def test_init(): with pytest.raises(AssertionError): edge.Edge(None, None, None) @@ -14,7 +13,6 @@ def test_init(): ) -@pytest.mark.redismod def test_to_string(): props_result = edge.Edge( node.Node(), None, node.Node(), properties={"a": "a", "b": 10} @@ -27,7 +25,6 @@ def test_to_string(): assert no_props_result == "" -@pytest.mark.redismod def test_stringify(): john = node.Node( alias="a", @@ -60,7 +57,6 @@ def test_stringify(): ) -@pytest.mark.redismod def test_comparison(): node1 = node.Node(node_id=1) node2 = node.Node(node_id=2) diff --git a/tests/test_graph_utils/test_node.py b/tests/test_graph_utils/test_node.py index 22e6d59414..1c5d449946 100644 --- a/tests/test_graph_utils/test_node.py +++ b/tests/test_graph_utils/test_node.py @@ -12,7 +12,6 @@ def fixture(): return no_args, no_props, props_only, no_label, multi_label -@pytest.mark.redismod def test_to_string(fixture): no_args, no_props, props_only, no_label, multi_label = fixture assert no_args.to_string() == "" @@ -22,7 +21,6 @@ def test_to_string(fixture): assert multi_label.to_string() == "" -@pytest.mark.redismod def test_stringify(fixture): no_args, no_props, props_only, no_label, multi_label = fixture assert str(no_args) == "()" @@ -32,7 +30,6 @@ def test_stringify(fixture): assert str(multi_label) == "(alias:l:ll)" -@pytest.mark.redismod def test_comparison(fixture): no_args, no_props, props_only, no_label, multi_label = fixture diff --git a/tests/test_graph_utils/test_path.py b/tests/test_graph_utils/test_path.py index 1bd38efab4..459ada9fe0 100644 --- a/tests/test_graph_utils/test_path.py +++ b/tests/test_graph_utils/test_path.py @@ -2,7 +2,6 @@ from redis.commands.graph import edge, node, path -@pytest.mark.redismod def test_init(): with pytest.raises(TypeError): path.Path(None, None) @@ -12,7 +11,6 @@ def test_init(): assert isinstance(path.Path([], []), path.Path) -@pytest.mark.redismod def test_new_empty_path(): new_empty_path = path.Path.new_empty_path() assert isinstance(new_empty_path, path.Path) @@ -20,7 +18,6 @@ def test_new_empty_path(): assert new_empty_path._edges == [] -@pytest.mark.redismod def test_wrong_flows(): node_1 = node.Node(node_id=1) node_2 = node.Node(node_id=2) @@ -42,7 +39,6 @@ def test_wrong_flows(): p.add_edge(edge_2) -@pytest.mark.redismod def test_nodes_and_edges(): node_1 = node.Node(node_id=1) node_2 = node.Node(node_id=2) @@ -69,7 +65,6 @@ def test_nodes_and_edges(): assert 2 == p.nodes_count() -@pytest.mark.redismod def test_compare(): node_1 = node.Node(node_id=1) node_2 = node.Node(node_id=2) diff --git a/tests/test_json.py b/tests/test_json.py index f4cea73787..ab65a37f7f 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -1,20 +1,19 @@ import pytest import redis -from redis import Redis, exceptions +from redis import exceptions, Redis from redis.commands.json.decoders import decode_list, unstring from redis.commands.json.path import Path -from .conftest import _get_client, assert_resp_response, skip_ifmodversion_lt +from .conftest import assert_resp_response, skip_ifmodversion_lt, _get_client @pytest.fixture -def client(request, stack_url): - r = _get_client(Redis, request, decode_responses=True, from_url=stack_url) +def client(request): + r = _get_client(Redis, request, decode_responses=True) r.flushdb() return r -@pytest.mark.redismod def test_json_setbinarykey(client): d = {"hello": "world", b"some": "value"} with pytest.raises(TypeError): @@ -22,7 +21,6 @@ def test_json_setbinarykey(client): assert client.json().set("somekey", Path.root_path(), d, decode_keys=True) -@pytest.mark.redismod def test_json_setgetdeleteforget(client): assert client.json().set("foo", Path.root_path(), "bar") assert client.json().get("foo") == "bar" @@ -32,13 +30,11 @@ def test_json_setgetdeleteforget(client): assert client.exists("foo") == 0 -@pytest.mark.redismod def test_jsonget(client): client.json().set("foo", Path.root_path(), "bar") assert client.json().get("foo") == "bar" -@pytest.mark.redismod def test_json_get_jset(client): assert client.json().set("foo", Path.root_path(), "bar") assert client.json().get("foo") == "bar" @@ -47,7 +43,6 @@ def test_json_get_jset(client): assert client.exists("foo") == 0 -@pytest.mark.redismod @skip_ifmodversion_lt("2.06.00", "ReJSON") # todo: update after the release def test_json_merge(client): # Test with root path $ @@ -80,7 +75,6 @@ def test_json_merge(client): } -@pytest.mark.redismod def test_nonascii_setgetdelete(client): assert client.json().set("notascii", Path.root_path(), "hyvää-élève") assert client.json().get("notascii", no_escape=True) == "hyvää-élève" @@ -88,7 +82,6 @@ def test_nonascii_setgetdelete(client): assert client.exists("notascii") == 0 -@pytest.mark.redismod def test_jsonsetexistentialmodifiersshouldsucceed(client): obj = {"foo": "bar"} assert client.json().set("obj", Path.root_path(), obj) @@ -106,7 +99,6 @@ def test_jsonsetexistentialmodifiersshouldsucceed(client): client.json().set("obj", Path("foo"), "baz", nx=True, xx=True) -@pytest.mark.redismod def test_mgetshouldsucceed(client): client.json().set("1", Path.root_path(), 1) client.json().set("2", Path.root_path(), 2) @@ -116,7 +108,6 @@ def test_mgetshouldsucceed(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("2.06.00", "ReJSON") def test_mset(client): client.json().mset([("1", Path.root_path(), 1), ("2", Path.root_path(), 2)]) @@ -125,7 +116,6 @@ def test_mset(client): assert client.json().mget(["1", "2"], Path.root_path()) == [1, 2] -@pytest.mark.redismod @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release def test_clear(client): client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) @@ -133,7 +123,6 @@ def test_clear(client): assert_resp_response(client, client.json().get("arr"), [], []) -@pytest.mark.redismod def test_type(client): client.json().set("1", Path.root_path(), 1) assert_resp_response( @@ -142,7 +131,6 @@ def test_type(client): assert_resp_response(client, client.json().type("1"), "integer", ["integer"]) -@pytest.mark.redismod def test_numincrby(client): client.json().set("num", Path.root_path(), 1) assert_resp_response( @@ -156,7 +144,6 @@ def test_numincrby(client): ) -@pytest.mark.redismod def test_nummultby(client): client.json().set("num", Path.root_path(), 1) @@ -172,7 +159,6 @@ def test_nummultby(client): ) -@pytest.mark.redismod @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release def test_toggle(client): client.json().set("bool", Path.root_path(), False) @@ -184,14 +170,12 @@ def test_toggle(client): client.json().toggle("num", Path.root_path()) -@pytest.mark.redismod def test_strappend(client): client.json().set("jsonkey", Path.root_path(), "foo") assert 6 == client.json().strappend("jsonkey", "bar") assert "foobar" == client.json().get("jsonkey", Path.root_path()) -@pytest.mark.redismod def test_strlen(client): client.json().set("str", Path.root_path(), "foo") assert 3 == client.json().strlen("str", Path.root_path()) @@ -200,7 +184,6 @@ def test_strlen(client): assert 6 == client.json().strlen("str") -@pytest.mark.redismod def test_arrappend(client): client.json().set("arr", Path.root_path(), [1]) assert 2 == client.json().arrappend("arr", Path.root_path(), 2) @@ -208,7 +191,6 @@ def test_arrappend(client): assert 7 == client.json().arrappend("arr", Path.root_path(), *[5, 6, 7]) -@pytest.mark.redismod def test_arrindex(client): client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 1 == client.json().arrindex("arr", Path.root_path(), 1) @@ -220,7 +202,6 @@ def test_arrindex(client): assert -1 == client.json().arrindex("arr", Path.root_path(), 4, start=1, stop=3) -@pytest.mark.redismod def test_arrinsert(client): client.json().set("arr", Path.root_path(), [0, 4]) assert 5 - -client.json().arrinsert("arr", Path.root_path(), 1, *[1, 2, 3]) @@ -232,7 +213,6 @@ def test_arrinsert(client): assert client.json().get("val2") == [["some", "thing"], 5, 6, 7, 8, 9] -@pytest.mark.redismod def test_arrlen(client): client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 5 == client.json().arrlen("arr", Path.root_path()) @@ -240,7 +220,6 @@ def test_arrlen(client): assert client.json().arrlen("fakekey") is None -@pytest.mark.redismod def test_arrpop(client): client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 4 == client.json().arrpop("arr", Path.root_path(), 4) @@ -258,7 +237,6 @@ def test_arrpop(client): assert client.json().arrpop("arr") is None -@pytest.mark.redismod def test_arrtrim(client): client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4]) assert 3 == client.json().arrtrim("arr", Path.root_path(), 1, 3) @@ -281,7 +259,6 @@ def test_arrtrim(client): assert 0 == client.json().arrtrim("arr", Path.root_path(), 9, 11) -@pytest.mark.redismod def test_resp(client): obj = {"foo": "bar", "baz": 1, "qaz": True} client.json().set("obj", Path.root_path(), obj) @@ -291,7 +268,6 @@ def test_resp(client): assert isinstance(client.json().resp("obj"), list) -@pytest.mark.redismod def test_objkeys(client): obj = {"foo": "bar", "baz": "qaz"} client.json().set("obj", Path.root_path(), obj) @@ -308,7 +284,6 @@ def test_objkeys(client): assert client.json().objkeys("fakekey") is None -@pytest.mark.redismod def test_objlen(client): obj = {"foo": "bar", "baz": "qaz"} client.json().set("obj", Path.root_path(), obj) @@ -318,7 +293,6 @@ def test_objlen(client): assert len(obj) == client.json().objlen("obj") -@pytest.mark.redismod def test_json_commands_in_pipeline(client): p = client.json().pipeline() p.set("foo", Path.root_path(), "bar") @@ -342,7 +316,6 @@ def test_json_commands_in_pipeline(client): assert client.get("foo") is None -@pytest.mark.redismod def test_json_delete_with_dollar(client): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} assert client.json().set("doc1", "$", doc1) @@ -394,7 +367,6 @@ def test_json_delete_with_dollar(client): client.json().delete("not_a_document", "..a") -@pytest.mark.redismod def test_json_forget_with_dollar(client): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} assert client.json().set("doc1", "$", doc1) @@ -446,7 +418,6 @@ def test_json_forget_with_dollar(client): client.json().forget("not_a_document", "..a") -@pytest.mark.redismod def test_json_mget_dollar(client): # Test mget with multi paths client.json().set( @@ -476,7 +447,6 @@ def test_json_mget_dollar(client): assert client.json().mget(["missing_doc1", "missing_doc2"], "$..a") == [None, None] -@pytest.mark.redismod def test_numby_commands_dollar(client): # Test NUMINCRBY client.json().set("doc1", "$", {"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}) @@ -522,7 +492,6 @@ def test_numby_commands_dollar(client): ) -@pytest.mark.redismod def test_strappend_dollar(client): client.json().set( "doc1", "$", {"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}} @@ -553,7 +522,6 @@ def test_strappend_dollar(client): client.json().strappend("doc1", "piu") -@pytest.mark.redismod def test_strlen_dollar(client): # Test multi client.json().set( @@ -574,7 +542,6 @@ def test_strlen_dollar(client): client.json().strlen("non_existing_doc", "$..a") -@pytest.mark.redismod def test_arrappend_dollar(client): client.json().set( "doc1", @@ -649,7 +616,6 @@ def test_arrappend_dollar(client): client.json().arrappend("non_existing_doc", "$..a") -@pytest.mark.redismod def test_arrinsert_dollar(client): client.json().set( "doc1", @@ -688,7 +654,6 @@ def test_arrinsert_dollar(client): client.json().arrappend("non_existing_doc", "$..a") -@pytest.mark.redismod def test_arrlen_dollar(client): client.json().set( "doc1", @@ -737,7 +702,6 @@ def test_arrlen_dollar(client): assert client.json().arrlen("non_existing_doc", "..a") is None -@pytest.mark.redismod def test_arrpop_dollar(client): client.json().set( "doc1", @@ -779,7 +743,6 @@ def test_arrpop_dollar(client): client.json().arrpop("non_existing_doc", "..a") -@pytest.mark.redismod def test_arrtrim_dollar(client): client.json().set( "doc1", @@ -832,7 +795,6 @@ def test_arrtrim_dollar(client): client.json().arrtrim("non_existing_doc", "..a", 1, 1) -@pytest.mark.redismod def test_objkeys_dollar(client): client.json().set( "doc1", @@ -862,7 +824,6 @@ def test_objkeys_dollar(client): assert client.json().objkeys("doc1", "$..nowhere") == [] -@pytest.mark.redismod def test_objlen_dollar(client): client.json().set( "doc1", @@ -917,7 +878,6 @@ def load_types_data(nested_key_name): return jdata, types -@pytest.mark.redismod def test_type_dollar(client): jdata, jtypes = load_types_data("a") client.json().set("doc1", "$", jdata) @@ -935,7 +895,6 @@ def test_type_dollar(client): ) -@pytest.mark.redismod def test_clear_dollar(client): client.json().set( "doc1", @@ -986,7 +945,6 @@ def test_clear_dollar(client): client.json().clear("non_existing_doc", "$..a") -@pytest.mark.redismod def test_toggle_dollar(client): client.json().set( "doc1", @@ -1015,7 +973,6 @@ def test_toggle_dollar(client): client.json().toggle("non_existing_doc", "$..a") -@pytest.mark.redismod def test_resp_dollar(client): data = { "L1": { @@ -1244,7 +1201,6 @@ def test_resp_dollar(client): client.json().resp("non_existing_doc", "$..a") -@pytest.mark.redismod def test_arrindex_dollar(client): client.json().set( "store", @@ -1468,7 +1424,6 @@ def test_arrindex_dollar(client): assert client.json().arrindex("test_None", "..nested2_not_found.arr", "None") == 0 -@pytest.mark.redismod def test_decoders_and_unstring(): assert unstring("4") == 4 assert unstring("45.55") == 45.55 @@ -1479,7 +1434,6 @@ def test_decoders_and_unstring(): assert decode_list(["hello", b"world"]) == ["hello", "world"] -@pytest.mark.redismod def test_custom_decoder(client): import json @@ -1495,7 +1449,6 @@ def test_custom_decoder(client): assert not isinstance(cj.__decoder__, json.JSONDecoder) -@pytest.mark.redismod def test_set_file(client): import json import tempfile @@ -1514,7 +1467,6 @@ def test_set_file(client): client.json().set_file("test2", Path.root_path(), nojsonfile.name) -@pytest.mark.redismod def test_set_path(client): import json import tempfile diff --git a/tests/test_search.py b/tests/test_search.py index dde59f0f87..da9871aba8 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -108,13 +108,12 @@ def createIndex(client, num_docs=100, definition=None): @pytest.fixture -def client(request, stack_url): - r = _get_client(redis.Redis, request, decode_responses=True, from_url=stack_url) +def client(request): + r = _get_client(redis.Redis, request, decode_responses=True) r.flushdb() return r -@pytest.mark.redismod def test_client(client): num_docs = 500 createIndex(client.ft(), num_docs=num_docs) @@ -310,7 +309,6 @@ def test_client(client): client.ft().delete_document("doc-5ghs2") -@pytest.mark.redismod @pytest.mark.onlynoncluster def test_scores(client): client.ft().create_index((TextField("txt"),)) @@ -332,7 +330,6 @@ def test_scores(client): assert "doc1" == res["results"][1]["id"] -@pytest.mark.redismod def test_stopwords(client): client.ft().create_index((TextField("txt"),), stopwords=["foo", "bar", "baz"]) client.hset("doc1", mapping={"txt": "foo bar"}) @@ -350,7 +347,6 @@ def test_stopwords(client): assert 1 == res2["total_results"] -@pytest.mark.redismod def test_filters(client): client.ft().create_index((TextField("txt"), NumericField("num"), GeoField("loc"))) client.hset( @@ -403,7 +399,6 @@ def test_filters(client): assert ["doc1", "doc2"] == res -@pytest.mark.redismod def test_sort_by(client): client.ft().create_index((TextField("txt"), NumericField("num", sortable=True))) client.hset("doc1", mapping={"txt": "foo bar", "num": 1}) @@ -435,7 +430,6 @@ def test_sort_by(client): assert "doc3" == res2["results"][0]["id"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") def test_drop_index(client): """ @@ -454,7 +448,6 @@ def test_drop_index(client): assert i == keep_docs[1] -@pytest.mark.redismod def test_example(client): # Creating the index definition and schema client.ft().create_index((TextField("title", weight=5.0), TextField("body"))) @@ -475,7 +468,6 @@ def test_example(client): assert res is not None -@pytest.mark.redismod @skip_if_redis_enterprise() def test_auto_complete(client): n = 0 @@ -525,7 +517,6 @@ def test_auto_complete(client): assert sug.payload.startswith("pl") -@pytest.mark.redismod def test_no_index(client): client.ft().create_index( ( @@ -603,20 +594,17 @@ def test_no_index(client): TagField("name", no_index=True, sortable=False) -@pytest.mark.redismod def test_explain(client): client.ft().create_index((TextField("f1"), TextField("f2"), TextField("f3"))) res = client.ft().explain("@f3:f3_val @f2:f2_val @f1:f1_val") assert res -@pytest.mark.redismod def test_explaincli(client): with pytest.raises(NotImplementedError): client.ft().explain_cli("foo") -@pytest.mark.redismod def test_summarize(client): createIndex(client.ft()) waitForIndex(client, getattr(client.ft(), "index_name", "idx")) @@ -659,7 +647,6 @@ def test_summarize(client): ) -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") def test_alias(client): index1 = getClient(client) @@ -722,7 +709,6 @@ def test_alias(client): alias_client2.search("*").docs[0] -@pytest.mark.redismod @pytest.mark.xfail(strict=False) def test_alias_basic(client): # Creating a client with one index @@ -771,7 +757,6 @@ def test_alias_basic(client): _ = alias_client2.search("*").docs[0] -@pytest.mark.redismod def test_textfield_sortable_nostem(client): # Creating the index definition with sortable and no_stem client.ft().create_index((TextField("txt", sortable=True, no_stem=True),)) @@ -786,7 +771,6 @@ def test_textfield_sortable_nostem(client): assert "NOSTEM" in response["attributes"][0]["flags"] -@pytest.mark.redismod def test_alter_schema_add(client): # Creating the index definition and schema client.ft().create_index(TextField("title")) @@ -810,7 +794,6 @@ def test_alter_schema_add(client): assert 1 == res["total_results"] -@pytest.mark.redismod def test_spell_check(client): client.ft().create_index((TextField("f1"), TextField("f2"))) @@ -879,7 +862,6 @@ def test_spell_check(client): assert res == {"results": {}} -@pytest.mark.redismod def test_dict_operations(client): client.ft().create_index((TextField("f1"), TextField("f2"))) # Add three items @@ -898,7 +880,6 @@ def test_dict_operations(client): client.ft().dict_del("custom_dict", *res) -@pytest.mark.redismod def test_phonetic_matcher(client): client.ft().create_index((TextField("name"),)) client.hset("doc1", mapping={"name": "Jon"}) @@ -930,7 +911,6 @@ def test_phonetic_matcher(client): ) -@pytest.mark.redismod @pytest.mark.onlynoncluster def test_scorer(client): client.ft().create_index((TextField("description"),)) @@ -978,7 +958,6 @@ def test_scorer(client): assert 0.0 == res["results"][0]["score"] -@pytest.mark.redismod def test_get(client): client.ft().create_index((TextField("f1"), TextField("f2"))) @@ -1001,7 +980,6 @@ def test_get(client): ] == client.ft().get("doc1", "doc2") -@pytest.mark.redismod @pytest.mark.onlynoncluster @skip_ifmodversion_lt("2.2.0", "search") def test_config(client): @@ -1014,7 +992,6 @@ def test_config(client): assert "100" == res["TIMEOUT"] -@pytest.mark.redismod @pytest.mark.onlynoncluster def test_aggregations_groupby(client): # Creating the index definition and schema @@ -1264,7 +1241,6 @@ def test_aggregations_groupby(client): ] -@pytest.mark.redismod def test_aggregations_sort_by_and_limit(client): client.ft().create_index((TextField("t1"), TextField("t2"))) @@ -1323,7 +1299,6 @@ def test_aggregations_sort_by_and_limit(client): assert res["results"][0]["extra_attributes"] == {"t1": "b"} -@pytest.mark.redismod def test_aggregations_load(client): client.ft().create_index((TextField("t1"), TextField("t2"))) @@ -1361,7 +1336,6 @@ def test_aggregations_load(client): assert res["results"][0]["extra_attributes"] == {"t1": "hello", "t2": "world"} -@pytest.mark.redismod def test_aggregations_apply(client): client.ft().create_index( ( @@ -1394,7 +1368,6 @@ def test_aggregations_apply(client): assert res_set == {"6373878785249699840", "6373878758592700416"} -@pytest.mark.redismod def test_aggregations_filter(client): client.ft().create_index( (TextField("name", sortable=True), NumericField("age", sortable=True)) @@ -1440,7 +1413,6 @@ def test_aggregations_filter(client): assert res["results"][1]["extra_attributes"] == {"age": "25"} -@pytest.mark.redismod @skip_ifmodversion_lt("2.10.05", "search") def test_aggregations_add_scores(client): client.ft().create_index( @@ -1466,7 +1438,6 @@ def test_aggregations_add_scores(client): assert res.rows[1] == ["__score", "0.2"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") def test_index_definition(client): """ @@ -1510,20 +1481,18 @@ def test_index_definition(client): createIndex(client.ft(), num_docs=500, definition=definition) -@pytest.mark.redismod @pytest.mark.onlynoncluster @skip_if_redis_enterprise() def test_expire(client): client.ft().create_index((TextField("txt", sortable=True),), temporary=4) - ttl = client.execute_command("ft.debug", "TTL", "idx") + ttl = client.execute_command("_ft.debug", "TTL", "idx") assert ttl > 2 while ttl > 2: - ttl = client.execute_command("ft.debug", "TTL", "idx") + ttl = client.execute_command("_ft.debug", "TTL", "idx") time.sleep(0.01) -@pytest.mark.redismod def test_skip_initial_scan(client): client.hset("doc1", "foo", "bar") q = Query("@foo:bar") @@ -1536,7 +1505,6 @@ def test_skip_initial_scan(client): assert res["total_results"] == 0 -@pytest.mark.redismod def test_summarize_disabled_nooffset(client): client.ft().create_index((TextField("txt"),), no_term_offsets=True) client.hset("doc1", mapping={"txt": "foo bar"}) @@ -1544,7 +1512,6 @@ def test_summarize_disabled_nooffset(client): client.ft().search(Query("foo").summarize(fields=["txt"])) -@pytest.mark.redismod def test_summarize_disabled_nohl(client): client.ft().create_index((TextField("txt"),), no_highlight=True) client.hset("doc1", mapping={"txt": "foo bar"}) @@ -1552,7 +1519,6 @@ def test_summarize_disabled_nohl(client): client.ft().search(Query("foo").summarize(fields=["txt"])) -@pytest.mark.redismod def test_max_text_fields(client): # Creating the index definition client.ft().create_index((TextField("f0"),)) @@ -1571,7 +1537,6 @@ def test_max_text_fields(client): client.ft().alter_schema_add((TextField(f"f{x}"),)) -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") def test_create_client_definition(client): """ @@ -1589,7 +1554,6 @@ def test_create_client_definition(client): assert 495 == int(info["num_docs"]) -@pytest.mark.redismod @skip_ifmodversion_lt("2.0.0", "search") def test_create_client_definition_hash(client): """ @@ -1607,7 +1571,6 @@ def test_create_client_definition_hash(client): assert 495 == int(info["num_docs"]) -@pytest.mark.redismod @skip_ifmodversion_lt("2.2.0", "search") def test_create_client_definition_json(client): """ @@ -1632,7 +1595,6 @@ def test_create_client_definition_json(client): assert res["total_results"] == 1 -@pytest.mark.redismod @skip_ifmodversion_lt("2.2.0", "search") def test_fields_as_name(client): # create index @@ -1660,7 +1622,6 @@ def test_fields_as_name(client): assert "25" == res["results"][0]["extra_attributes"]["just_a_number"] -@pytest.mark.redismod def test_casesensitive(client): # create index SCHEMA = (TagField("t", case_sensitive=False),) @@ -1694,7 +1655,6 @@ def test_casesensitive(client): assert "1" == res["results"][0]["id"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.2.0", "search") def test_search_return_fields(client): res = client.json().set( @@ -1732,7 +1692,6 @@ def test_search_return_fields(client): assert "telmatosaurus" == total["results"][0]["extra_attributes"]["txt"] -@pytest.mark.redismod @skip_if_resp_version(3) def test_binary_and_text_fields(client): fake_vec = np.array([0.1, 0.2, 0.3, 0.4], dtype=np.float32) @@ -1780,7 +1739,6 @@ def test_binary_and_text_fields(client): ), "The text field is not decoded correctly" -@pytest.mark.redismod def test_synupdate(client): definition = IndexDefinition(index_type=IndexType.HASH) client.ft().create_index( @@ -1804,7 +1762,6 @@ def test_synupdate(client): assert res["results"][0]["extra_attributes"]["body"] == "another test" -@pytest.mark.redismod def test_syndump(client): definition = IndexDefinition(index_type=IndexType.HASH) client.ft().create_index( @@ -1825,7 +1782,6 @@ def test_syndump(client): } -@pytest.mark.redismod @skip_ifmodversion_lt("2.2.0", "search") def test_create_json_with_alias(client): """ @@ -1870,7 +1826,6 @@ def test_create_json_with_alias(client): client.ft().search("@$.name:henry") -@pytest.mark.redismod @skip_ifmodversion_lt("2.2.0", "search") def test_json_with_multipath(client): """ @@ -1914,7 +1869,6 @@ def test_json_with_multipath(client): assert res["total_results"] == 1 -@pytest.mark.redismod @skip_ifmodversion_lt("2.2.0", "search") def test_json_with_jsonpath(client): definition = IndexDefinition(index_type=IndexType.JSON) @@ -1964,7 +1918,6 @@ def test_json_with_jsonpath(client): assert res["results"][0]["extra_attributes"]["name"] == "RediSearch" -@pytest.mark.redismod @pytest.mark.onlynoncluster @skip_if_redis_enterprise() def test_profile(client): @@ -2013,7 +1966,6 @@ def test_profile(client): assert len(res["results"]) == 2 # check also the search result -@pytest.mark.redismod @pytest.mark.onlynoncluster def test_profile_limited(client): client.ft().create_index((TextField("t"),)) @@ -2050,7 +2002,6 @@ def test_profile_limited(client): assert len(res["results"]) == 3 # check also the search result -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_profile_query_params(client): client.ft().create_index( @@ -2081,7 +2032,6 @@ def test_profile_query_params(client): assert "0" == res["results"][0]["extra_attributes"]["__v_score"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_vector_field(client): client.flushdb() @@ -2108,7 +2058,6 @@ def test_vector_field(client): assert "0" == res["results"][0]["extra_attributes"]["__v_score"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_vector_field_error(r): r.flushdb() @@ -2122,7 +2071,6 @@ def test_vector_field_error(r): r.ft().create_index((VectorField("v", "SORT", {}),)) -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_text_params(client): client.flushdb() @@ -2145,7 +2093,6 @@ def test_text_params(client): assert "doc2" == res["results"][1]["id"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_numeric_params(client): client.flushdb() @@ -2169,7 +2116,6 @@ def test_numeric_params(client): assert "doc2" == res["results"][1]["id"] -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_geo_params(client): client.ft().create_index(GeoField("g")) @@ -2183,7 +2129,6 @@ def test_geo_params(client): _assert_search_result(client, res, ["doc1", "doc2", "doc3"]) -@pytest.mark.redismod def test_geoshapes_query_intersects_and_disjoint(client): client.ft().create_index((GeoShapeField("g", coord_system=GeoShapeField.FLAT))) client.hset("doc_point1", mapping={"g": "POINT (10 10)"}) @@ -2206,7 +2151,6 @@ def test_geoshapes_query_intersects_and_disjoint(client): _assert_search_result(client, disjunction, ["doc_point1", "doc_polygon2"]) -@pytest.mark.redismod @skip_ifmodversion_lt("2.10.0", "search") def test_geoshapes_query_contains_and_within(client): client.ft().create_index((GeoShapeField("g", coord_system=GeoShapeField.FLAT))) @@ -2236,7 +2180,6 @@ def test_geoshapes_query_contains_and_within(client): _assert_search_result(client, within, ["doc_point2", "doc_polygon1"]) -@pytest.mark.redismod @skip_if_redis_enterprise() def test_search_commands_in_pipeline(client): p = client.ft().pipeline() @@ -2266,7 +2209,6 @@ def test_search_commands_in_pipeline(client): ) -@pytest.mark.redismod @pytest.mark.onlynoncluster @skip_ifmodversion_lt("2.4.3", "search") def test_dialect_config(client): @@ -2277,7 +2219,6 @@ def test_dialect_config(client): client.ft().config_set("DEFAULT_DIALECT", 0) -@pytest.mark.redismod @skip_ifmodversion_lt("2.4.3", "search") def test_dialect(client): client.ft().create_index( @@ -2311,7 +2252,6 @@ def test_dialect(client): assert "Syntax error" in str(err) -@pytest.mark.redismod def test_expire_while_search(client: redis.Redis): client.ft().create_index((TextField("txt"),)) client.hset("hset:1", "txt", "a") @@ -2333,7 +2273,6 @@ def test_expire_while_search(client: redis.Redis): assert 2 == client.ft().search(Query("*"))["total_results"] -@pytest.mark.redismod @pytest.mark.experimental def test_withsuffixtrie(client: redis.Redis): # create index @@ -2375,7 +2314,6 @@ def test_withsuffixtrie(client: redis.Redis): assert "WITHSUFFIXTRIE" in info["attributes"][0]["flags"] -@pytest.mark.redismod def test_query_timeout(r: redis.Redis): q1 = Query("foo").timeout(5000) assert q1.get_args() == ["foo", "TIMEOUT", 5000, "LIMIT", 0, 10] @@ -2386,7 +2324,6 @@ def test_query_timeout(r: redis.Redis): r.ft().search(q2) -@pytest.mark.redismod def test_geoshape(client: redis.Redis): client.ft().create_index(GeoShapeField("geom", GeoShapeField.FLAT)) waitForIndex(client, getattr(client.ft(), "index_name", "idx")) @@ -2402,7 +2339,6 @@ def test_geoshape(client: redis.Redis): _assert_search_result(client, result, ["small", "large"]) -@pytest.mark.redismod def test_search_missing_fields(client): definition = IndexDefinition(prefix=["property:"], index_type=IndexType.HASH) @@ -2469,7 +2405,6 @@ def test_search_missing_fields(client): _assert_search_result(client, res, ["property:1", "property:2"]) -@pytest.mark.redismod def test_search_empty_fields(client): definition = IndexDefinition(prefix=["property:"], index_type=IndexType.HASH) @@ -2540,7 +2475,6 @@ def test_search_empty_fields(client): _assert_search_result(client, res, ["property:1", "property:2"]) -@pytest.mark.redismod def test_special_characters_in_fields(client): definition = IndexDefinition(prefix=["resource:"], index_type=IndexType.HASH) diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py index fb604d0329..32061e9df4 100644 --- a/tests/test_timeseries.py +++ b/tests/test_timeseries.py @@ -14,9 +14,9 @@ @pytest.fixture() -def decoded_r(request, stack_url): +def decoded_r(request): with _get_client( - redis.Redis, request, decode_responses=True, from_url=stack_url + redis.Redis, request, decode_responses=True ) as client: yield client @@ -27,7 +27,6 @@ def client(decoded_r): return decoded_r -@pytest.mark.redismod def test_create(client): assert client.ts().create(1) assert client.ts().create(2, retention_msecs=5) @@ -45,7 +44,6 @@ def test_create(client): assert_resp_response(client, 128, info.get("chunk_size"), info.get("chunkSize")) -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") def test_create_duplicate_policy(client): # Test for duplicate policy @@ -61,7 +59,6 @@ def test_create_duplicate_policy(client): ) -@pytest.mark.redismod def test_alter(client): assert client.ts().create(1) info = client.ts().info(1) @@ -82,7 +79,6 @@ def test_alter(client): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") def test_alter_duplicate_policy(client): assert client.ts().create(1) @@ -97,7 +93,6 @@ def test_alter_duplicate_policy(client): ) -@pytest.mark.redismod def test_add(client): assert 1 == client.ts().add(1, 1, 1) assert 2 == client.ts().add(2, 2, 3, retention_msecs=10) @@ -120,7 +115,6 @@ def test_add(client): assert_resp_response(client, 128, info.get("chunk_size"), info.get("chunkSize")) -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") def test_add_on_duplicate(client): # Test for duplicate policy BLOCK @@ -151,13 +145,11 @@ def test_add_on_duplicate(client): assert 5.0 == client.ts().get("time-serie-add-ooo-min")[1] -@pytest.mark.redismod def test_madd(client): client.ts().create("a") assert [1, 2, 3] == client.ts().madd([("a", 1, 5), ("a", 2, 10), ("a", 3, 15)]) -@pytest.mark.redismod def test_madd_missing_timeseries(client): response = client.ts().madd([("a", 1, 5), ("a", 2, 10)]) assert isinstance(response, list) @@ -166,7 +158,6 @@ def test_madd_missing_timeseries(client): assert isinstance(response[1], redis.ResponseError) -@pytest.mark.redismod def test_incrby_decrby(client): for _ in range(100): assert client.ts().incrby(1, 1) @@ -195,7 +186,6 @@ def test_incrby_decrby(client): assert_resp_response(client, 128, info.get("chunk_size"), info.get("chunkSize")) -@pytest.mark.redismod def test_create_and_delete_rule(client): # test rule creation time = 100 @@ -219,7 +209,6 @@ def test_create_and_delete_rule(client): assert not info["rules"] -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") def test_del_range(client): try: @@ -234,7 +223,6 @@ def test_del_range(client): assert_resp_response(client, client.ts().range(1, 22, 22), [(22, 1.0)], [[22, 1.0]]) -@pytest.mark.redismod def test_range(client): for i in range(100): client.ts().add(1, i, i % 7) @@ -249,7 +237,6 @@ def test_range(client): assert 10 == len(client.ts().range(1, 0, 500, count=10)) -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") def test_range_advanced(client): for i in range(100): @@ -279,7 +266,6 @@ def test_range_advanced(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_range_latest(client: redis.Redis): timeseries = client.ts() @@ -304,7 +290,6 @@ def test_range_latest(client: redis.Redis): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_range_bucket_timestamp(client: redis.Redis): timeseries = client.ts() @@ -338,7 +323,6 @@ def test_range_bucket_timestamp(client: redis.Redis): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_range_empty(client: redis.Redis): timeseries = client.ts() @@ -383,7 +367,6 @@ def test_range_empty(client: redis.Redis): assert_resp_response(client, res, resp2_expected, resp3_expected) -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") def test_rev_range(client): for i in range(100): @@ -432,7 +415,6 @@ def test_rev_range(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_revrange_latest(client: redis.Redis): timeseries = client.ts() @@ -451,7 +433,6 @@ def test_revrange_latest(client: redis.Redis): assert_resp_response(client, res, [(0, 4.0)], [[0, 4.0]]) -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_revrange_bucket_timestamp(client: redis.Redis): timeseries = client.ts() @@ -485,7 +466,6 @@ def test_revrange_bucket_timestamp(client: redis.Redis): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_revrange_empty(client: redis.Redis): timeseries = client.ts() @@ -531,7 +511,6 @@ def test_revrange_empty(client: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod def test_mrange(client): client.ts().create(1, labels={"Test": "This", "team": "ny"}) client.ts().create(2, labels={"Test": "This", "Taste": "That", "team": "sf"}) @@ -580,7 +559,6 @@ def test_mrange(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") def test_multi_range_advanced(client): client.ts().create(1, labels={"Test": "This", "team": "ny"}) @@ -694,7 +672,6 @@ def test_multi_range_advanced(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_mrange_latest(client: redis.Redis): timeseries = client.ts() @@ -724,7 +701,6 @@ def test_mrange_latest(client: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.10.0", "timeseries") def test_multi_reverse_range(client): client.ts().create(1, labels={"Test": "This", "team": "ny"}) @@ -843,7 +819,6 @@ def test_multi_reverse_range(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_mrevrange_latest(client: redis.Redis): timeseries = client.ts() @@ -872,7 +847,6 @@ def test_mrevrange_latest(client: redis.Redis): ) -@pytest.mark.redismod def test_get(client): name = "test" client.ts().create(name) @@ -884,7 +858,6 @@ def test_get(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_get_latest(client: redis.Redis): timeseries = client.ts() @@ -902,7 +875,6 @@ def test_get_latest(client: redis.Redis): @pytest.mark.onlynoncluster -@pytest.mark.redismod def test_mget(client): client.ts().create(1, labels={"Test": "This"}) client.ts().create(2, labels={"Test": "This", "Taste": "That"}) @@ -938,7 +910,6 @@ def test_mget(client): @pytest.mark.onlynoncluster -@pytest.mark.redismod @skip_ifmodversion_lt("1.8.0", "timeseries") def test_mget_latest(client: redis.Redis): timeseries = client.ts() @@ -955,7 +926,6 @@ def test_mget_latest(client: redis.Redis): assert_resp_response(client, res, [{"t2": [{}, 10, 8.0]}], {"t2": [{}, [10, 8.0]]}) -@pytest.mark.redismod def test_info(client): client.ts().create(1, retention_msecs=5, labels={"currentLabel": "currentData"}) info = client.ts().info(1) @@ -965,7 +935,6 @@ def test_info(client): assert info["labels"]["currentLabel"] == "currentData" -@pytest.mark.redismod @skip_ifmodversion_lt("1.4.0", "timeseries") def test_info_duplicate_policy(client): client.ts().create(1, retention_msecs=5, labels={"currentLabel": "currentData"}) @@ -981,7 +950,6 @@ def test_info_duplicate_policy(client): ) -@pytest.mark.redismod @pytest.mark.onlynoncluster def test_query_index(client): client.ts().create(1, labels={"Test": "This"}) @@ -991,7 +959,6 @@ def test_query_index(client): assert_resp_response(client, client.ts().queryindex(["Taste=That"]), [2], ["2"]) -@pytest.mark.redismod def test_pipeline(client): pipeline = client.ts().pipeline() pipeline.create("with_pipeline") @@ -1009,7 +976,6 @@ def test_pipeline(client): assert client.ts().get("with_pipeline")[1] == 99 * 1.1 -@pytest.mark.redismod def test_uncompressed(client): client.ts().create("compressed") client.ts().create("uncompressed", uncompressed=True) @@ -1024,7 +990,6 @@ def test_uncompressed(client): assert compressed_info["memoryUsage"] < uncompressed_info["memoryUsage"] -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_create_with_insertion_filters(client): client.ts().create( @@ -1048,7 +1013,6 @@ def test_create_with_insertion_filters(client): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_create_with_insertion_filters_other_duplicate_policy(client): client.ts().create( @@ -1070,7 +1034,6 @@ def test_create_with_insertion_filters_other_duplicate_policy(client): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_alter_with_insertion_filters(client): assert 1000 == client.ts().add("time-series-1", 1000, 1.0) @@ -1095,7 +1058,6 @@ def test_alter_with_insertion_filters(client): ) -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_add_with_insertion_filters(client): assert 1000 == client.ts().add( @@ -1113,7 +1075,6 @@ def test_add_with_insertion_filters(client): assert_resp_response(client, data_points, [(1000, 1.0)], [[1000, 1.0]]) -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_incrby_with_insertion_filters(client): assert 1000 == client.ts().incrby( @@ -1136,7 +1097,6 @@ def test_incrby_with_insertion_filters(client): assert_resp_response(client, data_points, [(1000, 11.1)], [[1000, 11.1]]) -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_decrby_with_insertion_filters(client): assert 1000 == client.ts().decrby( @@ -1159,7 +1119,6 @@ def test_decrby_with_insertion_filters(client): assert_resp_response(client, data_points, [(1000, -11.1)], [[1000, -11.1]]) -@pytest.mark.redismod @skip_ifmodversion_lt("1.12.0", "timeseries") def test_madd_with_insertion_filters(client): client.ts().create( From 93c0bb7738f86c374b31855cca59385cf02f5d21 Mon Sep 17 00:00:00 2001 From: vladvildanov Date: Tue, 24 Sep 2024 09:15:12 +0300 Subject: [PATCH 2/5] Deprecate graph tests --- tests/test_asyncio/test_graph.py | 527 +++++++++++++++++++++++++ tests/test_graph.py | 657 +++++++++++++++++++++++++++++++ 2 files changed, 1184 insertions(+) create mode 100644 tests/test_asyncio/test_graph.py create mode 100644 tests/test_graph.py diff --git a/tests/test_asyncio/test_graph.py b/tests/test_asyncio/test_graph.py new file mode 100644 index 0000000000..fff2c3db0c --- /dev/null +++ b/tests/test_asyncio/test_graph.py @@ -0,0 +1,527 @@ +import pytest +import pytest_asyncio +import redis.asyncio as redis +from redis.commands.graph import Edge, Node, Path +from redis.commands.graph.execution_plan import Operation +from redis.exceptions import ResponseError +from tests.conftest import skip_if_redis_enterprise, skip_if_resp_version + + +@pytest_asyncio.fixture() +async def decoded_r(create_redis): + return await create_redis(decode_responses=True, url="redis://localhost:6480") + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_bulk(decoded_r): + with pytest.raises(NotImplementedError): + await decoded_r.graph().bulk() + await decoded_r.graph().bulk(foo="bar!") + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_graph_creation(decoded_r: redis.Redis): + graph = decoded_r.graph() + + john = Node( + label="person", + properties={ + "name": "John Doe", + "age": 33, + "gender": "male", + "status": "single", + }, + ) + graph.add_node(john) + japan = Node(label="country", properties={"name": "Japan"}) + + graph.add_node(japan) + edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) + graph.add_edge(edge) + + await graph.commit() + + query = ( + 'MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) ' + "RETURN p, v, c" + ) + + result = await graph.query(query) + + person = result.result_set[0][0] + visit = result.result_set[0][1] + country = result.result_set[0][2] + + assert person == john + assert visit.properties == edge.properties + assert country == japan + + query = """RETURN [1, 2.3, "4", true, false, null]""" + result = await graph.query(query) + assert [1, 2.3, "4", True, False, None] == result.result_set[0][0] + + # All done, remove graph. + await graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_array_functions(decoded_r: redis.Redis): + graph = decoded_r.graph() + + query = """CREATE (p:person{name:'a',age:32, array:[0,1,2]})""" + await graph.query(query) + + query = """WITH [0,1,2] as x return x""" + result = await graph.query(query) + assert [0, 1, 2] == result.result_set[0][0] + + query = """MATCH(n) return collect(n)""" + result = await graph.query(query) + + a = Node( + node_id=0, + label="person", + properties={"name": "a", "age": 32, "array": [0, 1, 2]}, + ) + + assert [a] == result.result_set[0][0] + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_path(decoded_r: redis.Redis): + node0 = Node(node_id=0, label="L1") + node1 = Node(node_id=1, label="L1") + edge01 = Edge(node0, "R1", node1, edge_id=0, properties={"value": 1}) + + graph = decoded_r.graph() + graph.add_node(node0) + graph.add_node(node1) + graph.add_edge(edge01) + await graph.flush() + + path01 = Path.new_empty_path().add_node(node0).add_edge(edge01).add_node(node1) + expected_results = [[path01]] + + query = "MATCH p=(:L1)-[:R1]->(:L1) RETURN p ORDER BY p" + result = await graph.query(query) + assert expected_results == result.result_set + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_param(decoded_r: redis.Redis): + params = [1, 2.3, "str", True, False, None, [0, 1, 2]] + query = "RETURN $param" + for param in params: + result = await decoded_r.graph().query(query, {"param": param}) + expected_results = [[param]] + assert expected_results == result.result_set + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_map(decoded_r: redis.Redis): + query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}" + + actual = (await decoded_r.graph().query(query)).result_set[0][0] + expected = { + "a": 1, + "b": "str", + "c": None, + "d": [1, 2, 3], + "e": True, + "f": {"x": 1, "y": 2}, + } + + assert actual == expected + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_point(decoded_r: redis.Redis): + query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})" + expected_lat = 32.070794860 + expected_lon = 34.820751118 + actual = (await decoded_r.graph().query(query)).result_set[0][0] + assert abs(actual["latitude"] - expected_lat) < 0.001 + assert abs(actual["longitude"] - expected_lon) < 0.001 + + query = "RETURN point({latitude: 32, longitude: 34.0})" + expected_lat = 32 + expected_lon = 34 + actual = (await decoded_r.graph().query(query)).result_set[0][0] + assert abs(actual["latitude"] - expected_lat) < 0.001 + assert abs(actual["longitude"] - expected_lon) < 0.001 + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_index_response(decoded_r: redis.Redis): + result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)") + assert 1 == result_set.indices_created + + result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)") + assert 0 == result_set.indices_created + + result_set = await decoded_r.graph().query("DROP INDEX ON :person(age)") + assert 1 == result_set.indices_deleted + + with pytest.raises(ResponseError): + await decoded_r.graph().query("DROP INDEX ON :person(age)") + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_stringify_query_result(decoded_r: redis.Redis): + graph = decoded_r.graph() + + john = Node( + alias="a", + label="person", + properties={ + "name": "John Doe", + "age": 33, + "gender": "male", + "status": "single", + }, + ) + graph.add_node(john) + + japan = Node(alias="b", label="country", properties={"name": "Japan"}) + graph.add_node(japan) + + edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) + graph.add_edge(edge) + + assert ( + str(john) + == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa + ) + assert ( + str(edge) + == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa + + """-[:visited{purpose:"pleasure"}]->""" + + """(b:country{name:"Japan"})""" + ) + assert str(japan) == """(b:country{name:"Japan"})""" + + await graph.commit() + + query = """MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) + RETURN p, v, c""" + + result = await graph.query(query) + person = result.result_set[0][0] + visit = result.result_set[0][1] + country = result.result_set[0][2] + + assert ( + str(person) + == """(:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa + ) + assert str(visit) == """()-[:visited{purpose:"pleasure"}]->()""" + assert str(country) == """(:country{name:"Japan"})""" + + await graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_optional_match(decoded_r: redis.Redis): + # Build a graph of form (a)-[R]->(b) + node0 = Node(node_id=0, label="L1", properties={"value": "a"}) + node1 = Node(node_id=1, label="L1", properties={"value": "b"}) + + edge01 = Edge(node0, "R", node1, edge_id=0) + + graph = decoded_r.graph() + graph.add_node(node0) + graph.add_node(node1) + graph.add_edge(edge01) + await graph.flush() + + # Issue a query that collects all outgoing edges from both nodes + # (the second has none) + query = """MATCH (a) OPTIONAL MATCH (a)-[e]->(b) RETURN a, e, b ORDER BY a.value""" # noqa + expected_results = [[node0, edge01, node1], [node1, None, None]] + + result = await graph.query(query) + assert expected_results == result.result_set + + await graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_cached_execution(decoded_r: redis.Redis): + await decoded_r.graph().query("CREATE ()") + + uncached_result = await decoded_r.graph().query( + "MATCH (n) RETURN n, $param", {"param": [0]} + ) + assert uncached_result.cached_execution is False + + # loop to make sure the query is cached on each thread on server + for x in range(0, 64): + cached_result = await decoded_r.graph().query( + "MATCH (n) RETURN n, $param", {"param": [0]} + ) + assert uncached_result.result_set == cached_result.result_set + + # should be cached on all threads by now + assert cached_result.cached_execution + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_slowlog(decoded_r: redis.Redis): + create_query = """CREATE + (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), + (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), + (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" + await decoded_r.graph().query(create_query) + + results = await decoded_r.graph().slowlog() + assert results[0][1] == "GRAPH.QUERY" + assert results[0][2] == create_query + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@pytest.mark.xfail(strict=False) +@skip_if_resp_version(3) +async def test_query_timeout(decoded_r: redis.Redis): + # Build a sample graph with 1000 nodes. + await decoded_r.graph().query("UNWIND range(0,1000) as val CREATE ({v: val})") + # Issue a long-running query with a 1-millisecond timeout. + with pytest.raises(ResponseError): + await decoded_r.graph().query("MATCH (a), (b), (c), (d) RETURN *", timeout=1) + assert False is False + + with pytest.raises(Exception): + await decoded_r.graph().query("RETURN 1", timeout="str") + assert False is False + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_read_only_query(decoded_r: redis.Redis): + with pytest.raises(Exception): + # Issue a write query, specifying read-only true, + # this call should fail. + await decoded_r.graph().query("CREATE (p:person {name:'a'})", read_only=True) + assert False is False + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_profile(decoded_r: redis.Redis): + q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})""" + profile = (await decoded_r.graph().profile(q)).result_set + assert "Create | Records produced: 3" in profile + assert "Unwind | Records produced: 3" in profile + + q = "MATCH (p:Person) WHERE p.v > 1 RETURN p" + profile = (await decoded_r.graph().profile(q)).result_set + assert "Results | Records produced: 2" in profile + assert "Project | Records produced: 2" in profile + assert "Filter | Records produced: 2" in profile + assert "Node By Label Scan | (p:Person) | Records produced: 3" in profile + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_redis_enterprise() +@skip_if_resp_version(3) +async def test_config(decoded_r: redis.Redis): + config_name = "RESULTSET_SIZE" + config_value = 3 + + # Set configuration + response = await decoded_r.graph().config(config_name, config_value, set=True) + assert response == "OK" + + # Make sure config been updated. + response = await decoded_r.graph().config(config_name, set=False) + expected_response = [config_name, config_value] + assert response == expected_response + + config_name = "QUERY_MEM_CAPACITY" + config_value = 1 << 20 # 1MB + + # Set configuration + response = await decoded_r.graph().config(config_name, config_value, set=True) + assert response == "OK" + + # Make sure config been updated. + response = await decoded_r.graph().config(config_name, set=False) + expected_response = [config_name, config_value] + assert response == expected_response + + # reset to default + await decoded_r.graph().config("QUERY_MEM_CAPACITY", 0, set=True) + await decoded_r.graph().config("RESULTSET_SIZE", -100, set=True) + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@pytest.mark.onlynoncluster +@skip_if_resp_version(3) +async def test_list_keys(decoded_r: redis.Redis): + result = await decoded_r.graph().list_keys() + assert result == [] + + await decoded_r.graph("G").query("CREATE (n)") + result = await decoded_r.graph().list_keys() + assert result == ["G"] + + await decoded_r.graph("X").query("CREATE (m)") + result = await decoded_r.graph().list_keys() + assert result == ["G", "X"] + + await decoded_r.delete("G") + await decoded_r.rename("X", "Z") + result = await decoded_r.graph().list_keys() + assert result == ["Z"] + + await decoded_r.delete("Z") + result = await decoded_r.graph().list_keys() + assert result == [] + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_multi_label(decoded_r: redis.Redis): + redis_graph = decoded_r.graph("g") + + node = Node(label=["l", "ll"]) + redis_graph.add_node(node) + await redis_graph.commit() + + query = "MATCH (n) RETURN n" + result = await redis_graph.query(query) + result_node = result.result_set[0][0] + assert result_node == node + + try: + Node(label=1) + assert False + except AssertionError: + assert True + + try: + Node(label=["l", 1]) + assert False + except AssertionError: + assert True + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_execution_plan(decoded_r: redis.Redis): + redis_graph = decoded_r.graph("execution_plan") + create_query = """CREATE + (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), + (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), + (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" + await redis_graph.query(create_query) + + result = await redis_graph.execution_plan( + "MATCH (r:Rider)-[:rides]->(t:Team) WHERE t.name = $name RETURN r.name, t.name, $params", # noqa + {"name": "Yehuda"}, + ) + expected = "Results\n Project\n Conditional Traverse | (t)->(r:Rider)\n Filter\n Node By Label Scan | (t:Team)" # noqa + assert result == expected + + await redis_graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +async def test_explain(decoded_r: redis.Redis): + redis_graph = decoded_r.graph("execution_plan") + # graph creation / population + create_query = """CREATE +(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), +(:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), +(:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" + await redis_graph.query(create_query) + + result = await redis_graph.explain( + """MATCH (r:Rider)-[:rides]->(t:Team) +WHERE t.name = $name +RETURN r.name, t.name +UNION +MATCH (r:Rider)-[:rides]->(t:Team) +WHERE t.name = $name +RETURN r.name, t.name""", + {"name": "Yamaha"}, + ) + expected = """\ +Results +Distinct + Join + Project + Conditional Traverse | (t)->(r:Rider) + Filter + Node By Label Scan | (t:Team) + Project + Conditional Traverse | (t)->(r:Rider) + Filter + Node By Label Scan | (t:Team)""" + assert str(result).replace(" ", "").replace("\n", "") == expected.replace( + " ", "" + ).replace("\n", "") + + expected = Operation("Results").append_child( + Operation("Distinct").append_child( + Operation("Join") + .append_child( + Operation("Project").append_child( + Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( + Operation("Filter").append_child( + Operation("Node By Label Scan", "(t:Team)") + ) + ) + ) + ) + .append_child( + Operation("Project").append_child( + Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( + Operation("Filter").append_child( + Operation("Node By Label Scan", "(t:Team)") + ) + ) + ) + ) + ) + ) + + assert result.structured_plan == expected + + result = await redis_graph.explain( + """MATCH (r:Rider), (t:Team) + RETURN r.name, t.name""" + ) + expected = """\ +Results +Project + Cartesian Product + Node By Label Scan | (r:Rider) + Node By Label Scan | (t:Team)""" + assert str(result).replace(" ", "").replace("\n", "") == expected.replace( + " ", "" + ).replace("\n", "") + + expected = Operation("Results").append_child( + Operation("Project").append_child( + Operation("Cartesian Product") + .append_child(Operation("Node By Label Scan")) + .append_child(Operation("Node By Label Scan")) + ) + ) + + assert result.structured_plan == expected + + await redis_graph.delete() diff --git a/tests/test_graph.py b/tests/test_graph.py new file mode 100644 index 0000000000..e50f39b8c2 --- /dev/null +++ b/tests/test_graph.py @@ -0,0 +1,657 @@ +from unittest.mock import patch + +import pytest +from redis import Redis +from redis.commands.graph import Edge, Node, Path +from redis.commands.graph.execution_plan import Operation +from redis.commands.graph.query_result import ( + CACHED_EXECUTION, + INDICES_CREATED, + INDICES_DELETED, + INTERNAL_EXECUTION_TIME, + LABELS_ADDED, + LABELS_REMOVED, + NODES_CREATED, + NODES_DELETED, + PROPERTIES_REMOVED, + PROPERTIES_SET, + RELATIONSHIPS_CREATED, + RELATIONSHIPS_DELETED, + QueryResult, +) +from redis.exceptions import ResponseError +from tests.conftest import _get_client, skip_if_redis_enterprise, skip_if_resp_version + + +@pytest.fixture +def client(request): + r = _get_client( + Redis, request, decode_responses=True, from_url="redis://localhost:6480" + ) + r.flushdb() + return r + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_bulk(client): + with pytest.raises(NotImplementedError): + client.graph().bulk() + client.graph().bulk(foo="bar!") + + +@pytest.mark.skip(reason="Graph module is deprecated.") +def test_graph_creation_throws_deprecation_warning(client): + """Verify that a DeprecationWarning is raised when creating a Graph instance.""" + + match = "RedisGraph support is deprecated as of Redis Stack 7.2" + with pytest.warns(DeprecationWarning, match=match): + client.graph() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_graph_creation(client): + graph = client.graph() + + john = Node( + label="person", + properties={ + "name": "John Doe", + "age": 33, + "gender": "male", + "status": "single", + }, + ) + graph.add_node(john) + japan = Node(label="country", properties={"name": "Japan"}) + + graph.add_node(japan) + edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) + graph.add_edge(edge) + + graph.commit() + + query = ( + 'MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) ' + "RETURN p, v, c" + ) + + result = graph.query(query) + + person = result.result_set[0][0] + visit = result.result_set[0][1] + country = result.result_set[0][2] + + assert person == john + assert visit.properties == edge.properties + assert country == japan + + query = """RETURN [1, 2.3, "4", true, false, null]""" + result = graph.query(query) + assert [1, 2.3, "4", True, False, None] == result.result_set[0][0] + + # All done, remove graph. + graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_array_functions(client): + query = """CREATE (p:person{name:'a',age:32, array:[0,1,2]})""" + client.graph().query(query) + + query = """WITH [0,1,2] as x return x""" + result = client.graph().query(query) + assert [0, 1, 2] == result.result_set[0][0] + + query = """MATCH(n) return collect(n)""" + result = client.graph().query(query) + + a = Node( + node_id=0, + label="person", + properties={"name": "a", "age": 32, "array": [0, 1, 2]}, + ) + + assert [a] == result.result_set[0][0] + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_path(client): + node0 = Node(node_id=0, label="L1") + node1 = Node(node_id=1, label="L1") + edge01 = Edge(node0, "R1", node1, edge_id=0, properties={"value": 1}) + + graph = client.graph() + graph.add_node(node0) + graph.add_node(node1) + graph.add_edge(edge01) + graph.flush() + + path01 = Path.new_empty_path().add_node(node0).add_edge(edge01).add_node(node1) + expected_results = [[path01]] + + query = "MATCH p=(:L1)-[:R1]->(:L1) RETURN p ORDER BY p" + result = graph.query(query) + assert expected_results == result.result_set + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_param(client): + params = [1, 2.3, "str", True, False, None, [0, 1, 2], r"\" RETURN 1337 //"] + query = "RETURN $param" + for param in params: + result = client.graph().query(query, {"param": param}) + expected_results = [[param]] + assert expected_results == result.result_set + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_map(client): + query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}" + + actual = client.graph().query(query).result_set[0][0] + expected = { + "a": 1, + "b": "str", + "c": None, + "d": [1, 2, 3], + "e": True, + "f": {"x": 1, "y": 2}, + } + + assert actual == expected + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_point(client): + query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})" + expected_lat = 32.070794860 + expected_lon = 34.820751118 + actual = client.graph().query(query).result_set[0][0] + assert abs(actual["latitude"] - expected_lat) < 0.001 + assert abs(actual["longitude"] - expected_lon) < 0.001 + + query = "RETURN point({latitude: 32, longitude: 34.0})" + expected_lat = 32 + expected_lon = 34 + actual = client.graph().query(query).result_set[0][0] + assert abs(actual["latitude"] - expected_lat) < 0.001 + assert abs(actual["longitude"] - expected_lon) < 0.001 + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_index_response(client): + result_set = client.graph().query("CREATE INDEX ON :person(age)") + assert 1 == result_set.indices_created + + result_set = client.graph().query("CREATE INDEX ON :person(age)") + assert 0 == result_set.indices_created + + result_set = client.graph().query("DROP INDEX ON :person(age)") + assert 1 == result_set.indices_deleted + + with pytest.raises(ResponseError): + client.graph().query("DROP INDEX ON :person(age)") + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_stringify_query_result(client): + graph = client.graph() + + john = Node( + alias="a", + label="person", + properties={ + "name": "John Doe", + "age": 33, + "gender": "male", + "status": "single", + }, + ) + graph.add_node(john) + + japan = Node(alias="b", label="country", properties={"name": "Japan"}) + graph.add_node(japan) + + edge = Edge(john, "visited", japan, properties={"purpose": "pleasure"}) + graph.add_edge(edge) + + assert ( + str(john) + == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa + ) + assert ( + str(edge) + == """(a:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa + + """-[:visited{purpose:"pleasure"}]->""" + + """(b:country{name:"Japan"})""" + ) + assert str(japan) == """(b:country{name:"Japan"})""" + + graph.commit() + + query = """MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) + RETURN p, v, c""" + + result = client.graph().query(query) + person = result.result_set[0][0] + visit = result.result_set[0][1] + country = result.result_set[0][2] + + assert ( + str(person) + == """(:person{age:33,gender:"male",name:"John Doe",status:"single"})""" # noqa + ) + assert str(visit) == """()-[:visited{purpose:"pleasure"}]->()""" + assert str(country) == """(:country{name:"Japan"})""" + + graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_optional_match(client): + # Build a graph of form (a)-[R]->(b) + node0 = Node(node_id=0, label="L1", properties={"value": "a"}) + node1 = Node(node_id=1, label="L1", properties={"value": "b"}) + + edge01 = Edge(node0, "R", node1, edge_id=0) + + graph = client.graph() + graph.add_node(node0) + graph.add_node(node1) + graph.add_edge(edge01) + graph.flush() + + # Issue a query that collects all outgoing edges from both nodes + # (the second has none) + query = """MATCH (a) OPTIONAL MATCH (a)-[e]->(b) RETURN a, e, b ORDER BY a.value""" # noqa + expected_results = [[node0, edge01, node1], [node1, None, None]] + + result = client.graph().query(query) + assert expected_results == result.result_set + + graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_cached_execution(client): + client.graph().query("CREATE ()") + + uncached_result = client.graph().query("MATCH (n) RETURN n, $param", {"param": [0]}) + assert uncached_result.cached_execution is False + + # loop to make sure the query is cached on each thread on server + for x in range(0, 64): + cached_result = client.graph().query( + "MATCH (n) RETURN n, $param", {"param": [0]} + ) + assert uncached_result.result_set == cached_result.result_set + + # should be cached on all threads by now + assert cached_result.cached_execution + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_slowlog(client): + create_query = """CREATE (:Rider + {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), + (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), + (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" + client.graph().query(create_query) + + results = client.graph().slowlog() + assert results[0][1] == "GRAPH.QUERY" + assert results[0][2] == create_query + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +@pytest.mark.xfail(strict=False) +def test_query_timeout(client): + # Build a sample graph with 1000 nodes. + client.graph().query("UNWIND range(0,1000) as val CREATE ({v: val})") + # Issue a long-running query with a 1-millisecond timeout. + with pytest.raises(ResponseError): + client.graph().query("MATCH (a), (b), (c), (d) RETURN *", timeout=1) + assert False is False + + with pytest.raises(Exception): + client.graph().query("RETURN 1", timeout="str") + assert False is False + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_read_only_query(client): + with pytest.raises(Exception): + # Issue a write query, specifying read-only true, + # this call should fail. + client.graph().query("CREATE (p:person {name:'a'})", read_only=True) + assert False is False + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_profile(client): + q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})""" + profile = client.graph().profile(q).result_set + assert "Create | Records produced: 3" in profile + assert "Unwind | Records produced: 3" in profile + + q = "MATCH (p:Person) WHERE p.v > 1 RETURN p" + profile = client.graph().profile(q).result_set + assert "Results | Records produced: 2" in profile + assert "Project | Records produced: 2" in profile + assert "Filter | Records produced: 2" in profile + assert "Node By Label Scan | (p:Person) | Records produced: 3" in profile + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +@skip_if_redis_enterprise() +def test_config(client): + config_name = "RESULTSET_SIZE" + config_value = 3 + + # Set configuration + response = client.graph().config(config_name, config_value, set=True) + assert response == "OK" + + # Make sure config been updated. + response = client.graph().config(config_name, set=False) + expected_response = [config_name, config_value] + assert response == expected_response + + config_name = "QUERY_MEM_CAPACITY" + config_value = 1 << 20 # 1MB + + # Set configuration + response = client.graph().config(config_name, config_value, set=True) + assert response == "OK" + + # Make sure config been updated. + response = client.graph().config(config_name, set=False) + expected_response = [config_name, config_value] + assert response == expected_response + + # reset to default + client.graph().config("QUERY_MEM_CAPACITY", 0, set=True) + client.graph().config("RESULTSET_SIZE", -100, set=True) + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@pytest.mark.onlynoncluster +@skip_if_resp_version(3) +def test_list_keys(client): + result = client.graph().list_keys() + assert result == [] + + client.graph("G").query("CREATE (n)") + result = client.graph().list_keys() + assert result == ["G"] + + client.graph("X").query("CREATE (m)") + result = client.graph().list_keys() + assert result == ["G", "X"] + + client.delete("G") + client.rename("X", "Z") + result = client.graph().list_keys() + assert result == ["Z"] + + client.delete("Z") + result = client.graph().list_keys() + assert result == [] + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_multi_label(client): + redis_graph = client.graph("g") + + node = Node(label=["l", "ll"]) + redis_graph.add_node(node) + redis_graph.commit() + + query = "MATCH (n) RETURN n" + result = redis_graph.query(query) + result_node = result.result_set[0][0] + assert result_node == node + + try: + Node(label=1) + assert False + except AssertionError: + assert True + + try: + Node(label=["l", 1]) + assert False + except AssertionError: + assert True + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_cache_sync(client): + pass + return + # This test verifies that client internal graph schema cache stays + # in sync with the graph schema + # + # Client B will try to get Client A out of sync by: + # 1. deleting the graph + # 2. reconstructing the graph in a different order, this will casuse + # a difference in the current mapping between string IDs and the + # mapping Client A is aware of + # + # Client A should pick up on the changes by comparing graph versions + # and resyncing its cache. + + A = client.graph("cache-sync") + B = client.graph("cache-sync") + + # Build order: + # 1. introduce label 'L' and 'K' + # 2. introduce attribute 'x' and 'q' + # 3. introduce relationship-type 'R' and 'S' + + A.query("CREATE (:L)") + B.query("CREATE (:K)") + A.query("MATCH (n) SET n.x = 1") + B.query("MATCH (n) SET n.q = 1") + A.query("MATCH (n) CREATE (n)-[:R]->()") + B.query("MATCH (n) CREATE (n)-[:S]->()") + + # Cause client A to populate its cache + A.query("MATCH (n)-[e]->() RETURN n, e") + + assert len(A._labels) == 2 + assert len(A._properties) == 2 + assert len(A._relationship_types) == 2 + assert A._labels[0] == "L" + assert A._labels[1] == "K" + assert A._properties[0] == "x" + assert A._properties[1] == "q" + assert A._relationship_types[0] == "R" + assert A._relationship_types[1] == "S" + + # Have client B reconstruct the graph in a different order. + B.delete() + + # Build order: + # 1. introduce relationship-type 'R' + # 2. introduce label 'L' + # 3. introduce attribute 'x' + B.query("CREATE ()-[:S]->()") + B.query("CREATE ()-[:R]->()") + B.query("CREATE (:K)") + B.query("CREATE (:L)") + B.query("MATCH (n) SET n.q = 1") + B.query("MATCH (n) SET n.x = 1") + + # A's internal cached mapping is now out of sync + # issue a query and make sure A's cache is synced. + A.query("MATCH (n)-[e]->() RETURN n, e") + + assert len(A._labels) == 2 + assert len(A._properties) == 2 + assert len(A._relationship_types) == 2 + assert A._labels[0] == "K" + assert A._labels[1] == "L" + assert A._properties[0] == "q" + assert A._properties[1] == "x" + assert A._relationship_types[0] == "S" + assert A._relationship_types[1] == "R" + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_execution_plan(client): + redis_graph = client.graph("execution_plan") + create_query = """CREATE + (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), + (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), + (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" + redis_graph.query(create_query) + + result = redis_graph.execution_plan( + "MATCH (r:Rider)-[:rides]->(t:Team) WHERE t.name = $name RETURN r.name, t.name, $params", # noqa + {"name": "Yehuda"}, + ) + expected = "Results\n Project\n Conditional Traverse | (t)->(r:Rider)\n Filter\n Node By Label Scan | (t:Team)" # noqa + assert result == expected + + redis_graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_explain(client): + redis_graph = client.graph("execution_plan") + # graph creation / population + create_query = """CREATE +(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), +(:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), +(:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})""" + redis_graph.query(create_query) + + result = redis_graph.explain( + """MATCH (r:Rider)-[:rides]->(t:Team) +WHERE t.name = $name +RETURN r.name, t.name +UNION +MATCH (r:Rider)-[:rides]->(t:Team) +WHERE t.name = $name +RETURN r.name, t.name""", + {"name": "Yamaha"}, + ) + expected = """\ +Results +Distinct + Join + Project + Conditional Traverse | (t)->(r:Rider) + Filter + Node By Label Scan | (t:Team) + Project + Conditional Traverse | (t)->(r:Rider) + Filter + Node By Label Scan | (t:Team)""" + assert str(result).replace(" ", "").replace("\n", "") == expected.replace( + " ", "" + ).replace("\n", "") + + expected = Operation("Results").append_child( + Operation("Distinct").append_child( + Operation("Join") + .append_child( + Operation("Project").append_child( + Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( + Operation("Filter").append_child( + Operation("Node By Label Scan", "(t:Team)") + ) + ) + ) + ) + .append_child( + Operation("Project").append_child( + Operation("Conditional Traverse", "(t)->(r:Rider)").append_child( + Operation("Filter").append_child( + Operation("Node By Label Scan", "(t:Team)") + ) + ) + ) + ) + ) + ) + + assert result.structured_plan == expected + + result = redis_graph.explain( + """MATCH (r:Rider), (t:Team) + RETURN r.name, t.name""" + ) + expected = """\ +Results +Project + Cartesian Product + Node By Label Scan | (r:Rider) + Node By Label Scan | (t:Team)""" + assert str(result).replace(" ", "").replace("\n", "") == expected.replace( + " ", "" + ).replace("\n", "") + + expected = Operation("Results").append_child( + Operation("Project").append_child( + Operation("Cartesian Product") + .append_child(Operation("Node By Label Scan")) + .append_child(Operation("Node By Label Scan")) + ) + ) + + assert result.structured_plan == expected + + redis_graph.delete() + + +@pytest.mark.skip(reason="Graph module is deprecated.") +@skip_if_resp_version(3) +def test_resultset_statistics(client): + with patch.object(target=QueryResult, attribute="_get_stat") as mock_get_stats: + result = client.graph().query("RETURN 1") + result.labels_added + mock_get_stats.assert_called_with(LABELS_ADDED) + result.labels_removed + mock_get_stats.assert_called_with(LABELS_REMOVED) + result.nodes_created + mock_get_stats.assert_called_with(NODES_CREATED) + result.nodes_deleted + mock_get_stats.assert_called_with(NODES_DELETED) + result.properties_set + mock_get_stats.assert_called_with(PROPERTIES_SET) + result.properties_removed + mock_get_stats.assert_called_with(PROPERTIES_REMOVED) + result.relationships_created + mock_get_stats.assert_called_with(RELATIONSHIPS_CREATED) + result.relationships_deleted + mock_get_stats.assert_called_with(RELATIONSHIPS_DELETED) + result.indices_created + mock_get_stats.assert_called_with(INDICES_CREATED) + result.indices_deleted + mock_get_stats.assert_called_with(INDICES_DELETED) + result.cached_execution + mock_get_stats.assert_called_with(CACHED_EXECUTION) + result.run_time_ms + mock_get_stats.assert_called_with(INTERNAL_EXECUTION_TIME) From 879a2bc251a4f0a0a3550bf6c386ae7ccc6b677f Mon Sep 17 00:00:00 2001 From: vladvildanov Date: Tue, 24 Sep 2024 09:16:02 +0300 Subject: [PATCH 3/5] Codestyle changes --- tests/test_bloom.py | 4 +--- tests/test_json.py | 4 ++-- tests/test_timeseries.py | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_bloom.py b/tests/test_bloom.py index 0b74286f4f..da30422d9b 100644 --- a/tests/test_bloom.py +++ b/tests/test_bloom.py @@ -14,9 +14,7 @@ @pytest.fixture() def decoded_r(request): - with _get_client( - redis.Redis, request, decode_responses=True - ) as client: + with _get_client(redis.Redis, request, decode_responses=True) as client: yield client diff --git a/tests/test_json.py b/tests/test_json.py index ab65a37f7f..b2c0e107bc 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -1,10 +1,10 @@ import pytest import redis -from redis import exceptions, Redis +from redis import Redis, exceptions from redis.commands.json.decoders import decode_list, unstring from redis.commands.json.path import Path -from .conftest import assert_resp_response, skip_ifmodversion_lt, _get_client +from .conftest import _get_client, assert_resp_response, skip_ifmodversion_lt @pytest.fixture diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py index 32061e9df4..bad23d16c4 100644 --- a/tests/test_timeseries.py +++ b/tests/test_timeseries.py @@ -15,9 +15,7 @@ @pytest.fixture() def decoded_r(request): - with _get_client( - redis.Redis, request, decode_responses=True - ) as client: + with _get_client(redis.Redis, request, decode_responses=True) as client: yield client From 43fe5f41b8255c4ac3213adc7afd1ab0066a96a2 Mon Sep 17 00:00:00 2001 From: vladvildanov Date: Tue, 24 Sep 2024 09:19:58 +0300 Subject: [PATCH 4/5] Revert image changes --- docker-compose.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 32c9e8b434..c6e54309b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ version: "3.8" services: redis: - image: redis:8.0-M01 + image: ${REDIS_IMAGE:-redis:latest} container_name: redis-standalone command: redis-server --enable-debug-command yes --protected-mode no ports: @@ -17,7 +17,7 @@ services: - all replica: - image: redis:8.0-M01 + image: ${REDIS_IMAGE:-redis:latest} container_name: redis-replica depends_on: - redis @@ -34,7 +34,7 @@ services: context: . dockerfile: dockers/Dockerfile.cluster args: - REDIS_IMAGE: redis:8.0-M01 + REDIS_IMAGE: ${REDIS_IMAGE:-redis:latest} ports: - 16379:16379 - 16380:16380 @@ -63,7 +63,7 @@ services: - "./dockers/stunnel/keys:/etc/stunnel/keys:ro" sentinel: - image: redis:8.0-M01 + image: ${REDIS_IMAGE:-redis:latest} container_name: redis-sentinel depends_on: - redis @@ -77,7 +77,7 @@ services: - all sentinel2: - image: redis:8.0-M01 + image: ${REDIS_IMAGE:-redis:latest} container_name: redis-sentinel2 depends_on: - redis @@ -91,7 +91,7 @@ services: - all sentinel3: - image: redis:8.0-M01 + image: ${REDIS_IMAGE:-redis:latest} container_name: redis-sentinel3 depends_on: - redis From 5cffef3fac1383144cfa92272652c5e3cd032dfe Mon Sep 17 00:00:00 2001 From: vladvildanov Date: Tue, 24 Sep 2024 09:21:33 +0300 Subject: [PATCH 5/5] Reverted parsing changes --- redis/commands/search/commands.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/redis/commands/search/commands.py b/redis/commands/search/commands.py index f203a0260c..da79016ad4 100644 --- a/redis/commands/search/commands.py +++ b/redis/commands/search/commands.py @@ -101,15 +101,7 @@ def _parse_profile(self, res, **kwargs): with_scores=query._with_scores, ) - docs = {} - for i in range(0, len(res[1]), 2): - if isinstance(res[1][i + 1], list): - for item in res[1][i + 1]: - res[1][i + 1] = parse_to_dict(item) - - docs[res[1][i]] = res[1][i + 1] - - return result, docs + return result, parse_to_dict(res[1]) def _parse_spellcheck(self, res, **kwargs): corrections = {}