From 5de6afbb61aeea352c63b58256b3bf56be358d09 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 27 Aug 2026 22:40:00 +0200 Subject: [PATCH] test(support): add resp client and server test helpers --- test/support/resp_client.ex | 84 +++++++++++++++++++++++++++++++++++++ test/support/server_case.ex | 37 ++++++++++++++++ test/test_helper.exs | 1 + 3 files changed, 122 insertions(+) create mode 100644 test/support/resp_client.ex create mode 100644 test/support/server_case.ex create mode 100644 test/test_helper.exs diff --git a/test/support/resp_client.ex b/test/support/resp_client.ex new file mode 100644 index 0000000..e826f18 --- /dev/null +++ b/test/support/resp_client.ex @@ -0,0 +1,84 @@ +defmodule Excache.Test.RespClient do + @moduledoc false + + def connect(port) do + {:ok, socket} = + :gen_tcp.connect(~c"127.0.0.1", port, [:binary, active: false, packet: :raw]) + + %{socket: socket, buffer: ""} + end + + def close(%{socket: socket}), do: :gen_tcp.close(socket) + + def command(conn, args) do + :ok = :gen_tcp.send(conn.socket, encode_request(args)) + read(conn) + end + + def send_raw(conn, bytes) do + :ok = :gen_tcp.send(conn.socket, bytes) + conn + end + + def read(conn) do + {<>, conn} = read_line(conn) + + case type do + ?+ -> {{:simple, payload}, conn} + ?- -> {{:error, payload}, conn} + ?: -> {String.to_integer(payload), conn} + ?$ -> read_bulk(String.to_integer(payload), conn) + ?* -> read_array(String.to_integer(payload), conn) + end + end + + defp read_bulk(length, conn) when length < 0, do: {nil, conn} + + defp read_bulk(length, conn) do + conn = fill(conn, length + 2) + <> = conn.buffer + {data, %{conn | buffer: rest}} + end + + defp read_array(count, conn) when count < 0, do: {nil, conn} + + defp read_array(count, conn), do: read_elements(count, conn, []) + + defp read_elements(0, conn, acc), do: {Enum.reverse(acc), conn} + + defp read_elements(count, conn, acc) do + {value, conn} = read(conn) + read_elements(count - 1, conn, [value | acc]) + end + + defp read_line(conn) do + case :binary.split(conn.buffer, "\r\n") do + [line, rest] -> + {line, %{conn | buffer: rest}} + + [_only] -> + {:ok, more} = :gen_tcp.recv(conn.socket, 0, 2000) + read_line(%{conn | buffer: conn.buffer <> more}) + end + end + + defp fill(conn, needed) do + if byte_size(conn.buffer) >= needed do + conn + else + {:ok, more} = :gen_tcp.recv(conn.socket, 0, 2000) + fill(%{conn | buffer: conn.buffer <> more}, needed) + end + end + + defp encode_request(args) do + [ + "*", + Integer.to_string(length(args)), + "\r\n", + Enum.map(args, fn arg -> + ["$", Integer.to_string(byte_size(arg)), "\r\n", arg, "\r\n"] + end) + ] + end +end diff --git a/test/support/server_case.ex b/test/support/server_case.ex new file mode 100644 index 0000000..f5a93b9 --- /dev/null +++ b/test/support/server_case.ex @@ -0,0 +1,37 @@ +defmodule Excache.Test.ServerCase do + @moduledoc false + + use ExUnit.CaseTemplate + + using do + quote do + alias Excache.Test.RespClient, as: Client + end + end + + setup do + snapshot = Path.join(File.cwd!(), "tmp/snapshot_#{System.unique_integer([:positive])}.bin") + aof = Path.join(File.cwd!(), "tmp/aof_#{System.unique_integer([:positive])}.bin") + + Application.put_env(:excache, :snapshot_path, snapshot) + Application.put_env(:excache, :aof_path, aof) + Excache.Store.flushdb() + Excache.Aof.restart() + + {:ok, acceptor} = Excache.Server.Acceptor.start_link(port: 0) + port = Excache.Server.Acceptor.port(acceptor) + conn = Excache.Test.RespClient.connect(port) + + on_exit(fn -> + Excache.Test.RespClient.close(conn) + if Process.alive?(acceptor), do: GenServer.stop(acceptor) + File.rm(snapshot) + File.rm(aof) + Excache.Store.flushdb() + Application.delete_env(:excache, :snapshot_path) + Application.delete_env(:excache, :aof_path) + end) + + %{conn: conn, port: port} + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()