remove lmpop unit tests

Also remove sentinel_test.py only for this branch.

Signed-off-by: adi_holden <adi@dragonflydb.io>
Signed-off-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
adi_holden
2025-02-09 11:24:14 +02:00
committed by Roman Gershman
parent b16b5349e6
commit e713b8c692
2 changed files with 0 additions and 529 deletions

View File

@ -1086,227 +1086,6 @@ TEST_F(ListFamilyTest, ContendExpire) {
}
}
TEST_F(ListFamilyTest, LMPopInvalidSyntax) {
// Not enough arguments
auto resp = Run({"lmpop", "1", "a"});
EXPECT_THAT(resp, ErrArg("wrong number of arguments"));
// Zero keys
resp = Run({"lmpop", "0", "LEFT", "COUNT", "1"});
EXPECT_THAT(resp, ErrArg("syntax error"));
// Number of keys is not uint
resp = Run({"lmpop", "aa", "a", "LEFT"});
EXPECT_THAT(resp, ErrArg("value is not an integer or out of range"));
// Missing LEFT/RIGHT
resp = Run({"lmpop", "1", "a", "COUNT", "1"});
EXPECT_THAT(resp, ErrArg("syntax error"));
// Wrong number of keys
resp = Run({"lmpop", "1", "a", "b", "LEFT"});
EXPECT_THAT(resp, ErrArg("syntax error"));
// COUNT without number
resp = Run({"lmpop", "1", "a", "LEFT", "COUNT"});
EXPECT_THAT(resp, ErrArg("syntax error"));
// COUNT is not uint
resp = Run({"lmpop", "1", "a", "LEFT", "COUNT", "boo"});
EXPECT_THAT(resp, ErrArg("value is not an integer or out of range"));
// Too many arguments
resp = Run({"lmpop", "1", "c", "LEFT", "COUNT", "2", "foo"});
EXPECT_THAT(resp, ErrArg("syntax error"));
}
TEST_F(ListFamilyTest, LMPop) {
// All lists are empty
auto resp = Run({"lmpop", "1", "e", "LEFT"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
// LEFT operation
resp = Run({"lpush", "a", "a1", "a2"});
EXPECT_THAT(resp, IntArg(2));
resp = Run({"lmpop", "1", "a", "LEFT"});
EXPECT_THAT(resp, RespArray(ElementsAre("a", RespArray(ElementsAre("a2")))));
// RIGHT operation
resp = Run({"lpush", "b", "b1", "b2"});
EXPECT_THAT(resp, IntArg(2));
resp = Run({"lmpop", "1", "b", "RIGHT"});
EXPECT_THAT(resp, RespArray(ElementsAre("b", RespArray(ElementsAre("b1")))));
// COUNT > 1
resp = Run({"lpush", "c", "c1", "c2"});
EXPECT_THAT(resp, IntArg(2));
resp = Run({"lmpop", "1", "c", "RIGHT", "COUNT", "2"});
EXPECT_THAT(resp, RespArray(ElementsAre("c", RespArray(ElementsAre("c1", "c2")))));
resp = Run({"llen", "c"});
EXPECT_THAT(resp, IntArg(0));
// COUNT > number of elements in list
resp = Run({"lpush", "d", "d1", "d2"});
EXPECT_THAT(resp, IntArg(2));
resp = Run({"lmpop", "1", "d", "RIGHT", "COUNT", "3"});
EXPECT_THAT(resp, RespArray(ElementsAre("d", RespArray(ElementsAre("d1", "d2")))));
resp = Run({"llen", "d"});
EXPECT_THAT(resp, IntArg(0));
// First non-empty list is not the first list
resp = Run({"lpush", "x", "x1"});
EXPECT_THAT(resp, IntArg(1));
resp = Run({"lpush", "y", "y1"});
EXPECT_THAT(resp, IntArg(1));
resp = Run({"lmpop", "3", "empty", "x", "y", "RIGHT"});
EXPECT_THAT(resp, RespArray(ElementsAre("x", RespArray(ElementsAre("x1")))));
resp = Run({"llen", "x"});
EXPECT_THAT(resp, IntArg(0));
}
TEST_F(ListFamilyTest, LMPopMultipleElements) {
// Test removing multiple elements from left end
Run({"rpush", "list1", "a", "b", "c", "d", "e"});
auto resp = Run({"lmpop", "1", "list1", "LEFT", "COUNT", "3"});
EXPECT_THAT(resp, RespArray(ElementsAre("list1", RespArray(ElementsAre("a", "b", "c")))));
resp = Run({"lrange", "list1", "0", "-1"});
EXPECT_THAT(resp.GetVec(), ElementsAre("d", "e"));
// Test removing multiple elements from right end
Run({"rpush", "list2", "v", "w", "x", "y", "z"});
resp = Run({"lmpop", "1", "list2", "RIGHT", "COUNT", "2"});
EXPECT_THAT(resp, RespArray(ElementsAre("list2", RespArray(ElementsAre("z", "y")))));
resp = Run({"lrange", "list2", "0", "-1"});
EXPECT_THAT(resp.GetVec(), ElementsAre("v", "w", "x"));
}
TEST_F(ListFamilyTest, LMPopMultipleLists) {
// Test finding first non-empty list
Run({"rpush", "list1", "a", "b"});
Run({"rpush", "list2", "c", "d"});
Run({"rpush", "list3", "e", "f"});
// Pop from first non-empty list
auto resp = Run({"lmpop", "3", "list1", "list2", "list3", "LEFT"});
EXPECT_THAT(resp, RespArray(ElementsAre("list1", RespArray(ElementsAre("a")))));
// Pop from second list after first becomes empty
Run({"lmpop", "1", "list1", "LEFT"}); // Empty list1
resp = Run({"lmpop", "3", "list1", "list2", "list3", "RIGHT", "COUNT", "2"});
EXPECT_THAT(resp, RespArray(ElementsAre("list2", RespArray(ElementsAre("d", "c")))));
// Verify third list remains untouched
resp = Run({"lrange", "list3", "0", "-1"});
EXPECT_THAT(resp.GetVec(), ElementsAre("e", "f"));
}
TEST_F(ListFamilyTest, LMPopEdgeCases) {
// Test with empty list
Run({"rpush", "empty_list", "a"});
Run({"lpop", "empty_list"});
auto resp = Run({"lmpop", "1", "empty_list", "LEFT"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
// Test with non-existent list
resp = Run({"lmpop", "1", "nonexistent", "LEFT"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
// Test with wrong type key
Run({"set", "string_key", "value"});
resp = Run({"lmpop", "1", "string_key", "LEFT"});
EXPECT_THAT(resp, ErrArg("WRONGTYPE Operation against a key holding the wrong kind of value"));
// Test without COUNT parameter - should return 1 element by default
Run({"rpush", "list", "a", "b"});
resp = Run({"lmpop", "1", "list", "LEFT"});
EXPECT_THAT(resp,
RespArray(ElementsAre(
"list", RespArray(ElementsAre("a"))))); // Should return 1 element by default
// Test with COUNT = 0 - should return error
resp = Run({"lmpop", "1", "list", "LEFT", "COUNT", "0"});
EXPECT_THAT(resp, RespArray(ElementsAre("list", RespArray(ElementsAre()))));
// Test with negative COUNT - should return error
resp = Run({"lmpop", "1", "list", "LEFT", "COUNT", "-1"});
EXPECT_THAT(resp, RespArray(ElementsAre("list", RespArray(ElementsAre("b")))));
}
TEST_F(ListFamilyTest, LMPopDocExample) {
// Try to pop from non-existing lists
auto resp = Run({"LMPOP", "2", "non1", "non2", "LEFT", "COUNT", "10"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
// Create first list and test basic pop
resp = Run({"LPUSH", "mylist", "one", "two", "three", "four", "five"});
EXPECT_THAT(resp, IntArg(5));
resp = Run({"LMPOP", "1", "mylist", "LEFT"});
EXPECT_THAT(resp, RespArray(ElementsAre("mylist", RespArray(ElementsAre("five")))));
resp = Run({"LRANGE", "mylist", "0", "-1"});
EXPECT_THAT(resp.GetVec(), ElementsAre("four", "three", "two", "one"));
// Test RIGHT pop with COUNT
resp = Run({"LMPOP", "1", "mylist", "RIGHT", "COUNT", "10"});
EXPECT_THAT(resp, RespArray(ElementsAre("mylist",
RespArray(ElementsAre("one", "two", "three", "four")))));
// Create two lists and test multi-key pop
resp = Run({"LPUSH", "mylist", "one", "two", "three", "four", "five"});
EXPECT_THAT(resp, IntArg(5));
resp = Run({"LPUSH", "mylist2", "a", "b", "c", "d", "e"});
EXPECT_THAT(resp, IntArg(5));
resp = Run({"LMPOP", "2", "mylist", "mylist2", "RIGHT", "COUNT", "3"});
EXPECT_THAT(resp,
RespArray(ElementsAre("mylist", RespArray(ElementsAre("one", "two", "three")))));
resp = Run({"LRANGE", "mylist", "0", "-1"});
EXPECT_THAT(resp.GetVec(), ElementsAre("five", "four"));
resp = Run({"LMPOP", "2", "mylist", "mylist2", "RIGHT", "COUNT", "5"});
EXPECT_THAT(resp, RespArray(ElementsAre("mylist", RespArray(ElementsAre("four", "five")))));
resp = Run({"LMPOP", "2", "mylist", "mylist2", "RIGHT", "COUNT", "10"});
EXPECT_THAT(resp,
RespArray(ElementsAre("mylist2", RespArray(ElementsAre("a", "b", "c", "d", "e")))));
// Verify both lists are now empty
resp = Run({"EXISTS", "mylist", "mylist2"});
EXPECT_THAT(resp, IntArg(0));
}
TEST_F(ListFamilyTest, LMPopWrongType) {
// Setup: create a list and a hash
Run({"lpush", "l1", "e1"});
Run({"hset", "foo", "k1", "v1"});
// Test: first key is wrong type
auto resp = Run({"lmpop", "2", "foo", "l1", "left"});
EXPECT_THAT(resp, ErrArg("WRONGTYPE Operation against a key holding the wrong kind of value"));
// Test: second key is wrong type but first doesn't exist
resp = Run({"lmpop", "2", "nonexistent", "foo", "left"});
EXPECT_THAT(resp, ErrArg("WRONGTYPE Operation against a key holding the wrong kind of value"));
// Test: second key is wrong type but first is a valid list
resp = Run({"lmpop", "2", "l1", "foo", "left"});
EXPECT_THAT(resp, RespArray(ElementsAre("l1", RespArray(ElementsAre("e1")))));
}
// Reproduce a flow that trigerred a wrong DCHECK in the transaction flow.
TEST_F(ListFamilyTest, AwakeMulti) {
auto f1 = pp_->at(1)->LaunchFiber(Launch::dispatch, [&] {

View File

@ -1,308 +0,0 @@
import pathlib
import subprocess
from typing import Awaitable
from redis import asyncio as aioredis
import pytest
import time
import asyncio
from datetime import datetime
from sys import stderr
import logging
from .utility import assert_eventually, wait_available_async
from .instance import DflyInstanceFactory
from . import dfly_args
# Helper function to parse some sentinel cli commands output as key value dictionaries.
# Output is expected be of even number of lines where each pair of consecutive lines results in a single key value pair.
# If new_dict_key is not empty, encountering it in the output will start a new dictionary, this let us return multiple
# dictionaries, for example in the 'slaves' command, one dictionary for each slave.
def stdout_as_list_of_dicts(cp: subprocess.CompletedProcess, new_dict_key=""):
lines = cp.stdout.splitlines()
res = []
d = None
if new_dict_key == "":
d = dict()
res.append(d)
for i in range(0, len(lines), 2):
if (lines[i]) == new_dict_key: # assumes output never has '' as a key
d = dict()
res.append(d)
d[lines[i]] = lines[i + 1]
return res
def wait_for(func, pred, timeout_sec, timeout_msg=""):
while not pred(func()):
assert timeout_sec > 0, timeout_msg
timeout_sec = timeout_sec - 1
time.sleep(1)
async def await_for(func, pred, timeout_sec, timeout_msg=""):
done = False
while not done:
val = func()
if isinstance(val, Awaitable):
val = await val
done = pred(val)
assert timeout_sec > 0, timeout_msg
timeout_sec = timeout_sec - 1
await asyncio.sleep(1)
@assert_eventually
async def assert_master_became_replica(client):
repl_info = await client.info("replication")
assert repl_info["role"] == "slave"
class Sentinel:
def __init__(self, port, master_port, config_dir) -> None:
self.config_file = pathlib.Path(config_dir).joinpath("sentinel.conf")
self.port = port
self.image = "bitnami/redis-sentinel:latest"
self.container_name = "sentinel_test_py_sentinel"
self.default_deployment = "my_deployment"
self.initial_master_port = master_port
self.proc = None
def start(self):
config = [
f"port {self.port}",
f"sentinel monitor {self.default_deployment} 127.0.0.1 {self.initial_master_port} 1",
f"sentinel down-after-milliseconds {self.default_deployment} 3000",
f"slave-priority 100",
]
self.config_file.write_text("\n".join(config))
logging.info(self.config_file.read_text())
self.proc = subprocess.Popen(
["redis-server", f"{self.config_file.absolute()}", "--sentinel"]
)
def stop(self):
self.proc.terminate()
self.proc.wait(timeout=10)
def run_cmd(
self, args, sentinel_cmd=True, capture_output=False, assert_ok=True
) -> subprocess.CompletedProcess:
run_args = ["redis-cli", "-p", f"{self.port}"]
if sentinel_cmd:
run_args = run_args + ["sentinel"]
run_args = run_args + args
cp = subprocess.run(run_args, capture_output=capture_output, text=True)
if assert_ok:
assert cp.returncode == 0, f"Command failed: {run_args}"
return cp
def wait_ready(self):
wait_for(
lambda: self.run_cmd(["ping"], sentinel_cmd=False, assert_ok=False),
lambda cp: cp.returncode == 0,
timeout_sec=10,
timeout_msg="Timeout waiting for sentinel to become ready.",
)
def master(self, deployment="") -> dict:
if deployment == "":
deployment = self.default_deployment
cp = self.run_cmd(["master", deployment], capture_output=True)
return stdout_as_list_of_dicts(cp)[0]
def slaves(self, deployment="") -> dict:
if deployment == "":
deployment = self.default_deployment
cp = self.run_cmd(["slaves", deployment], capture_output=True)
return stdout_as_list_of_dicts(cp)
def live_master_port(self, deployment=""):
if deployment == "":
deployment = self.default_deployment
cp = self.run_cmd(["get-master-addr-by-name", deployment], capture_output=True)
return int(cp.stdout.splitlines()[1])
def failover(self, deployment=""):
if deployment == "":
deployment = self.default_deployment
self.run_cmd(
[
"failover",
deployment,
]
)
@pytest.fixture(
scope="function"
) # Sentinel has state which we don't want carried over form test to test.
def sentinel(tmp_dir, port_picker) -> Sentinel:
s = Sentinel(port_picker.get_available_port(), port_picker.get_available_port(), tmp_dir)
s.start()
s.wait_ready()
yield s
s.stop()
@pytest.mark.asyncio
@pytest.mark.slow
async def test_failover(df_factory: DflyInstanceFactory, sentinel, port_picker):
master = df_factory.create(port=sentinel.initial_master_port)
replica = df_factory.create(port=port_picker.get_available_port())
master.start()
replica.start()
master_client = aioredis.Redis(port=master.port)
replica_client = aioredis.Redis(port=replica.port)
logging.info("master: " + str(master.port) + " replica: " + str(replica.port))
await replica_client.execute_command("REPLICAOF localhost " + str(master.port))
assert sentinel.live_master_port() == master.port
# Verify sentinel picked up replica.
await await_for(
lambda: sentinel.master(),
lambda m: m["num-slaves"] == "1",
timeout_sec=15,
timeout_msg="Timeout waiting for sentinel to pick up replica.",
)
sentinel.failover()
# Verify sentinel switched.
await await_for(
lambda: sentinel.live_master_port(),
lambda p: p == replica.port,
timeout_sec=10,
timeout_msg="Timeout waiting for sentinel to report replica as master.",
)
assert sentinel.slaves()[0]["port"] == str(master.port)
# Verify we can now write to replica and read replicated value from master.
assert await replica_client.set("key", "value"), "Failed to set key on promoted replica."
logging.info("key was set on promoted replica, awaiting get on promoted replica. ")
await assert_master_became_replica(master_client)
await wait_available_async(master_client)
try:
await await_for(
lambda: master_client.get("key"),
lambda val: val == b"value",
10,
"Timeout waiting for key to exist in replica.",
)
except AssertionError:
syncid, r_offset = await master_client.execute_command("DEBUG REPLICA OFFSET")
replicaoffset_cmd = "DFLY REPLICAOFFSET " + syncid.decode()
m_offset = await replica_client.execute_command(replicaoffset_cmd)
logging.info(f"{syncid.decode()} {r_offset} {m_offset}")
logging.info("replica client role:")
logging.info(await replica_client.execute_command("role"))
logging.info("master client role:")
logging.info(await master_client.execute_command("role"))
logging.info("replica client info:")
logging.info(await replica_client.info())
logging.info("master client info:")
logging.info(await master_client.info())
replica_val = await replica_client.get("key")
master_val = await master_client.get("key")
logging.info(f"replica val: {replica_val}")
logging.info(f"master val: {master_val}")
raise
@pytest.mark.asyncio
@pytest.mark.slow
async def test_master_failure(df_factory, sentinel, port_picker):
master = df_factory.create(port=sentinel.initial_master_port)
replica = df_factory.create(port=port_picker.get_available_port())
master.start()
replica.start()
replica_client = aioredis.Redis(port=replica.port)
await replica_client.execute_command("REPLICAOF localhost " + str(master.port))
assert sentinel.live_master_port() == master.port
# Verify sentinel picked up replica.
await await_for(
lambda: sentinel.master(),
lambda m: m["num-slaves"] == "1",
timeout_sec=15,
timeout_msg="Timeout waiting for sentinel to pick up replica.",
)
# Simulate master failure.
master.stop()
# Verify replica promoted.
await await_for(
lambda: sentinel.live_master_port(),
lambda p: p == replica.port,
timeout_sec=300,
timeout_msg="Timeout waiting for sentinel to report replica as master.",
)
# Verify we can now write to replica.
await replica_client.set("key", "value")
assert await replica_client.get("key") == b"value"
@dfly_args({"info_replication_valkey_compatible": True})
@pytest.mark.asyncio
async def test_priority_on_failover(df_factory, sentinel, port_picker):
master = df_factory.create(port=sentinel.initial_master_port)
# lower priority is the best candidate for sentinel
low_priority_repl = df_factory.create(
port=port_picker.get_available_port(), replica_priority=20
)
mid_priority_repl = df_factory.create(
port=port_picker.get_available_port(), replica_priority=60
)
high_priority_repl = df_factory.create(
port=port_picker.get_available_port(), replica_priority=80
)
master.start()
low_priority_repl.start()
mid_priority_repl.start()
high_priority_repl.start()
high_client = aioredis.Redis(port=high_priority_repl.port)
await high_client.execute_command("REPLICAOF localhost " + str(master.port))
mid_client = aioredis.Redis(port=mid_priority_repl.port)
await mid_client.execute_command("REPLICAOF localhost " + str(master.port))
low_client = aioredis.Redis(port=low_priority_repl.port)
await low_client.execute_command("REPLICAOF localhost " + str(master.port))
assert sentinel.live_master_port() == master.port
# Verify sentinel picked up replica.
await await_for(
lambda: sentinel.master(),
lambda m: m["num-slaves"] == "3",
timeout_sec=15,
timeout_msg="Timeout waiting for sentinel to pick up replica.",
)
# Simulate master failure.
master.stop()
# Verify replica promoted.
await await_for(
lambda: sentinel.live_master_port(),
lambda p: p == low_priority_repl.port,
timeout_sec=30,
timeout_msg="Timeout waiting for sentinel to report replica as master.",
)