diff --git a/.github/workflows/test-fakeredis.yml b/.github/workflows/test-fakeredis.yml index 0d02c977329a..29f87ece47ca 100644 --- a/.github/workflows/test-fakeredis.yml +++ b/.github/workflows/test-fakeredis.yml @@ -75,8 +75,6 @@ jobs: run: | poetry run pytest test/ \ --ignore test/test_hypothesis.py \ - --ignore test/test_geo_commands.py \ - --ignore test/test_bitmap_commands.py \ --ignore test/test_json/ \ --ignore test/test_mixins/test_bitmap_commands.py \ --junit-xml=results-tests.xml --html=report-tests.html -v diff --git a/tests/fakeredis/poetry.lock b/tests/fakeredis/poetry.lock index dbed5d3497f1..f29d89f07a40 100644 --- a/tests/fakeredis/poetry.lock +++ b/tests/fakeredis/poetry.lock @@ -144,22 +144,22 @@ test = ["pytest (>=6)"] [[package]] name = "fakeredis" -version = "2.23.5" +version = "2.25.1" description = "Python implementation of redis API, can be used for testing purposes." optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "fakeredis-2.23.5-py3-none-any.whl", hash = "sha256:4d85b1b6b3a80cbbb3a8967f8686f7bf6ddf5bd7cd5ac7ac90b3561d8c3a7ddb"}, - {file = "fakeredis-2.23.5.tar.gz", hash = "sha256:edffc79fdce0f1d83cbb20b52694a9cba4a5fe5beb627c11722a42aa0fa44f52"}, + {file = "fakeredis-2.25.1-py3-none-any.whl", hash = "sha256:d08dcbaceae0804db4644fa634106e3c42d76fe4d11aea2949eda768df0c6450"}, + {file = "fakeredis-2.25.1.tar.gz", hash = "sha256:e9e73bacf412d1d942ee7f80525dc188182158e82d41be57eb9c4e71f7474ac8"}, ] [package.dependencies] jsonpath-ng = {version = ">=1.6,<2.0", optional = true, markers = "extra == \"json\""} lupa = {version = ">=2.1,<3.0", optional = true, markers = "extra == \"lua\""} pyprobables = {version = ">=0.6,<0.7", optional = true, markers = "extra == \"bf\" or extra == \"cf\" or extra == \"probabilistic\""} -redis = ">=4" +redis = {version = ">=4.3", markers = "python_full_version > \"3.8.0\""} sortedcontainers = ">=2,<3" -typing_extensions = {version = ">=4.7,<5.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.7,<5.0", markers = "python_version < \"3.11\""} [package.extras] bf = ["pyprobables (>=0.6,<0.7)"] @@ -170,13 +170,13 @@ probabilistic = ["pyprobables (>=0.6,<0.7)"] [[package]] name = "hypothesis" -version = "6.111.1" +version = "6.112.2" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.111.1-py3-none-any.whl", hash = "sha256:9422adbac4b2104f6cf92dc6604b5c9df975efc08ffc7145ecc39bc617243835"}, - {file = "hypothesis-6.111.1.tar.gz", hash = "sha256:6ab6185a858fa692bf125c0d0a936134edc318bee01c05e407c71c9ead0b61c5"}, + {file = "hypothesis-6.112.2-py3-none-any.whl", hash = "sha256:914b55f75b7c6f653cd36fef66b61a773a51c1e363939fcbc0216773ff4ee0d9"}, + {file = "hypothesis-6.112.2.tar.gz", hash = "sha256:90cd62d9487eaf294bf0dceb47dbaca6432408b2e9417cfa6e3409313dbde95b"}, ] [package.dependencies] @@ -185,10 +185,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.66)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.12)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.72)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.14)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.66)", "hypothesis-crosshair (>=0.0.12)"] +crosshair = ["crosshair-tool (>=0.0.72)", "hypothesis-crosshair (>=0.0.14)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -199,7 +199,7 @@ pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.1)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.2)"] [[package]] name = "iniconfig" @@ -457,13 +457,13 @@ files = [ [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -583,21 +583,21 @@ pytest = ">=7.0.0" [[package]] name = "redis" -version = "5.0.8" +version = "5.1.0" description = "Python client for Redis database and key-value store" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4"}, - {file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"}, + {file = "redis-5.1.0-py3-none-any.whl", hash = "sha256:fd4fccba0d7f6aa48c58a78d76ddb4afc698f5da4a2c1d03d916e4fd7ab88cdd"}, + {file = "redis-5.1.0.tar.gz", hash = "sha256:b756df1e4a3858fcc0ef861f3fc53623a96c41e2b1f5304e09e0fe758d333d40"}, ] [package.dependencies] async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} [package.extras] -hiredis = ["hiredis (>1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +hiredis = ["hiredis (>=3.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] [[package]] name = "sortedcontainers" @@ -612,13 +612,13 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -635,4 +635,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "942f6f5d521576b80ff3df540956c1d9c16826b9d82bd4f9b15d439a1df98f9c" +content-hash = "c7fb24a461b6c934fb5ef67a10701aff7bab809ee76c3cff267959c9798a6a81" diff --git a/tests/fakeredis/pyproject.toml b/tests/fakeredis/pyproject.toml index c5ad4b005735..e53ed1a144a5 100644 --- a/tests/fakeredis/pyproject.toml +++ b/tests/fakeredis/pyproject.toml @@ -19,7 +19,7 @@ maintainers = [ [tool.poetry.dependencies] python = "^3.10" redis = ">=5" -fakeredis = { version = "^2.23", extras = ["json", "bf", "cf", "lua"] } +fakeredis = { version = "^2.25", extras = ["json", "bf", "cf", "lua"] } hypothesis = "^6.111" pytest = "^8.3" pytest-timeout = "^2.3.1" diff --git a/tests/fakeredis/test/test_asyncredis.py b/tests/fakeredis/test/test_asyncredis.py index 9dbef5f055ff..395a9c8047bb 100644 --- a/tests/fakeredis/test/test_asyncredis.py +++ b/tests/fakeredis/test/test_asyncredis.py @@ -1,5 +1,4 @@ import asyncio -import re import sys if sys.version_info >= (3, 11): @@ -11,14 +10,10 @@ import redis import redis.asyncio -from fakeredis import FakeServer, aioredis, FakeAsyncRedis, FakeStrictRedis +from fakeredis import FakeServer, aioredis from test import testtools - pytestmark = [] -fake_only = pytest.mark.parametrize( - "async_redis", [pytest.param("fake", marks=pytest.mark.fake)], indirect=True -) pytestmark.extend( [ pytest.mark.asyncio, @@ -183,31 +178,6 @@ async def test_failed_script_error7(self, async_redis): await async_redis.eval('return redis.call("ZCOUNT", KEYS[1])', 1, "foo") -@testtools.run_test_if_redispy_ver("gte", "5.1") -async def test_repr_redis_51(async_redis: redis.asyncio.Redis): - assert re.fullmatch( - r",db=0)>)>", - repr(async_redis.connection_pool), - ) - - -@fake_only -@pytest.mark.disconnected -async def test_not_connected(async_redis: redis.asyncio.Redis): - with pytest.raises(redis.asyncio.ConnectionError): - await async_redis.ping() - - -@fake_only -async def test_disconnect_server(async_redis, fake_server): - await async_redis.ping() - fake_server.connected = False - with pytest.raises(redis.asyncio.ConnectionError): - await async_redis.ping() - fake_server.connected = True - - async def test_type(async_redis: redis.asyncio.Redis): await async_redis.set("string_key", "value") await async_redis.lpush("list_key", "value") @@ -238,78 +208,6 @@ async def test_xdel(async_redis: redis.asyncio.Redis): assert await async_redis.xdel(stream, m2, m3) == 2 -@pytest.mark.fake -async def test_from_url(): - r0 = aioredis.FakeRedis.from_url("redis://localhost?db=0") - r1 = aioredis.FakeRedis.from_url("redis://localhost?db=1") - # Check that they are indeed different databases - await r0.set("foo", "a") - await r1.set("foo", "b") - assert await r0.get("foo") == b"a" - assert await r1.get("foo") == b"b" - await r0.connection_pool.disconnect() - await r1.connection_pool.disconnect() - - -@pytest.mark.fake -async def test_from_url_with_version(): - r0 = aioredis.FakeRedis.from_url("redis://localhost?db=0", version=(6,)) - r1 = aioredis.FakeRedis.from_url("redis://localhost?db=1", version=(6,)) - # Check that they are indeed different databases - await r0.set("foo", "a") - await r1.set("foo", "b") - assert await r0.get("foo") == b"a" - assert await r1.get("foo") == b"b" - await r0.connection_pool.disconnect() - await r1.connection_pool.disconnect() - - -@fake_only -async def test_from_url_with_server(async_redis, fake_server): - r2 = aioredis.FakeRedis.from_url("redis://localhost", server=fake_server) - await async_redis.set("foo", "bar") - assert await r2.get("foo") == b"bar" - await r2.connection_pool.disconnect() - - -@pytest.mark.fake -async def test_without_server(): - r = aioredis.FakeRedis() - assert await r.ping() - - -@pytest.mark.fake -async def test_without_server_disconnected(): - r = aioredis.FakeRedis(connected=False) - with pytest.raises(redis.asyncio.ConnectionError): - await r.ping() - - -@pytest.mark.fake -async def test_async(): - # arrange - cache = aioredis.FakeRedis() - # act - await cache.set("fakeredis", "plz") - x = await cache.get("fakeredis") - # assert - assert x == b"plz" - - -@testtools.run_test_if_redispy_ver("gte", "4.4.0") -@pytest.mark.parametrize("nowait", [False, True]) -@pytest.mark.fake -async def test_connection_disconnect(nowait): - server = FakeServer() - r = aioredis.FakeRedis(server=server) - conn = await r.connection_pool.get_connection("_") - assert conn is not None - - await conn.disconnect(nowait=nowait) - - assert conn._sock is None - - async def test_connection_with_username_and_password(): server = FakeServer() r = aioredis.FakeRedis(server=server, username="username", password="password") @@ -320,31 +218,6 @@ async def test_connection_with_username_and_password(): assert result.decode() == test_value -@pytest.mark.fake -async def test_init_args(): - sync_r1 = FakeStrictRedis() - r1 = FakeAsyncRedis() - r5 = FakeAsyncRedis() - r2 = FakeAsyncRedis(server=FakeServer()) - - shared_server = FakeServer() - r3 = FakeAsyncRedis(server=shared_server) - r4 = FakeAsyncRedis(server=shared_server) - - await r1.set("foo", "bar") - await r3.set("bar", "baz") - - assert await r1.get("foo") == b"bar" - assert await r5.get("foo") is None - assert sync_r1.get("foo") is None - assert await r2.get("foo") is None - assert await r3.get("foo") is None - - assert await r3.get("bar") == b"baz" - assert await r4.get("bar") == b"baz" - assert await r1.get("bar") is None - - @pytest.mark.asyncio async def test_cause_fakeredis_bug(async_redis): if sys.version_info < (3, 11): diff --git a/tests/fakeredis/test/test_connection.py b/tests/fakeredis/test/test_connection.py deleted file mode 100644 index 1ee9297f5e9b..000000000000 --- a/tests/fakeredis/test/test_connection.py +++ /dev/null @@ -1,484 +0,0 @@ -from test import testtools - -import pytest -import redis -import redis.client -from fakeredis import _msgs as msgs -from redis.exceptions import ResponseError -from test.testtools import raw_command - - -def test_ping(r: redis.Redis): - assert r.ping() - assert testtools.raw_command(r, "ping", "test") == b"test" - with pytest.raises( - redis.ResponseError, match=msgs.WRONG_ARGS_MSG6.format("ping")[4:] - ): - raw_command(r, "ping", "arg1", "arg2") - - -def test_echo(r: redis.Redis): - assert r.echo(b"hello") == b"hello" - assert r.echo("hello") == b"hello" - - -@testtools.fake_only -def test_time(r, mocker): - fake_time = mocker.patch("time.time") - fake_time.return_value = 1234567890.1234567 - assert r.time() == (1234567890, 123457) - fake_time.return_value = 1234567890.000001 - assert r.time() == (1234567890, 1) - fake_time.return_value = 1234567890.9999999 - assert r.time() == (1234567891, 0) - - -@pytest.mark.decode_responses -class TestDecodeResponses: - def test_decode_str(self, r): - r.set("foo", "bar") - assert r.get("foo") == "bar" - - def test_decode_set(self, r): - r.sadd("foo", "member1") - assert r.smembers("foo") == {"member1"} - - def test_decode_list(self, r): - r.rpush("foo", "a", "b") - assert r.lrange("foo", 0, -1) == ["a", "b"] - - def test_decode_dict(self, r): - r.hset("foo", "key", "value") - assert r.hgetall("foo") == {"key": "value"} - - def test_decode_error(self, r): - r.set("foo", "bar") - with pytest.raises(ResponseError) as exc_info: - r.hset("foo", "bar", "baz") - assert isinstance(exc_info.value.args[0], str) - - -@pytest.mark.disconnected -@testtools.fake_only -class TestFakeStrictRedisConnectionErrors: - def test_flushdb(self, r): - with pytest.raises(redis.ConnectionError): - r.flushdb() - - def test_flushall(self, r): - with pytest.raises(redis.ConnectionError): - r.flushall() - - def test_append(self, r): - with pytest.raises(redis.ConnectionError): - r.append("key", "value") - - def test_bitcount(self, r): - with pytest.raises(redis.ConnectionError): - r.bitcount("key", 0, 20) - - def test_decr(self, r): - with pytest.raises(redis.ConnectionError): - r.decr("key", 2) - - def test_exists(self, r): - with pytest.raises(redis.ConnectionError): - r.exists("key") - - def test_expire(self, r): - with pytest.raises(redis.ConnectionError): - r.expire("key", 20) - - def test_pexpire(self, r): - with pytest.raises(redis.ConnectionError): - r.pexpire("key", 20) - - def test_echo(self, r): - with pytest.raises(redis.ConnectionError): - r.echo("value") - - def test_get(self, r): - with pytest.raises(redis.ConnectionError): - r.get("key") - - def test_getbit(self, r): - with pytest.raises(redis.ConnectionError): - r.getbit("key", 2) - - def test_getset(self, r): - with pytest.raises(redis.ConnectionError): - r.getset("key", "value") - - def test_incr(self, r): - with pytest.raises(redis.ConnectionError): - r.incr("key") - - def test_incrby(self, r): - with pytest.raises(redis.ConnectionError): - r.incrby("key") - - def test_ncrbyfloat(self, r): - with pytest.raises(redis.ConnectionError): - r.incrbyfloat("key") - - def test_keys(self, r): - with pytest.raises(redis.ConnectionError): - r.keys() - - def test_mget(self, r): - with pytest.raises(redis.ConnectionError): - r.mget(["key1", "key2"]) - - def test_mset(self, r): - with pytest.raises(redis.ConnectionError): - r.mset({"key": "value"}) - - def test_msetnx(self, r): - with pytest.raises(redis.ConnectionError): - r.msetnx({"key": "value"}) - - def test_persist(self, r): - with pytest.raises(redis.ConnectionError): - r.persist("key") - - def test_rename(self, r): - server = r.connection_pool.connection_kwargs["server"] - server.connected = True - r.set("key1", "value") - server.connected = False - with pytest.raises(redis.ConnectionError): - r.rename("key1", "key2") - server.connected = True - assert r.exists("key1") - - def test_eval(self, r): - with pytest.raises(redis.ConnectionError): - r.eval("", 0) - - def test_lpush(self, r): - with pytest.raises(redis.ConnectionError): - r.lpush("name", 1, 2) - - def test_lrange(self, r): - with pytest.raises(redis.ConnectionError): - r.lrange("name", 1, 5) - - def test_llen(self, r): - with pytest.raises(redis.ConnectionError): - r.llen("name") - - def test_lrem(self, r): - with pytest.raises(redis.ConnectionError): - r.lrem("name", 2, 2) - - def test_rpush(self, r): - with pytest.raises(redis.ConnectionError): - r.rpush("name", 1) - - def test_lpop(self, r): - with pytest.raises(redis.ConnectionError): - r.lpop("name") - - def test_lset(self, r): - with pytest.raises(redis.ConnectionError): - r.lset("name", 1, 4) - - def test_rpushx(self, r): - with pytest.raises(redis.ConnectionError): - r.rpushx("name", 1) - - def test_ltrim(self, r): - with pytest.raises(redis.ConnectionError): - r.ltrim("name", 1, 4) - - def test_lindex(self, r): - with pytest.raises(redis.ConnectionError): - r.lindex("name", 1) - - def test_lpushx(self, r): - with pytest.raises(redis.ConnectionError): - r.lpushx("name", 1) - - def test_rpop(self, r): - with pytest.raises(redis.ConnectionError): - r.rpop("name") - - def test_linsert(self, r): - with pytest.raises(redis.ConnectionError): - r.linsert("name", "where", "refvalue", "value") - - def test_rpoplpush(self, r): - with pytest.raises(redis.ConnectionError): - r.rpoplpush("src", "dst") - - def test_blpop(self, r): - with pytest.raises(redis.ConnectionError): - r.blpop("keys") - - def test_brpop(self, r): - with pytest.raises(redis.ConnectionError): - r.brpop("keys") - - def test_brpoplpush(self, r): - with pytest.raises(redis.ConnectionError): - r.brpoplpush("src", "dst") - - def test_hdel(self, r): - with pytest.raises(redis.ConnectionError): - r.hdel("name") - - def test_hexists(self, r): - with pytest.raises(redis.ConnectionError): - r.hexists("name", "key") - - def test_hget(self, r): - with pytest.raises(redis.ConnectionError): - r.hget("name", "key") - - def test_hgetall(self, r): - with pytest.raises(redis.ConnectionError): - r.hgetall("name") - - def test_hincrby(self, r): - with pytest.raises(redis.ConnectionError): - r.hincrby("name", "key") - - def test_hincrbyfloat(self, r): - with pytest.raises(redis.ConnectionError): - r.hincrbyfloat("name", "key") - - def test_hkeys(self, r): - with pytest.raises(redis.ConnectionError): - r.hkeys("name") - - def test_hlen(self, r): - with pytest.raises(redis.ConnectionError): - r.hlen("name") - - def test_hset(self, r): - with pytest.raises(redis.ConnectionError): - r.hset("name", "key", 1) - - def test_hsetnx(self, r): - with pytest.raises(redis.ConnectionError): - r.hsetnx("name", "key", 2) - - def test_hmset(self, r): - with pytest.raises(redis.ConnectionError): - r.hmset("name", {"key": 1}) - - def test_hmget(self, r): - with pytest.raises(redis.ConnectionError): - r.hmget("name", ["a", "b"]) - - def test_hvals(self, r): - with pytest.raises(redis.ConnectionError): - r.hvals("name") - - def test_sadd(self, r): - with pytest.raises(redis.ConnectionError): - r.sadd("name", 1, 2) - - def test_scard(self, r): - with pytest.raises(redis.ConnectionError): - r.scard("name") - - def test_sdiff(self, r): - with pytest.raises(redis.ConnectionError): - r.sdiff(["a", "b"]) - - def test_sdiffstore(self, r): - with pytest.raises(redis.ConnectionError): - r.sdiffstore("dest", ["a", "b"]) - - def test_sinter(self, r): - with pytest.raises(redis.ConnectionError): - r.sinter(["a", "b"]) - - def test_sinterstore(self, r): - with pytest.raises(redis.ConnectionError): - r.sinterstore("dest", ["a", "b"]) - - def test_sismember(self, r): - with pytest.raises(redis.ConnectionError): - r.sismember("name", 20) - - def test_smembers(self, r): - with pytest.raises(redis.ConnectionError): - r.smembers("name") - - def test_smove(self, r): - with pytest.raises(redis.ConnectionError): - r.smove("src", "dest", 20) - - def test_spop(self, r): - with pytest.raises(redis.ConnectionError): - r.spop("name") - - def test_srandmember(self, r): - with pytest.raises(redis.ConnectionError): - r.srandmember("name") - - def test_srem(self, r): - with pytest.raises(redis.ConnectionError): - r.srem("name") - - def test_sunion(self, r): - with pytest.raises(redis.ConnectionError): - r.sunion(["a", "b"]) - - def test_sunionstore(self, r): - with pytest.raises(redis.ConnectionError): - r.sunionstore("dest", ["a", "b"]) - - def test_zadd(self, r): - with pytest.raises(redis.ConnectionError): - r.zadd("name", {"key": "value"}) - - def test_zcard(self, r): - with pytest.raises(redis.ConnectionError): - r.zcard("name") - - def test_zcount(self, r): - with pytest.raises(redis.ConnectionError): - r.zcount("name", 1, 5) - - def test_zincrby(self, r): - with pytest.raises(redis.ConnectionError): - r.zincrby("name", 1, 1) - - def test_zinterstore(self, r): - with pytest.raises(redis.ConnectionError): - r.zinterstore("dest", ["a", "b"]) - - def test_zrange(self, r): - with pytest.raises(redis.ConnectionError): - r.zrange("name", 1, 5) - - def test_zrangebyscore(self, r): - with pytest.raises(redis.ConnectionError): - r.zrangebyscore("name", 1, 5) - - def test_rangebylex(self, r): - with pytest.raises(redis.ConnectionError): - r.zrangebylex("name", 1, 4) - - def test_zrem(self, r): - with pytest.raises(redis.ConnectionError): - r.zrem("name", "value") - - def test_zremrangebyrank(self, r): - with pytest.raises(redis.ConnectionError): - r.zremrangebyrank("name", 1, 5) - - def test_zremrangebyscore(self, r): - with pytest.raises(redis.ConnectionError): - r.zremrangebyscore("name", 1, 5) - - def test_zremrangebylex(self, r): - with pytest.raises(redis.ConnectionError): - r.zremrangebylex("name", 1, 5) - - def test_zlexcount(self, r): - with pytest.raises(redis.ConnectionError): - r.zlexcount("name", 1, 5) - - def test_zrevrange(self, r): - with pytest.raises(redis.ConnectionError): - r.zrevrange("name", 1, 5, 1) - - def test_zrevrangebyscore(self, r): - with pytest.raises(redis.ConnectionError): - r.zrevrangebyscore("name", 5, 1) - - def test_zrevrangebylex(self, r): - with pytest.raises(redis.ConnectionError): - r.zrevrangebylex("name", 5, 1) - - def test_zrevran(self, r): - with pytest.raises(redis.ConnectionError): - r.zrevrank("name", 2) - - def test_zscore(self, r): - with pytest.raises(redis.ConnectionError): - r.zscore("name", 2) - - def test_zunionstor(self, r): - with pytest.raises(redis.ConnectionError): - r.zunionstore("dest", ["1", "2"]) - - def test_pipeline(self, r): - with pytest.raises(redis.ConnectionError): - r.pipeline().watch("key") - - def test_transaction(self, r): - with pytest.raises(redis.ConnectionError): - - def func(a): - return a * a - - r.transaction(func, 3) - - def test_lock(self, r): - with pytest.raises(redis.ConnectionError): - with r.lock("name"): - pass - - def test_pubsub(self, r): - with pytest.raises(redis.ConnectionError): - r.pubsub().subscribe("channel") - - def test_pfadd(self, r): - with pytest.raises(redis.ConnectionError): - r.pfadd("name", 1) - - def test_pfmerge(self, r): - with pytest.raises(redis.ConnectionError): - r.pfmerge("dest", "a", "b") - - def test_scan(self, r): - with pytest.raises(redis.ConnectionError): - list(r.scan()) - - def test_sscan(self, r): - with pytest.raises(redis.ConnectionError): - r.sscan("name") - - def test_hscan(self, r): - with pytest.raises(redis.ConnectionError): - r.hscan("name") - - def test_scan_iter(self, r): - with pytest.raises(redis.ConnectionError): - list(r.scan_iter()) - - def test_sscan_iter(self, r): - with pytest.raises(redis.ConnectionError): - list(r.sscan_iter("name")) - - def test_hscan_iter(self, r): - with pytest.raises(redis.ConnectionError): - list(r.hscan_iter("name")) - - -@pytest.mark.disconnected -@testtools.fake_only -class TestPubSubConnected: - @pytest.fixture - def pubsub(self, r): - return r.pubsub() - - def test_basic_subscribe(self, pubsub): - with pytest.raises(redis.ConnectionError): - pubsub.subscribe("logs") - - def test_subscription_conn_lost(self, fake_server, pubsub): - fake_server.connected = True - pubsub.subscribe("logs") - fake_server.connected = False - # The initial message is already in the pipe - msg = pubsub.get_message() - check = {"type": "subscribe", "pattern": None, "channel": b"logs", "data": 1} - assert msg == check, "Message was not published to channel" - with pytest.raises(redis.ConnectionError): - pubsub.get_message() diff --git a/tests/fakeredis/test/test_hypothesis.py b/tests/fakeredis/test/test_hypothesis.py index 7aacfba9d3a0..943fc3b2cd55 100644 --- a/tests/fakeredis/test/test_hypothesis.py +++ b/tests/fakeredis/test/test_hypothesis.py @@ -2,31 +2,20 @@ import math import operator import sys -from typing import Tuple, Any +from typing import Any, Tuple, Union -import fakeredis import hypothesis import hypothesis.stateful import hypothesis.strategies as st import pytest import redis -from fakeredis._server import _create_version from hypothesis.stateful import rule, initialize, precondition from hypothesis.strategies import SearchStrategy -self_strategy = st.runner() - +import fakeredis +from fakeredis._server import _create_version -def get_redis_version() -> Tuple[int]: - try: - r = redis.StrictRedis("localhost", port=6380, db=2) - r.ping() - return _create_version(r.info()["redis_version"]) - except redis.ConnectionError: - return (6,) - finally: - if hasattr(r, "close"): - r.close() # Absent in older versions of redis-py +self_strategy = st.runner() @st.composite @@ -38,7 +27,28 @@ def sample_attr(draw, name): return values[position] -redis_ver = get_redis_version() +def server_info() -> Tuple[str, Union[None, Tuple[int, ...]]]: + """Returns server's version or None if server is not running""" + client = None + try: + client = redis.Redis("localhost", port=6390, db=2) + client_info = client.info() + server_type = "dragonfly" if "dragonfly_version" in client_info else "redis" + server_version = ( + client_info["redis_version"] if server_type != "dragonfly" else (7, 0) + ) + server_version = _create_version(server_version) or (7,) + return server_type, server_version + except redis.ConnectionError as e: + print(e) + pytest.exit("Redis is not running") + return "redis", (6,) + finally: + if hasattr(client, "close"): + client.close() # Absent in older versions of redis-py + + +server_type, redis_ver = server_info() keys = sample_attr("keys") fields = sample_attr("fields") @@ -49,9 +59,7 @@ def sample_attr(draw, name): float_as_bytes = st.builds( lambda x: repr(default_normalize(x)).encode(), st.floats(width=32) ) -counts = st.integers(min_value=-3, max_value=3) | st.integers( - min_value=-2147483648, max_value=2147483647 -) +counts = st.integers(min_value=-3, max_value=3) | st.integers() limits = st.just(()) | st.tuples(st.just("limit"), counts, counts) # Redis has an integer overflow bug in swapdb, so we confine the numbers to # a limited range (https://github.com/antirez/redis/issues/5737). @@ -286,7 +294,7 @@ class CommonMachine(hypothesis.stateful.RuleBasedStateMachine): def __init__(self): super().__init__() try: - self.real = redis.StrictRedis("localhost", port=6380, db=2) + self.real = redis.StrictRedis("localhost", port=6390, db=2) self.real.ping() except redis.ConnectionError: pytest.skip("redis is not running") @@ -294,7 +302,7 @@ def __init__(self): self.real.connection_pool.disconnect() pytest.skip("redis server is not 64-bit") self.fake = fakeredis.FakeStrictRedis( - server=fakeredis.FakeServer(version=redis_ver), port=6380, db=2 + server=fakeredis.FakeServer(version=redis_ver), port=6390, db=2 ) # Disable the response parsing so that we can check the raw values returned self.fake.response_callbacks.clear() @@ -403,15 +411,22 @@ class BaseTest: command_strategy: SearchStrategy create_command_strategy = st.nothing() + command_strategy_redis7 = st.nothing() @pytest.mark.slow def test(self): class Machine(CommonMachine): create_command_strategy = self.create_command_strategy - command_strategy = self.command_strategy + command_strategy = ( + self.command_strategy | self.command_strategy_redis7 + if redis_ver >= (7,) + else self.command_strategy + ) - # hypothesis.settings.register_profile("debug", max_examples=10, verbosity=hypothesis.Verbosity.debug) - # hypothesis.settings.load_profile("debug") + hypothesis.settings.register_profile( + "debug", max_examples=10, verbosity=hypothesis.Verbosity.debug + ) + hypothesis.settings.load_profile("debug") hypothesis.stateful.run_state_machine_as_test(Machine) @@ -420,7 +435,7 @@ class TestConnection(BaseTest): connection_commands = ( commands(st.just("echo"), values) | commands(st.just("ping"), st.lists(values, max_size=2)) - # | commands(st.just("swapdb"), dbnums, dbnums) + | commands(st.just("swapdb"), dbnums, dbnums) ) command_strategy = connection_commands | common_commands @@ -478,6 +493,50 @@ class TestHash(BaseTest): | commands(st.just("hsetnx"), keys, fields, values) | commands(st.just("hstrlen"), keys, fields) ) + command_strategy_redis7 = ( + commands( + st.just("hpersist"), + st.just("fields"), + st.just(2), + st.lists(fields, min_size=2, max_size=2), + ) + | commands( + st.just("hexpiretime"), + st.just("fields"), + st.just(2), + st.lists(fields, min_size=2, max_size=2), + ) + | commands( + st.just("hpexpiretime"), + st.just("fields"), + st.just(2), + st.lists(fields, min_size=2, max_size=2), + ) + | commands( + st.just("hexpire"), + keys, + expires_seconds, + st.none() | st.just("nx"), + st.none() | st.just("xx"), + st.none() | st.just("gt"), + st.none() | st.just("lt"), + st.just("fields"), + st.just(2), + st.lists(fields, min_size=2, max_size=2), + ) + | commands( + st.just("hpexpire"), + keys, + expires_ms, + st.none() | st.just("nx"), + st.none() | st.just("xx"), + st.none() | st.just("gt"), + st.none() | st.just("lt"), + st.just("fields"), + st.just(2), + st.lists(fields, min_size=2, max_size=2), + ) + ) create_command_strategy = commands( st.just("hset"), keys, st.lists(st.tuples(fields, values), min_size=1) ) @@ -499,7 +558,7 @@ class TestList(BaseTest): | commands( st.sampled_from(["lpop", "rpop"]), keys, - st.just(None) | st.just([]) | counts, + st.just(None) | st.just([]) | st.integers(), ) | commands( st.sampled_from(["lpush", "lpushx", "rpush", "rpushx"]), @@ -781,8 +840,3 @@ def mutated_commands(commands): | add_arg(x, args) | swap_args(x), ) - - -class TestFuzz(BaseTest): - command_strategy = mutated_commands(TestJoint.command_strategy) - command_strategy = command_strategy.filter(lambda command: command.testable) diff --git a/tests/fakeredis/test/test_init_args.py b/tests/fakeredis/test/test_init_args.py deleted file mode 100644 index 7b17b8cb69e4..000000000000 --- a/tests/fakeredis/test/test_init_args.py +++ /dev/null @@ -1,168 +0,0 @@ -import fakeredis -import pytest - - -def test_multidb(create_redis): - r1 = create_redis(db=2) - r2 = create_redis(db=3) - - r1["r1"] = "r1" - r2["r2"] = "r2" - - assert "r2" not in r1 - assert "r1" not in r2 - - assert r1["r1"] == b"r1" - assert r2["r2"] == b"r2" - - assert r1.flushall() is True - - assert "r1" not in r1 - assert "r2" not in r2 - - -@pytest.mark.fake -class TestInitArgs: - def test_singleton(self): - shared_server = fakeredis.FakeServer() - r1 = fakeredis.FakeRedis() - r2 = fakeredis.FakeRedis(server=fakeredis.FakeServer()) - r3 = fakeredis.FakeRedis(server=shared_server) - r4 = fakeredis.FakeRedis(server=shared_server) - - r1.set("foo", "bar") - r3.set("bar", "baz") - - assert "foo" in r1 - assert "foo" not in r2 - assert "foo" not in r3 - - assert "bar" in r3 - assert "bar" in r4 - assert "bar" not in r1 - - def test_host_init_arg(self): - db = fakeredis.FakeStrictRedis(host="localhost") - db.set("foo", "bar") - assert db.get("foo") == b"bar" - - def test_from_url(self): - db = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/0") - db.set("foo", "bar") - assert db.get("foo") == b"bar" - - def test_from_url_user(self): - db = fakeredis.FakeStrictRedis.from_url("redis://user@localhost:6380/0") - db.set("foo", "bar") - assert db.get("foo") == b"bar" - - def test_from_url_user_password(self): - db = fakeredis.FakeStrictRedis.from_url( - "redis://user:password@localhost:6380/0" - ) - db.set("foo", "bar") - assert db.get("foo") == b"bar" - - def test_from_url_with_db_arg(self): - db = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/0") - db1 = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/1") - db2 = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/", db=2) - db.set("foo", "foo0") - db1.set("foo", "foo1") - db2.set("foo", "foo2") - assert db.get("foo") == b"foo0" - assert db1.get("foo") == b"foo1" - assert db2.get("foo") == b"foo2" - - def test_from_url_db_value_error(self): - # In the case of ValueError, should default to 0, or be absent in redis-py 4.0 - db = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/a") - assert db.connection_pool.connection_kwargs.get("db", 0) == 0 - - def test_can_pass_through_extra_args(self): - db = fakeredis.FakeStrictRedis.from_url( - "redis://localhost:6380/0", decode_responses=True - ) - db.set("foo", "bar") - assert db.get("foo") == "bar" - - def test_can_allow_extra_args(self): - db = fakeredis.FakeStrictRedis.from_url( - "redis://localhost:6380/0", - socket_connect_timeout=11, - socket_timeout=12, - socket_keepalive=True, - socket_keepalive_options={60: 30}, - socket_type=1, - retry_on_timeout=True, - ) - fake_conn = db.connection_pool.make_connection() - assert fake_conn.socket_connect_timeout == 11 - assert fake_conn.socket_timeout == 12 - assert fake_conn.socket_keepalive is True - assert fake_conn.socket_keepalive_options == {60: 30} - assert fake_conn.socket_type == 1 - assert fake_conn.retry_on_timeout is True - - # Make fallback logic match redis-py - db = fakeredis.FakeStrictRedis.from_url( - "redis://localhost:6380/0", socket_connect_timeout=None, socket_timeout=30 - ) - fake_conn = db.connection_pool.make_connection() - assert fake_conn.socket_connect_timeout == fake_conn.socket_timeout - assert fake_conn.socket_keepalive_options == {} - - def test_repr(self): - # repr is human-readable, so we only test that it doesn't crash, - # and that it contains the db number. - db = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/11") - rep = repr(db) - assert "db=11" in rep - - def test_from_unix_socket(self): - db = fakeredis.FakeStrictRedis.from_url("unix://a/b/c") - db.set("foo", "bar") - assert db.get("foo") == b"bar" - - def test_same_connection_params(self): - r1 = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/11") - r2 = fakeredis.FakeStrictRedis.from_url("redis://localhost:6380/11") - r3 = fakeredis.FakeStrictRedis(server=fakeredis.FakeServer()) - r1.set("foo", "bar") - assert r2.get("foo") == b"bar" - assert not r3.exists("foo") - - def test_new_server_with_positional_args(self): - from fakeredis import FakeRedis - - # same host, default port and db index - fake_redis_1 = FakeRedis("localhost") - fake_redis_2 = FakeRedis("localhost") - - fake_redis_1.set("foo", "bar") - - assert fake_redis_2.get("foo") == b"bar" - - # same host and port - fake_redis_1 = FakeRedis("localhost", 6000) - fake_redis_2 = FakeRedis("localhost", 6000) - - fake_redis_1.set("foo", "bar") - - assert fake_redis_2.get("foo") == b"bar" - - # same connection parameters, but different db index - fake_redis_1 = FakeRedis("localhost", 6000, 0) - fake_redis_2 = FakeRedis("localhost", 6000, 1) - - fake_redis_1.set("foo", "bar") - - assert fake_redis_2.get("foo") is None - - # mix of positional arguments and keyword args - fake_redis_1 = FakeRedis("localhost", port=6000, db=0) - fake_redis_2 = FakeRedis("localhost", port=6000, db=1) - - fake_redis_1.set("foo", "bar") - - assert fake_redis_2.get("foo") is None diff --git a/tests/fakeredis/test/test_lua_modules.py b/tests/fakeredis/test/test_lua_modules.py deleted file mode 100644 index 7489b4a350c6..000000000000 --- a/tests/fakeredis/test/test_lua_modules.py +++ /dev/null @@ -1,73 +0,0 @@ -import json - -import pytest -import redis - -pytestmark = [] -pytestmark.extend( - [ - pytest.mark.asyncio, - ] -) - -lua_modules_test = pytest.importorskip("lupa") - - -@pytest.mark.load_lua_modules("cjson") -async def test_async_asgi_ratelimit_script(async_redis: redis.Redis): - script = """ -local ruleset = cjson.decode(ARGV[1]) - --- Set limits -for i, key in pairs(KEYS) do - redis.call('SET', key, ruleset[key][1], 'EX', ruleset[key][2], 'NX') -end - --- Check limits -for i = 1, #KEYS do - local value = redis.call('GET', KEYS[i]) - if value and tonumber(value) < 1 then - return ruleset[KEYS[i]][2] - end -end - --- Decrease limits -for i, key in pairs(KEYS) do - redis.call('DECR', key) -end -return 0 -""" - - script = async_redis.register_script(script) - ruleset = {"path:get:user:name": (1, 1)} - await script(keys=list(ruleset.keys()), args=[json.dumps(ruleset)]) - - -@pytest.mark.load_lua_modules("cjson") -def test_asgi_ratelimit_script(r: redis.Redis): - script = """ -local ruleset = cjson.decode(ARGV[1]) - --- Set limits -for i, key in pairs(KEYS) do - redis.call('SET', key, ruleset[key][1], 'EX', ruleset[key][2], 'NX') -end - --- Check limits -for i = 1, #KEYS do - local value = redis.call('GET', KEYS[i]) - if value and tonumber(value) < 1 then - return ruleset[KEYS[i]][2] - end -end - --- Decrease limits -for i, key in pairs(KEYS) do - redis.call('DECR', key) -end -return 0 -""" - - script = r.register_script(script) - ruleset = {"path:get:user:name": (1, 1)} - script(keys=list(ruleset.keys()), args=[json.dumps(ruleset)]) diff --git a/tests/fakeredis/test/test_mixins/test_bitmap_commands.py b/tests/fakeredis/test/test_mixins/test_bitmap_commands.py index a9a55b1f0972..ccdd5389ad1f 100644 --- a/tests/fakeredis/test/test_mixins/test_bitmap_commands.py +++ b/tests/fakeredis/test/test_mixins/test_bitmap_commands.py @@ -21,6 +21,19 @@ def test_getbit_wrong_type(r: redis.Redis): r.getbit("foo", 1) +@pytest.mark.min_server("7") +def test_bitcount_error(r: redis.Redis): + with pytest.raises(redis.ResponseError) as e: + raw_command(r, b"BITCOUNT", b"", b"", b"") + assert str(e.value) == "value is not an integer or out of range" + + +@pytest.mark.min_server("7") +def test_bitcount_does_not_exist(r: redis.Redis): + res = raw_command(r, b"BITCOUNT", b"", 0, 0) + assert res == 0 + + def test_multiple_bits_set(r: redis.Redis): r.setbit("foo", 1, 1) r.setbit("foo", 3, 1) @@ -373,10 +386,7 @@ def test_bitfield_incr_sat(r: redis.Redis): r.set(key, b"\xff\xf0\x00") assert r.bitfield(key, "SAT").incrby("u8", 4, 0x123).incrby( "u8", 8, 0x55 - ).execute() == [ - 0xFF, - 0xFF, - ] + ).execute() == [0xFF, 0xFF] assert r.get(key) == b"\xff\xff\x00" assert r.bitfield(key, "SAT").incrby("u12", 0, -1).incrby("u1", 1, 2).execute() == [ 0xFFE, @@ -406,17 +416,11 @@ def test_bitfield_incr_fail(r: redis.Redis): r.set(key, b"\xff\xf0\x00") assert r.bitfield(key, "FAIL").incrby("u8", 4, 0x123).incrby( "u8", 8, 0x55 - ).execute() == [ - None, - None, - ] + ).execute() == [None, None] assert r.get(key) == b"\xff\xf0\x00" assert r.bitfield(key, "FAIL").incrby("u12", 0, -1).incrby( "u1", 1, 2 - ).execute() == [ - 0xFFE, - None, - ] + ).execute() == [0xFFE, None] assert r.get(key) == b"\xff\xe0\x00" assert r.bitfield(key, "FAIL").incrby("i4", 0, 8).incrby("i4", 4, 7).execute() == [ 7, diff --git a/tests/fakeredis/test/test_mixins/test_connection.py b/tests/fakeredis/test/test_mixins/test_connection.py new file mode 100644 index 000000000000..1b14f146da95 --- /dev/null +++ b/tests/fakeredis/test/test_mixins/test_connection.py @@ -0,0 +1,52 @@ +import pytest +import redis +import redis.client +from fakeredis import _msgs as msgs +from redis.exceptions import ResponseError + +from test import testtools +from test.testtools import raw_command + + +def test_ping(r: redis.Redis): + assert r.ping() + assert testtools.raw_command(r, "ping", "test") == b"test" + with pytest.raises( + redis.ResponseError, match=msgs.WRONG_ARGS_MSG6.format("ping")[4:] + ): + raw_command(r, "ping", "arg1", "arg2") + + +def test_echo(r: redis.Redis): + assert r.echo(b"hello") == b"hello" + assert r.echo("hello") == b"hello" + + +def test_unknown_command(r: redis.Redis): + with pytest.raises(redis.ResponseError): + raw_command(r, "0 3 3") + + +@pytest.mark.decode_responses +class TestDecodeResponses: + def test_decode_str(self, r): + r.set("foo", "bar") + assert r.get("foo") == "bar" + + def test_decode_set(self, r): + r.sadd("foo", "member1") + assert set(r.smembers("foo")) == {"member1"} + + def test_decode_list(self, r): + r.rpush("foo", "a", "b") + assert r.lrange("foo", 0, -1) == ["a", "b"] + + def test_decode_dict(self, r): + r.hset("foo", "key", "value") + assert r.hgetall("foo") == {"key": "value"} + + def test_decode_error(self, r): + r.set("foo", "bar") + with pytest.raises(ResponseError) as exc_info: + r.hset("foo", "bar", "baz") + assert isinstance(exc_info.value.args[0], str) diff --git a/tests/fakeredis/test/test_mixins/test_generic_commands.py b/tests/fakeredis/test/test_mixins/test_generic_commands.py index f75647727172..e480bbecd6b8 100644 --- a/tests/fakeredis/test/test_mixins/test_generic_commands.py +++ b/tests/fakeredis/test/test_mixins/test_generic_commands.py @@ -5,6 +5,7 @@ import redis from fakeredis import _msgs as msgs from redis.exceptions import ResponseError + from test.testtools import raw_command diff --git a/tests/fakeredis/test/test_mixins/test_geo_commands.py b/tests/fakeredis/test/test_mixins/test_geo_commands.py index b93743ed0e9e..ef2caa3a50be 100644 --- a/tests/fakeredis/test/test_mixins/test_geo_commands.py +++ b/tests/fakeredis/test/test_mixins/test_geo_commands.py @@ -1,9 +1,10 @@ -from test import testtools from typing import Dict, Any import pytest import redis +from test import testtools + def test_geoadd_ch(r: redis.Redis): values = (2.1909389952632, 41.433791470673, "place1") diff --git a/tests/fakeredis/test/test_mixins/test_hash_commands.py b/tests/fakeredis/test/test_mixins/test_hash_commands.py index 8f1e49feedf4..e2ad1360dae2 100644 --- a/tests/fakeredis/test/test_mixins/test_hash_commands.py +++ b/tests/fakeredis/test/test_mixins/test_hash_commands.py @@ -1,9 +1,9 @@ -from test import testtools - import pytest import redis import redis.client +from test import testtools + def test_hstrlen_missing(r: redis.Redis): assert r.hstrlen("foo", "doesnotexist") == 0 diff --git a/tests/fakeredis/test/test_mixins/test_list_commands.py b/tests/fakeredis/test/test_mixins/test_list_commands.py index e04d7a78f111..18c5ab8f027b 100644 --- a/tests/fakeredis/test/test_mixins/test_list_commands.py +++ b/tests/fakeredis/test/test_mixins/test_list_commands.py @@ -616,13 +616,6 @@ def test_blmove(r: redis.Redis): assert r.blmove("a", "b", 1, "RIGHT", "LEFT") -@pytest.mark.disconnected -@testtools.fake_only -def test_lmove_disconnected_raises_connection_error(r: redis.Redis): - with pytest.raises(redis.ConnectionError): - r.lmove(1, 2, "LEFT", "RIGHT") - - def test_lpos(r: redis.Redis): assert r.rpush("a", "a", "b", "c", "1", "2", "3", "c", "c") == 8 assert r.lpos("a", "a") == 0 diff --git a/tests/fakeredis/test/test_mixins/test_pubsub_commands.py b/tests/fakeredis/test/test_mixins/test_pubsub_commands.py index b09326079a9b..3f5ada8ed75a 100644 --- a/tests/fakeredis/test/test_mixins/test_pubsub_commands.py +++ b/tests/fakeredis/test/test_mixins/test_pubsub_commands.py @@ -5,7 +5,6 @@ from time import sleep from typing import Optional, Dict, Any -import fakeredis import pytest import redis from redis.client import PubSub @@ -363,17 +362,6 @@ def publish(): assert message is None -@pytest.mark.fake -def test_socket_cleanup_pubsub(fake_server): - r1 = fakeredis.FakeStrictRedis(server=fake_server) - r2 = fakeredis.FakeStrictRedis(server=fake_server) - ps = r1.pubsub() - with ps: - ps.subscribe("test") - ps.psubscribe("test*") - r2.publish("test", "foo") - - def test_pubsub_channels(r: redis.Redis): p = r.pubsub() p.subscribe("foo", "bar", "baz", "test") diff --git a/tests/fakeredis/test/test_scan.py b/tests/fakeredis/test/test_mixins/test_scan.py similarity index 99% rename from tests/fakeredis/test/test_scan.py rename to tests/fakeredis/test/test_mixins/test_scan.py index fdbc1b83dd00..f966c13f6416 100644 --- a/tests/fakeredis/test/test_scan.py +++ b/tests/fakeredis/test/test_mixins/test_scan.py @@ -2,6 +2,7 @@ import pytest import redis + from test.testtools import key_val_dict diff --git a/tests/fakeredis/test/test_mixins/test_scripting.py b/tests/fakeredis/test/test_mixins/test_scripting.py index 90cc5201e7c6..3f8cea63acb6 100644 --- a/tests/fakeredis/test/test_mixins/test_scripting.py +++ b/tests/fakeredis/test/test_mixins/test_scripting.py @@ -1,13 +1,10 @@ from __future__ import annotations -import logging -from test import testtools - -import fakeredis import pytest import redis import redis.client from redis.exceptions import ResponseError + from test.testtools import raw_command json_tests = pytest.importorskip("lupa") @@ -552,26 +549,6 @@ def test_script(r: redis.Redis): assert result == b"42" -@testtools.fake_only -def test_lua_log(r, caplog): - logger = fakeredis._server.LOGGER - script = """ - redis.log(redis.LOG_DEBUG, "debug") - redis.log(redis.LOG_VERBOSE, "verbose") - redis.log(redis.LOG_NOTICE, "notice") - redis.log(redis.LOG_WARNING, "warning") - """ - script = r.register_script(script) - with caplog.at_level("DEBUG"): - script() - assert caplog.record_tuples == [ - (logger.name, logging.DEBUG, "debug"), - (logger.name, logging.INFO, "verbose"), - (logger.name, logging.INFO, "notice"), - (logger.name, logging.WARNING, "warning"), - ] - - def test_lua_log_no_message(r: redis.Redis): script = "redis.log(redis.LOG_DEBUG)" script = r.register_script(script) @@ -579,18 +556,6 @@ def test_lua_log_no_message(r: redis.Redis): script() -@testtools.fake_only -def test_lua_log_different_types(r, caplog): - logger = logging.getLogger("fakeredis") - script = "redis.log(redis.LOG_DEBUG, 'string', 1, true, 3.14, 'string')" - script = r.register_script(script) - with caplog.at_level("DEBUG"): - script() - assert caplog.record_tuples == [ - (logger.name, logging.DEBUG, "string 1 3.14 string") - ] - - @pytest.mark.unsupported_server_types("dragonfly") def test_lua_log_wrong_level(r: redis.Redis): script = "redis.log(10, 'string')" @@ -599,19 +564,6 @@ def test_lua_log_wrong_level(r: redis.Redis): script() -@testtools.fake_only -def test_lua_log_defined_vars(r, caplog): - logger = fakeredis._server.LOGGER - script = """ - local var='string' - redis.log(redis.LOG_DEBUG, var) - """ - script = r.register_script(script) - with caplog.at_level("DEBUG"): - script() - assert caplog.record_tuples == [(logger.name, logging.DEBUG, "string")] - - def test_hscan_cursors_are_bytes(r: redis.Redis): r.hset("hkey", "foo", 1) diff --git a/tests/fakeredis/test/test_mixins/test_server_commands.py b/tests/fakeredis/test/test_mixins/test_server_commands.py index b9e1f752a9e1..b09d0dc4dfbe 100644 --- a/tests/fakeredis/test/test_mixins/test_server_commands.py +++ b/tests/fakeredis/test/test_mixins/test_server_commands.py @@ -3,9 +3,7 @@ import pytest import redis -from fakeredis._commands import SUPPORTED_COMMANDS from redis.exceptions import ResponseError -from test.testtools import fake_only @pytest.mark.unsupported_server_types("dragonfly") @@ -46,20 +44,6 @@ def test_lastsave(r: redis.Redis): assert isinstance(r.lastsave(), datetime) -@fake_only -def test_command(r: redis.Redis): - commands_dict = r.command() - one_word_commands = {cmd for cmd in SUPPORTED_COMMANDS if " " not in cmd} - assert one_word_commands - set(commands_dict.keys()) == set() - - -@fake_only -def test_command_count(r: redis.Redis): - assert r.command_count() >= len( - [cmd for cmd in SUPPORTED_COMMANDS if " " not in cmd] - ) - - @pytest.mark.unsupported_server_types("dragonfly") @pytest.mark.slow def test_bgsave_timestamp_update(r: redis.Redis): diff --git a/tests/fakeredis/test/test_sortedset_commands.py b/tests/fakeredis/test/test_mixins/test_sortedset_commands.py similarity index 99% rename from tests/fakeredis/test/test_sortedset_commands.py rename to tests/fakeredis/test/test_mixins/test_sortedset_commands.py index b42fd2ad9a84..0547aeab8a45 100644 --- a/tests/fakeredis/test/test_sortedset_commands.py +++ b/tests/fakeredis/test/test_mixins/test_sortedset_commands.py @@ -2,13 +2,14 @@ import math from collections import OrderedDict -from test import testtools from typing import Tuple, List, Optional import pytest import redis import redis.client +from test import testtools + def round_str(x): assert isinstance(x, bytes) diff --git a/tests/fakeredis/test/test_mixins/test_streams_commands.py b/tests/fakeredis/test/test_mixins/test_streams_commands.py index 7d2dc0696b76..0d63aafca701 100644 --- a/tests/fakeredis/test/test_mixins/test_streams_commands.py +++ b/tests/fakeredis/test/test_mixins/test_streams_commands.py @@ -1,12 +1,12 @@ import threading import time -from test import testtools from typing import List import pytest import redis from fakeredis import _msgs as msgs -from fakeredis._stream import XStream, StreamRangeTest + +from test import testtools def get_ids(results): @@ -20,49 +20,6 @@ def add_items(r: redis.Redis, stream: str, n: int): return id_list -@pytest.mark.fake -def test_xstream(): - stream = XStream() - stream.add([0, 0, 1, 1, 2, 2, 3, 3], "0-1") - stream.add([1, 1, 2, 2, 3, 3, 4, 4], "1-2") - stream.add([2, 2, 3, 3, 4, 4], "1-3") - stream.add([3, 3, 4, 4], "2-1") - stream.add([3, 3, 4, 4], "2-2") - stream.add([3, 3, 4, 4], "3-1") - assert stream.add([3, 3, 4, 4], "4-*") == b"4-0" - assert stream.last_item_key() == b"4-0" - assert stream.add([3, 3, 4, 4], "4-*-*") is None - assert len(stream) == 7 - i = iter(stream) - assert next(i) == [b"0-1", [0, 0, 1, 1, 2, 2, 3, 3]] - assert next(i) == [b"1-2", [1, 1, 2, 2, 3, 3, 4, 4]] - assert next(i) == [b"1-3", [2, 2, 3, 3, 4, 4]] - assert next(i) == [b"2-1", [3, 3, 4, 4]] - assert next(i) == [b"2-2", [3, 3, 4, 4]] - - assert stream.find_index_key_as_str("1-2") == (1, True) - assert stream.find_index_key_as_str("0-1") == (0, True) - assert stream.find_index_key_as_str("2-1") == (3, True) - assert stream.find_index_key_as_str("1-4") == (3, False) - - lst = stream.irange(StreamRangeTest.decode(b"0-2"), StreamRangeTest.decode(b"3-0")) - assert len(lst) == 4 - - stream = XStream() - assert stream.delete(["1"]) == 0 - entry_key: bytes = stream.add([0, 0, 1, 1, 2, 2, 3, 3]) - assert len(stream) == 1 - assert ( - stream.delete( - [ - entry_key, - ] - ) - == 1 - ) - assert len(stream) == 0 - - def test_xadd_redis__green(r: redis.Redis): stream = "stream" before = int(1000 * time.time()) diff --git a/tests/fakeredis/test/test_zadd.py b/tests/fakeredis/test/test_mixins/test_zadd.py similarity index 100% rename from tests/fakeredis/test/test_zadd.py rename to tests/fakeredis/test/test_mixins/test_zadd.py diff --git a/tests/fakeredis/test/test_stack/test_bloom_redis_py.py b/tests/fakeredis/test/test_stack/test_bloom_redis_py.py deleted file mode 100644 index 91093dfb0a54..000000000000 --- a/tests/fakeredis/test/test_stack/test_bloom_redis_py.py +++ /dev/null @@ -1,128 +0,0 @@ -import pytest -import redis.commands.bf -from redis.commands.bf import BFInfo - -json_tests = pytest.importorskip("probables") - - -def intlist(obj): - return [int(v) for v in obj] - - -def test_create_bf(r: redis.Redis): - """Test CREATE/RESERVE calls""" - assert r.bf().create("bloom", 0.01, 1000) - assert r.bf().create("bloom_e", 0.01, 1000, expansion=1) - assert r.bf().create("bloom_ns", 0.01, 1000, noScale=True) - - -@pytest.mark.unsupported_server_types("dragonfly") -def test_create_cf(r: redis.Redis): - assert r.cf().create("cuckoo", 1000) - assert r.cf().create("cuckoo_e", 1000, expansion=1) - assert r.cf().create("cuckoo_bs", 1000, bucket_size=4) - assert r.cf().create("cuckoo_mi", 1000, max_iterations=10) - assert r.cms().initbydim("cmsDim", 100, 5) - assert r.cms().initbyprob("cmsProb", 0.01, 0.01) - assert r.topk().reserve("topk", 5, 100, 5, 0.9) - - -def test_bf_reserve(r: redis.Redis): - """Testing BF.RESERVE""" - assert r.bf().reserve("bloom", 0.01, 1000) - assert r.bf().reserve("bloom_e", 0.01, 1000, expansion=1) - assert r.bf().reserve("bloom_ns", 0.01, 1000, noScale=True) - - -def test_bf_add(r: redis.Redis): - assert r.bf().create("bloom", 0.01, 1000) - assert 1 == r.bf().add("bloom", "foo") - assert 0 == r.bf().add("bloom", "foo") - assert [0] == intlist(r.bf().madd("bloom", "foo")) - assert [0, 1] == r.bf().madd("bloom", "foo", "bar") - assert [0, 0, 1] == r.bf().madd("bloom", "foo", "bar", "baz") - assert 1 == r.bf().exists("bloom", "foo") - assert 0 == r.bf().exists("bloom", "noexist") - assert [1, 0] == intlist(r.bf().mexists("bloom", "foo", "noexist")) - - -@pytest.mark.unsupported_server_types("dragonfly") -def test_bf_insert(r: redis.Redis): - assert r.bf().create("bloom", 0.01, 1000) - assert [1] == intlist(r.bf().insert("bloom", ["foo"])) - assert [0, 1] == intlist(r.bf().insert("bloom", ["foo", "bar"])) - assert [1] == intlist(r.bf().insert("captest", ["foo"], capacity=10)) - assert [1] == intlist(r.bf().insert("errtest", ["foo"], error=0.01)) - assert 1 == r.bf().exists("bloom", "foo") - assert 0 == r.bf().exists("bloom", "noexist") - assert [1, 0] == intlist(r.bf().mexists("bloom", "foo", "noexist")) - info = r.bf().info("bloom") - assert 2 == info.get("insertedNum") - assert 1000 == info.get("capacity") - assert 1 == info.get("filterNum") - - -@pytest.mark.unsupported_server_types("dragonfly") -def test_bf_scandump_and_loadchunk(r: redis.Redis): - r.bf().create("myBloom", "0.0001", "1000") - - # Test is probabilistic and might fail. It is OK to change variables if - # certain to not break anything - - res = 0 - for x in range(1000): - r.bf().add("myBloom", x) - assert r.bf().exists("myBloom", x) - rv = r.bf().exists("myBloom", f"nonexist_{x}") - res += rv == x - assert res < 5 - - cmds = list() - first = 0 - while first is not None: - cur = r.bf().scandump("myBloom", first) - if cur[0] == 0: - first = None - else: - first = cur[0] - cmds.append(cur) - - # Remove the filter - r.bf().client.delete("myBloom") - - # Now, load all the commands: - for cmd in cmds: - r.bf().loadchunk("myBloom1", *cmd) - - for x in range(1000): - assert r.bf().exists("myBloom1", x), f"{x} not in filter" - - -@pytest.mark.unsupported_server_types("dragonfly") -def test_bf_info(r: redis.Redis): - # Store a filter - r.bf().create("nonscaling", "0.0001", "1000", noScale=True) - info: BFInfo = r.bf().info("nonscaling") - assert info.expansionRate is None - - expansion = 4 - r.bf().create("expanding", "0.0001", "1000", expansion=expansion) - info = r.bf().info("expanding") - assert info.expansionRate == 4 - assert info.capacity == 1000 - assert info.insertedNum == 0 - - -@pytest.mark.unsupported_server_types("dragonfly") -def test_bf_card(r: redis.Redis): - # return 0 if the key does not exist - assert r.bf().card("not_exist") == 0 - - # Store a filter - assert r.bf().add("bf1", "item_foo") == 1 - assert r.bf().card("bf1") == 1 - - # Error when key is of a type other than Bloom filter. - with pytest.raises(redis.ResponseError): - r.set("setKey", "value") - r.bf().card("setKey") diff --git a/tests/fakeredis/test/test_stack/test_bloomfilter.py b/tests/fakeredis/test/test_stack/test_bloomfilter.py index 11f39b19767a..3c61fb871dd4 100644 --- a/tests/fakeredis/test/test_stack/test_bloomfilter.py +++ b/tests/fakeredis/test/test_stack/test_bloomfilter.py @@ -1,11 +1,44 @@ import pytest import redis +from redis.commands.bf import BFInfo from fakeredis import _msgs as msgs bloom_tests = pytest.importorskip("probables") +def intlist(obj): + return [int(v) for v in obj] + + +def test_create_bf(r: redis.Redis): + assert r.bf().create("bloom", 0.01, 1000) + assert r.bf().create("bloom_e", 0.01, 1000, expansion=1) + assert r.bf().create("bloom_ns", 0.01, 1000, noScale=True) + + +@pytest.mark.unsupported_server_types("dragonfly") +def test_create_cf(r: redis.Redis): + assert r.cf().create("cuckoo", 1000) + assert r.cf().create("cuckoo_e", 1000, expansion=1) + assert r.cf().create("cuckoo_bs", 1000, bucket_size=4) + assert r.cf().create("cuckoo_mi", 1000, max_iterations=10) + assert r.cms().initbydim("cmsDim", 100, 5) + assert r.cms().initbyprob("cmsProb", 0.01, 0.01) + assert r.topk().reserve("topk", 5, 100, 5, 0.9) + + +def test_bf_reserve(r: redis.Redis): + assert r.bf().reserve("bloom", 0.01, 1000) + assert r.bf().reserve("bloom_ns", 0.01, 1000, noScale=True) + with pytest.raises( + redis.exceptions.ResponseError, match=msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG + ): + assert r.bf().reserve("bloom_e", 0.01, 1000, expansion=1, noScale=True) + with pytest.raises(redis.exceptions.ResponseError, match=msgs.ITEM_EXISTS_MSG): + assert r.bf().reserve("bloom", 0.01, 1000) + + def test_bf_add(r: redis.Redis): assert r.bf().add("key", "value") == 1 assert r.bf().add("key", "value") == 0 @@ -13,6 +46,15 @@ def test_bf_add(r: redis.Redis): r.set("key1", "value") with pytest.raises(redis.exceptions.ResponseError): r.bf().add("key1", "v") + assert r.bf().create("bloom", 0.01, 1000) + assert 1 == r.bf().add("bloom", "foo") + assert 0 == r.bf().add("bloom", "foo") + assert [0] == intlist(r.bf().madd("bloom", "foo")) + assert [0, 1] == r.bf().madd("bloom", "foo", "bar") + assert [0, 0, 1] == r.bf().madd("bloom", "foo", "bar", "baz") + assert 1 == r.bf().exists("bloom", "foo") + assert 0 == r.bf().exists("bloom", "noexist") + assert [1, 0] == intlist(r.bf().mexists("bloom", "foo", "noexist")) def test_bf_madd(r: redis.Redis): @@ -33,6 +75,17 @@ def test_bf_card(r: redis.Redis): r.set("key1", "value") with pytest.raises(redis.exceptions.ResponseError): r.bf().card("key1") + # return 0 if the key does not exist + assert r.bf().card("not_exist") == 0 + + # Store a filter + assert r.bf().add("bf1", "item_foo") == 1 + assert r.bf().card("bf1") == 1 + + # Error when key is of a type other than Bloom filter. + with pytest.raises(redis.ResponseError): + r.set("setKey", "value") + r.bf().card("setKey") def test_bf_exists(r: redis.Redis): @@ -61,29 +114,78 @@ def test_bf_mexists(r: redis.Redis): r.bf().add("key1", "v") -@pytest.mark.unsupported_server_types("dragonfly") -def test_bf_reserve(r: redis.Redis): - assert r.bf().reserve("bloom", 0.01, 1000) - assert r.bf().reserve("bloom_ns", 0.01, 1000, noScale=True) - with pytest.raises( - redis.exceptions.ResponseError, match=msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG - ): - assert r.bf().reserve("bloom_e", 0.01, 1000, expansion=1, noScale=True) - with pytest.raises(redis.exceptions.ResponseError, match=msgs.ITEM_EXISTS_MSG): - assert r.bf().reserve("bloom", 0.01, 1000) - - @pytest.mark.unsupported_server_types("dragonfly") def test_bf_insert(r: redis.Redis): - assert r.bf().create("bloom", 0.01, 1000) - assert r.bf().insert("bloom", ["foo"]) == [1] - assert r.bf().insert("bloom", ["foo", "bar"]) == [0, 1] + assert r.bf().create("key", 0.01, 1000) + assert r.bf().insert("key", ["foo"]) == [1] + assert r.bf().insert("key", ["foo", "bar"]) == [0, 1] assert r.bf().insert("captest", ["foo"], capacity=10) == [1] assert r.bf().insert("errtest", ["foo"], error=0.01) == [1] - assert r.bf().exists("bloom", "foo") == 1 - assert r.bf().exists("bloom", "noexist") == 0 - assert r.bf().mexists("bloom", "foo", "noexist") == [1, 0] + assert r.bf().exists("key", "foo") == 1 + assert r.bf().exists("key", "noexist") == 0 + assert r.bf().mexists("key", "foo", "noexist") == [1, 0] with pytest.raises(redis.exceptions.ResponseError, match=msgs.NOT_FOUND_MSG): r.bf().insert("nocreate", [1, 2, 3], noCreate=True) # with pytest.raises(redis.exceptions.ResponseError, match=msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG): # r.bf().insert("nocreate", [1, 2, 3], expansion=2, noScale=True) + assert r.bf().create("bloom", 0.01, 1000) + assert [1] == intlist(r.bf().insert("bloom", ["foo"])) + assert [0, 1] == intlist(r.bf().insert("bloom", ["foo", "bar"])) + assert 1 == r.bf().exists("bloom", "foo") + assert 0 == r.bf().exists("bloom", "noexist") + assert [1, 0] == intlist(r.bf().mexists("bloom", "foo", "noexist")) + info = r.bf().info("bloom") + assert 2 == info.get("insertedNum") + assert 1000 == info.get("capacity") + assert 1 == info.get("filterNum") + + +@pytest.mark.unsupported_server_types("dragonfly") +def test_bf_scandump_and_loadchunk(r: redis.Redis): + r.bf().create("myBloom", "0.0001", "1000") + + # Test is probabilistic and might fail. It is OK to change variables if + # certain to not break anything + + res = 0 + for x in range(1000): + r.bf().add("myBloom", x) + assert r.bf().exists("myBloom", x) + rv = r.bf().exists("myBloom", f"nonexist_{x}") + res += rv == x + assert res < 5 + + cmds = list() + first = 0 + while first is not None: + cur = r.bf().scandump("myBloom", first) + if cur[0] == 0: + first = None + else: + first = cur[0] + cmds.append(cur) + + # Remove the filter + r.bf().client.delete("myBloom") + + # Now, load all the commands: + for cmd in cmds: + r.bf().loadchunk("myBloom1", *cmd) + + for x in range(1000): + assert r.bf().exists("myBloom1", x), f"{x} not in filter" + + +@pytest.mark.unsupported_server_types("dragonfly") +def test_bf_info(r: redis.Redis): + # Store a filter + r.bf().create("nonscaling", "0.0001", "1000", noScale=True) + info: BFInfo = r.bf().info("nonscaling") + assert info.expansionRate is None + + expansion = 4 + r.bf().create("expanding", "0.0001", "1000", expansion=expansion) + info = r.bf().info("expanding") + assert info.expansionRate == 4 + assert info.capacity == 1000 + assert info.insertedNum == 0