test: Aprsme.Is active-socket branches via TCP loopback, IsSupervisor smoke
This commit is contained in:
parent
2c4f876c00
commit
14c3790df0
2 changed files with 167 additions and 0 deletions
27
test/aprsme/is/is_supervisor_test.exs
Normal file
27
test/aprsme/is/is_supervisor_test.exs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
defmodule Aprsme.Is.IsSupervisorTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias Aprsme.Is.IsSupervisor
|
||||
|
||||
describe "init/1" do
|
||||
test "returns a supervisor spec with Aprsme.Is as a child" do
|
||||
# init/1 returns the {:ok, sup_flags, child_specs} tuple wrapped via
|
||||
# Supervisor.init/2 — the safest assertion is that the return type is
|
||||
# a valid supervisor init result.
|
||||
assert {:ok, {_sup_flags, child_specs}} = IsSupervisor.init(:ok)
|
||||
assert is_list(child_specs)
|
||||
assert Enum.any?(child_specs, fn spec -> spec.id == Aprsme.Is end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "start_link/1" do
|
||||
test "the supervisor module exists and is callable" do
|
||||
# We can't actually start the supervisor because Aprsme.Is.init/1 stops
|
||||
# itself in the test environment via :test_environment_disabled.
|
||||
# Verify the function exists and accepts the expected opts.
|
||||
Code.ensure_loaded(IsSupervisor)
|
||||
assert function_exported?(IsSupervisor, :start_link, 1)
|
||||
assert function_exported?(IsSupervisor, :init, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -498,6 +498,146 @@ defmodule Aprsme.IsTest do
|
|||
end
|
||||
end
|
||||
|
||||
# Local loopback TCP socket that exercises the active-socket branches.
|
||||
# The server side just accepts and discards; the client's send/setopts calls
|
||||
# still go through the real kernel. Returns {listen_sock, client_sock}.
|
||||
defp loopback_socket do
|
||||
{:ok, listen} = :gen_tcp.listen(0, [:binary, active: false, reuseaddr: true])
|
||||
{:ok, port} = :inet.port(listen)
|
||||
|
||||
# Accept on a spawned process so connect doesn't block.
|
||||
test_pid = self()
|
||||
|
||||
spawn_link(fn ->
|
||||
case :gen_tcp.accept(listen, 5_000) do
|
||||
{:ok, server_sock} -> send(test_pid, {:server_sock, server_sock})
|
||||
_ -> :ok
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, client} = :gen_tcp.connect(~c"127.0.0.1", port, [:binary, active: false])
|
||||
|
||||
receive do
|
||||
{:server_sock, _s} -> :ok
|
||||
after
|
||||
1_000 -> :ok
|
||||
end
|
||||
|
||||
on_exit = fn ->
|
||||
_ = :gen_tcp.close(client)
|
||||
_ = :gen_tcp.close(listen)
|
||||
end
|
||||
|
||||
{client, listen, on_exit}
|
||||
end
|
||||
|
||||
describe "handle_call({:send_message, _}, _, state) with active socket" do
|
||||
setup do
|
||||
ensure_ets_table()
|
||||
{client, _listen, cleanup} = loopback_socket()
|
||||
on_exit(cleanup)
|
||||
{:ok, socket: client}
|
||||
end
|
||||
|
||||
test "sends a message and returns :ok", %{socket: socket} do
|
||||
state = build_state(%{socket: socket})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:reply, :ok, ^state} =
|
||||
Aprsme.Is.handle_call({:send_message, "hello"}, self(), state)
|
||||
end)
|
||||
end
|
||||
|
||||
test "handles send error by closing socket and scheduling reconnect", %{socket: socket} do
|
||||
# Close the socket before send; the send call will return an error.
|
||||
:gen_tcp.close(socket)
|
||||
state = build_state(%{socket: socket})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:reply, {:error, _reason}, new_state} =
|
||||
Aprsme.Is.handle_call({:send_message, "fail"}, self(), state)
|
||||
|
||||
assert is_nil(new_state.socket)
|
||||
assert is_nil(new_state.timer)
|
||||
assert is_nil(new_state.keepalive_timer)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_info(:send_keepalive) with active socket" do
|
||||
setup do
|
||||
{client, _listen, cleanup} = loopback_socket()
|
||||
on_exit(cleanup)
|
||||
{:ok, socket: client}
|
||||
end
|
||||
|
||||
test "sends keepalive and schedules next", %{socket: socket} do
|
||||
state = build_state(%{socket: socket, keepalive_timer: nil})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:noreply, new_state} = Aprsme.Is.handle_info(:send_keepalive, state)
|
||||
assert is_reference(new_state.keepalive_timer)
|
||||
Process.cancel_timer(new_state.keepalive_timer)
|
||||
end)
|
||||
end
|
||||
|
||||
test "stops on send error", %{socket: socket} do
|
||||
:gen_tcp.close(socket)
|
||||
state = build_state(%{socket: socket})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:stop, :normal, _state} = Aprsme.Is.handle_info(:send_keepalive, state)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_info({:backpressure, _}) with active socket" do
|
||||
setup do
|
||||
{client, _listen, cleanup} = loopback_socket()
|
||||
on_exit(cleanup)
|
||||
{:ok, socket: client}
|
||||
end
|
||||
|
||||
test "activate sets backpressure and installs safety valve", %{socket: socket} do
|
||||
state = build_state(%{socket: socket, backpressure_active: false})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:noreply, new_state} =
|
||||
Aprsme.Is.handle_info({:backpressure, :activate}, state)
|
||||
|
||||
assert new_state.backpressure_active
|
||||
assert is_reference(new_state.safety_valve_timer)
|
||||
Process.cancel_timer(new_state.safety_valve_timer)
|
||||
end)
|
||||
end
|
||||
|
||||
test "deactivate clears backpressure and restores timer", %{socket: socket} do
|
||||
state = build_state(%{socket: socket, backpressure_active: true, safety_valve_timer: nil})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:noreply, new_state} =
|
||||
Aprsme.Is.handle_info({:backpressure, :deactivate}, state)
|
||||
|
||||
refute new_state.backpressure_active
|
||||
assert is_reference(new_state.timer)
|
||||
Process.cancel_timer(new_state.timer)
|
||||
end)
|
||||
end
|
||||
|
||||
test "safety_valve forces resume when socket is active", %{socket: socket} do
|
||||
state = build_state(%{socket: socket, backpressure_active: true, safety_valve_timer: nil})
|
||||
|
||||
capture_log(fn ->
|
||||
assert {:noreply, new_state} =
|
||||
Aprsme.Is.handle_info(:backpressure_safety_valve, state)
|
||||
|
||||
refute new_state.backpressure_active
|
||||
assert is_reference(new_state.timer)
|
||||
Process.cancel_timer(new_state.timer)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "send_message/3 and /1 helpers" do
|
||||
test "three-arg send_message delegates to send_message/1 without server running" do
|
||||
# GenServer not running → send_message/1 returns {:error, :not_connected}.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue