test: cover shardIndex and shardPid
This commit is contained in:
1 parent
fb085d0d90
commit
64b16d4761
1 file changed
+189
@@ -156,3 +156,192 @@ defmodule Excache.StoreOpsRegressionTest do
|
||||
key = "regr:lpos:blast-radius"
|
||||
neighbour = keyInSameShard("regr:lpos:neighbour-", key)
|
||||
|
||||
assert :ok = Store.set(neighbour, "survivor", [])
|
||||
assert {:ok, 1} = Store.rpush(key, ["only"])
|
||||
shardBefore = shardPid(key)
|
||||
|
||||
assert {:ok, 0} = Store.lpos(key, "only", 1, nil, 0)
|
||||
|
||||
assert shardPid(key) == shardBefore
|
||||
assert {:ok, "survivor"} = Store.get(neighbour)
|
||||
end
|
||||
|
||||
test "a nil count with no match yields nil rather than an empty list" do
|
||||
key = "regr:lpos:no-match"
|
||||
assert {:ok, 3} = Store.rpush(key, ["a", "b", "c"])
|
||||
|
||||
assert {:ok, nil} = Store.lpos(key, "zzz", 1, nil, 0)
|
||||
assert {:ok, nil} = Store.lpos("regr:lpos:missing-key", "a", 1, nil, 0)
|
||||
end
|
||||
|
||||
test "a nil count honours a positive rank" do
|
||||
key = "regr:lpos:rank-positive"
|
||||
assert {:ok, 4} = Store.rpush(key, ["a", "b", "c", "b"])
|
||||
|
||||
assert {:ok, 1} = Store.lpos(key, "b", 1, nil, 0)
|
||||
assert {:ok, 3} = Store.lpos(key, "b", 2, nil, 0)
|
||||
assert {:ok, nil} = Store.lpos(key, "b", 3, nil, 0)
|
||||
end
|
||||
|
||||
test "a nil count honours a negative rank and searches from the tail" do
|
||||
key = "regr:lpos:rank-negative"
|
||||
assert {:ok, 4} = Store.rpush(key, ["a", "b", "c", "b"])
|
||||
|
||||
assert {:ok, 3} = Store.lpos(key, "b", -1, nil, 0)
|
||||
assert {:ok, 1} = Store.lpos(key, "b", -2, nil, 0)
|
||||
assert {:ok, nil} = Store.lpos(key, "b", -3, nil, 0)
|
||||
end
|
||||
|
||||
test "a nil count honours maxlen" do
|
||||
key = "regr:lpos:maxlen"
|
||||
assert {:ok, 4} = Store.rpush(key, ["a", "b", "c", "b"])
|
||||
|
||||
assert {:ok, 1} = Store.lpos(key, "b", 1, nil, 2)
|
||||
assert {:ok, nil} = Store.lpos(key, "b", 1, nil, 1)
|
||||
end
|
||||
|
||||
test "a zero count still means every match, in both rank directions" do
|
||||
key = "regr:lpos:count-zero"
|
||||
assert {:ok, 4} = Store.rpush(key, ["a", "b", "c", "b"])
|
||||
|
||||
assert {:ok, [1, 3]} = Store.lpos(key, "b", 1, 0, 0)
|
||||
assert {:ok, [3, 1]} = Store.lpos(key, "b", -1, 0, 0)
|
||||
assert {:ok, [1]} = Store.lpos(key, "b", 1, 1, 0)
|
||||
end
|
||||
|
||||
test "a nil count on a wrong type key reports WRONGTYPE instead of crashing" do
|
||||
key = "regr:lpos:wrongtype"
|
||||
assert :ok = Store.set(key, "not-a-list", [])
|
||||
shardBefore = shardPid(key)
|
||||
|
||||
assert {:error, :wrongtype} = Store.lpos(key, "a", 1, nil, 0)
|
||||
assert shardPid(key) == shardBefore
|
||||
end
|
||||
|
||||
test "LPOS over RESP is unaffected by the fix", %{conn: conn} do
|
||||
assert {4, conn} = Client.command(conn, ["RPUSH", "regr:lpos:resp", "a", "b", "c", "b"])
|
||||
|
||||
assert {1, conn} = Client.command(conn, ["LPOS", "regr:lpos:resp", "b"])
|
||||
assert {nil, conn} = Client.command(conn, ["LPOS", "regr:lpos:resp", "zzz"])
|
||||
assert {3, conn} = Client.command(conn, ["LPOS", "regr:lpos:resp", "b", "RANK", "-1"])
|
||||
assert {[1, 3], _conn} = Client.command(conn, ["LPOS", "regr:lpos:resp", "b", "COUNT", "0"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "HMGET and SMISMEMBER must surface WRONGTYPE" do
|
||||
test "HMGET on a key of the wrong type reports the error instead of nils" do
|
||||
key = "regr:hmget:wrongtype"
|
||||
assert :ok = Store.set(key, "not-a-hash", [])
|
||||
|
||||
assert {:error, :wrongtype} = Store.hmget(key, ["field-a", "field-b"])
|
||||
end
|
||||
|
||||
test "HMGET keeps returning a bare list for hashes and missing keys" do
|
||||
key = "regr:hmget:hash"
|
||||
assert {:ok, 1} = Store.hset(key, [{"field-a", "value-a"}])
|
||||
|
||||
assert ["value-a", nil] = Store.hmget(key, ["field-a", "field-b"])
|
||||
assert [nil, nil] = Store.hmget("regr:hmget:missing", ["field-a", "field-b"])
|
||||
end
|
||||
|
||||
test "SMISMEMBER on a key of the wrong type reports the error instead of zeros" do
|
||||
key = "regr:smismember:wrongtype"
|
||||
assert :ok = Store.set(key, "not-a-set", [])
|
||||
|
||||
assert {:error, :wrongtype} = Store.smismember(key, ["alpha", "beta"])
|
||||
end
|
||||
|
||||
test "SMISMEMBER keeps returning a bare list for sets and missing keys" do
|
||||
key = "regr:smismember:set"
|
||||
assert {:ok, 1} = Store.sadd(key, ["alpha"])
|
||||
|
||||
assert [1, 0] = Store.smismember(key, ["alpha", "beta"])
|
||||
assert [0, 0] = Store.smismember("regr:smismember:missing", ["alpha", "beta"])
|
||||
end
|
||||
|
||||
test "HMGET and SMISMEMBER over RESP still report WRONGTYPE", %{conn: conn} do
|
||||
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "regr:mixed:string", "v"])
|
||||
|
||||
assert {{:error, "WRONGTYPE" <> _}, conn} =
|
||||
Client.command(conn, ["HMGET", "regr:mixed:string", "field-a"])
|
||||
|
||||
assert {{:error, "WRONGTYPE" <> _}, _conn} =
|
||||
Client.command(conn, ["SMISMEMBER", "regr:mixed:string", "alpha"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "SETRANGE with an empty value must not create a key" do
|
||||
test "an empty value on a missing key returns zero and creates nothing" do
|
||||
key = "regr:setrange:missing"
|
||||
|
||||
assert {:ok, 0} = Store.setrange(key, 5, "")
|
||||
assert 0 == Store.exists([key])
|
||||
assert "none" == Store.type(key)
|
||||
end
|
||||
|
||||
test "an empty value on an existing string returns its length and changes nothing" do
|
||||
key = "regr:setrange:existing"
|
||||
assert :ok = Store.set(key, "hello", [])
|
||||
|
||||
assert {:ok, 5} = Store.setrange(key, 100, "")
|
||||
assert {:ok, "hello"} = Store.get(key)
|
||||
assert 1 == Store.exists([key])
|
||||
end
|
||||
|
||||
test "an empty value on a key of the wrong type still reports WRONGTYPE" do
|
||||
key = "regr:setrange:wrongtype"
|
||||
assert {:ok, 1} = Store.rpush(key, ["element"])
|
||||
|
||||
assert {:error, :wrongtype} = Store.setrange(key, 0, "")
|
||||
assert {:ok, ["element"]} = Store.lrange(key, 0, -1)
|
||||
end
|
||||
|
||||
test "an empty value leaves an existing expiry in place" do
|
||||
key = "regr:setrange:ttl"
|
||||
assert :ok = Store.set(key, "hello", [])
|
||||
assert 1 == Store.expire(key, 60_000)
|
||||
|
||||
assert {:ok, 5} = Store.setrange(key, 0, "")
|
||||
assert Store.ttl(key, :seconds) > 0
|
||||
end
|
||||
|
||||
test "a non empty value still pads and overwrites as before" do
|
||||
key = "regr:setrange:write"
|
||||
|
||||
assert {:ok, 8} = Store.setrange(key, 5, "abc")
|
||||
assert {:ok, <<0, 0, 0, 0, 0, "abc">>} = Store.get(key)
|
||||
assert {:ok, 8} = Store.setrange(key, 0, "xy")
|
||||
assert {:ok, <<"xy", 0, 0, 0, "abc">>} = Store.get(key)
|
||||
end
|
||||
|
||||
test "SETRANGE with an empty value over RESP returns the length and creates nothing",
|
||||
%{conn: conn} do
|
||||
assert {0, conn} = Client.command(conn, ["SETRANGE", "regr:setrange:resp", "5", ""])
|
||||
assert {0, conn} = Client.command(conn, ["EXISTS", "regr:setrange:resp"])
|
||||
|
||||
assert {{:simple, "OK"}, conn} =
|
||||
Client.command(conn, ["SET", "regr:setrange:resp", "hello"])
|
||||
|
||||
assert {5, _conn} = Client.command(conn, ["SETRANGE", "regr:setrange:resp", "100", ""])
|
||||
end
|
||||
end
|
||||
|
||||
defp shardIndex(key), do: rem(:erlang.phash2(key), Store.shard_count())
|
||||
|
||||
defp shardPid(key), do: Process.whereis(Store.shard_name(shardIndex(key)))
|
||||
|
||||
defp keyInOtherShard(prefix, reference) do
|
||||
findKey(prefix, fn candidate -> shardIndex(candidate) != shardIndex(reference) end)
|
||||
end
|
||||
|
||||
defp keyInSameShard(prefix, reference) do
|
||||
findKey(prefix, fn candidate -> shardIndex(candidate) == shardIndex(reference) end)
|
||||
end
|
||||
|
||||
defp findKey(prefix, accept) do
|
||||
Enum.find_value(1..1024, fn suffix ->
|
||||
candidate = prefix <> Integer.to_string(suffix)
|
||||
if accept.(candidate), do: candidate
|
||||
end)
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user