From 3a979c096ebbd7a0b314e3a57370fa6868a938d3 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 14 Sep 2026 03:07:39 +0200 Subject: [PATCH] test: cover commandState and fillOverLimit --- test/excache/write_path_test.exs | 106 +++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/test/excache/write_path_test.exs b/test/excache/write_path_test.exs index f55eab2..44e71f0 100644 --- a/test/excache/write_path_test.exs +++ b/test/excache/write_path_test.exs @@ -157,3 +157,109 @@ defmodule Excache.WritePathTest do assert {1, conn} = Client.command(conn, ["LPUSH", "order:queue", "payload"]) assert Task.await(waiter) == ["order:queue", "payload"] + log = flushedLog() + + assert offsetOf(log, "LPUSH") < offsetOf(log, "LPOP") + + conn = replayFromScratch(conn) + + assert {0, _conn} = Client.command(conn, ["EXISTS", "order:queue"]) + end + end + + describe "the wake signal belongs to the command layer" do + test "a push dispatched straight through the command router wakes a waiter", %{port: port} do + waiter = blockingTask(port, ["BLPOP", "hook:queue", "2"]) + awaitWaiters("hook:queue", 1) + + Command.execute(["LPUSH", "hook:queue", "direct"], commandState()) + + assert Task.await(waiter) == ["hook:queue", "direct"] + end + + test "a rename dispatched straight through the command router wakes a waiter", %{port: port} do + assert {:ok, 1} = Store.rpush("hook:source", ["carried"]) + + waiter = blockingTask(port, ["BLPOP", "hook:target", "2"]) + awaitWaiters("hook:target", 1) + + Command.execute(["RENAME", "hook:source", "hook:target"], commandState()) + + assert Task.await(waiter) == ["hook:target", "carried"] + end + end + + defp commandState do + Map.put(Excache.Command.Transaction.initial_state(), :subscriptions, MapSet.new()) + end + + defp fillOverLimit(conn, listKey) do + conn = + Enum.reduce(1..30, conn, fn index, acc -> + {{:simple, "OK"}, acc} = Client.command(acc, ["SET", "oom:key:#{index}", @payload]) + acc + end) + + {1, conn} = Client.command(conn, ["RPUSH", listKey, "value"]) + {flat, conn} = Client.command(conn, ["MEMORY", "STATS"]) + + allocated = + flat + |> Enum.chunk_every(2) + |> Map.new(fn [name, value] -> {name, value} end) + |> Map.fetch!("total.allocated") + + {{:simple, "OK"}, conn} = + Client.command(conn, ["CONFIG", "SET", "maxmemory", Integer.to_string(div(allocated, 2))]) + + conn + end + + defp subscribeTo(port, channel) do + subscriber = Client.connect(port) + {["subscribe", ^channel, 1], subscriber} = Client.command(subscriber, ["SUBSCRIBE", channel]) + subscriber + end + + defp blockingTask(port, args) do + Task.async(fn -> + client = Client.connect(port) + {reply, _client} = Client.command(client, args) + Client.close(client) + reply + end) + end + + defp awaitWaiters(key, count), do: awaitUntil(fn -> Blocking.waitingCount(key) == count end) + + defp awaitUntil(check, attempts \\ 400) do + cond do + check.() -> + :ok + + attempts == 0 -> + flunk("the expected registry state was never reached") + + true -> + Process.sleep(5) + awaitUntil(check, attempts - 1) + end + end + + defp offsetOf(log, needle) do + {offset, _length} = :binary.match(log, needle) + offset + end + + defp flushedLog do + Aof.flush() + {:ok, log} = File.read(Application.get_env(:excache, :aof_path)) + log + end + + defp replayFromScratch(conn) do + Store.flushdb() + Aof.replay(Application.get_env(:excache, :aof_path)) + conn + end +end