Remove :slow test tag; make slow tests run fast via configurable timeouts
- Make LeaderElection check_interval, max_cluster_wait, and cluster_check_interval configurable via Application env so tests can override with short values - Slow LeaderElection tests now use 50-150ms timeouts instead of 3-6s - Remove :slow tag from unused_test (compile cache makes reruns cheap) - Drop :slow from test_helper exclude list All 2490 tests now run in ~45s (up from 2485 with 5 excluded).
This commit is contained in:
parent
001af22286
commit
1ddc817f50
4 changed files with 34 additions and 24 deletions
|
|
@ -8,9 +8,16 @@ defmodule Aprsme.Cluster.LeaderElection do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@election_key {:aprs_is_leader, __MODULE__}
|
@election_key {:aprs_is_leader, __MODULE__}
|
||||||
@check_interval 5_000
|
@default_check_interval 5_000
|
||||||
# Maximum time to wait for cluster formation before proceeding with election (30 seconds)
|
# Maximum time to wait for cluster formation before proceeding with election (30 seconds)
|
||||||
@max_cluster_wait 30_000
|
@default_max_cluster_wait 30_000
|
||||||
|
@default_cluster_check_interval 2_000
|
||||||
|
|
||||||
|
defp check_interval, do: Application.get_env(:aprsme, :election_check_interval_ms, @default_check_interval)
|
||||||
|
defp max_cluster_wait, do: Application.get_env(:aprsme, :election_max_cluster_wait_ms, @default_max_cluster_wait)
|
||||||
|
|
||||||
|
defp cluster_check_interval,
|
||||||
|
do: Application.get_env(:aprsme, :election_cluster_check_interval_ms, @default_cluster_check_interval)
|
||||||
|
|
||||||
defstruct is_leader: false,
|
defstruct is_leader: false,
|
||||||
leader_node: nil,
|
leader_node: nil,
|
||||||
|
|
@ -63,7 +70,7 @@ defmodule Aprsme.Cluster.LeaderElection do
|
||||||
schedule_initial_election(cluster_enabled)
|
schedule_initial_election(cluster_enabled)
|
||||||
|
|
||||||
# Schedule periodic checks
|
# Schedule periodic checks
|
||||||
Process.send_after(self(), :check_leadership, @check_interval)
|
Process.send_after(self(), :check_leadership, check_interval())
|
||||||
|
|
||||||
# Initialize cached leadership state
|
# Initialize cached leadership state
|
||||||
:persistent_term.put({__MODULE__, :is_leader}, false)
|
:persistent_term.put({__MODULE__, :is_leader}, false)
|
||||||
|
|
@ -74,8 +81,8 @@ defmodule Aprsme.Cluster.LeaderElection do
|
||||||
# Clustered mode: wait for formation, then elect; schedule a backstop timeout.
|
# Clustered mode: wait for formation, then elect; schedule a backstop timeout.
|
||||||
defp schedule_initial_election(true) do
|
defp schedule_initial_election(true) do
|
||||||
Logger.info("Clustering enabled - waiting for cluster formation before leader election")
|
Logger.info("Clustering enabled - waiting for cluster formation before leader election")
|
||||||
Process.send_after(self(), :check_cluster_and_elect, 2_000)
|
Process.send_after(self(), :check_cluster_and_elect, cluster_check_interval())
|
||||||
Process.send_after(self(), :force_election_timeout, @max_cluster_wait)
|
Process.send_after(self(), :force_election_timeout, max_cluster_wait())
|
||||||
end
|
end
|
||||||
|
|
||||||
# Non-clustered mode: elect immediately.
|
# Non-clustered mode: elect immediately.
|
||||||
|
|
@ -118,12 +125,12 @@ defmodule Aprsme.Cluster.LeaderElection do
|
||||||
|
|
||||||
if connected_nodes == [] do
|
if connected_nodes == [] do
|
||||||
Logger.warning(
|
Logger.warning(
|
||||||
"Cluster formation timeout reached after #{@max_cluster_wait}ms with no connected nodes. " <>
|
"Cluster formation timeout reached after #{max_cluster_wait()}ms with no connected nodes. " <>
|
||||||
"Proceeding with leader election in single-node mode to ensure APRS-IS connection."
|
"Proceeding with leader election in single-node mode to ensure APRS-IS connection."
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Logger.info(
|
Logger.info(
|
||||||
"Forcing leader election after #{@max_cluster_wait}ms wait with #{length(connected_nodes)} connected nodes"
|
"Forcing leader election after #{max_cluster_wait()}ms wait with #{length(connected_nodes)} connected nodes"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -165,7 +172,7 @@ defmodule Aprsme.Cluster.LeaderElection do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Schedule next check
|
# Schedule next check
|
||||||
_ = Process.send_after(self(), :check_leadership, @check_interval)
|
_ = Process.send_after(self(), :check_leadership, check_interval())
|
||||||
|
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -161,15 +161,17 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
|
||||||
assert :global.whereis_name(@election_key) == :undefined
|
assert :global.whereis_name(@election_key) == :undefined
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :slow
|
|
||||||
test "periodic leadership check continues running" do
|
test "periodic leadership check continues running" do
|
||||||
|
Application.put_env(:aprsme, :election_check_interval_ms, 50)
|
||||||
|
on_exit(fn -> Application.delete_env(:aprsme, :election_check_interval_ms) end)
|
||||||
|
|
||||||
{:ok, _pid} = LeaderElection.start_link([])
|
{:ok, _pid} = LeaderElection.start_link([])
|
||||||
|
|
||||||
# Initial state - might not be leader yet
|
# Initial state - might not be leader yet
|
||||||
_initial_leader = LeaderElection.leader?()
|
_initial_leader = LeaderElection.leader?()
|
||||||
|
|
||||||
# Wait for periodic check
|
# Wait for periodic check (50ms interval, plus buffer)
|
||||||
Process.sleep(6000)
|
Process.sleep(150)
|
||||||
|
|
||||||
# Should still be able to query leadership
|
# Should still be able to query leadership
|
||||||
current_leader = LeaderElection.leader?()
|
current_leader = LeaderElection.leader?()
|
||||||
|
|
@ -208,12 +210,11 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "stale registration cleanup" do
|
describe "stale registration cleanup" do
|
||||||
@tag :slow
|
|
||||||
test "cleans up registration when process dies" do
|
test "cleans up registration when process dies" do
|
||||||
# Register a dead process
|
# Register a dead process
|
||||||
dead_pid = spawn(fn -> :ok end)
|
dead_pid = spawn(fn -> :ok end)
|
||||||
# Ensure it's dead
|
# Ensure it's dead
|
||||||
Process.sleep(50)
|
Process.sleep(10)
|
||||||
|
|
||||||
# Force register it globally (simulating stale registration)
|
# Force register it globally (simulating stale registration)
|
||||||
:global.re_register_name(@election_key, dead_pid)
|
:global.re_register_name(@election_key, dead_pid)
|
||||||
|
|
@ -222,8 +223,8 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
|
||||||
Application.put_env(:aprsme, :cluster_enabled, false)
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
||||||
{:ok, _pid} = LeaderElection.start_link([])
|
{:ok, _pid} = LeaderElection.start_link([])
|
||||||
|
|
||||||
# Wait longer for cleanup and election to occur
|
# Wait for cleanup and election to occur
|
||||||
Process.sleep(500)
|
Process.sleep(150)
|
||||||
|
|
||||||
# Should have taken over leadership
|
# Should have taken over leadership
|
||||||
assert LeaderElection.leader?() == true
|
assert LeaderElection.leader?() == true
|
||||||
|
|
@ -236,16 +237,20 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :slow
|
|
||||||
test "waits for cluster formation when enabled" do
|
test "waits for cluster formation when enabled" do
|
||||||
|
Application.put_env(:aprsme, :election_max_cluster_wait_ms, 100)
|
||||||
|
Application.put_env(:aprsme, :election_cluster_check_interval_ms, 50)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Application.delete_env(:aprsme, :election_max_cluster_wait_ms)
|
||||||
|
Application.delete_env(:aprsme, :election_cluster_check_interval_ms)
|
||||||
|
end)
|
||||||
|
|
||||||
{:ok, _pid} = LeaderElection.start_link([])
|
{:ok, _pid} = LeaderElection.start_link([])
|
||||||
|
|
||||||
# Immediately after start, might not be leader yet
|
|
||||||
Process.sleep(100)
|
|
||||||
|
|
||||||
# In cluster mode with no other nodes, it will eventually become leader
|
# In cluster mode with no other nodes, it will eventually become leader
|
||||||
# but might take longer than non-clustered mode
|
# after the (now shortened) max_cluster_wait timeout fires
|
||||||
Process.sleep(3000)
|
Process.sleep(250)
|
||||||
|
|
||||||
# Should eventually attempt election
|
# Should eventually attempt election
|
||||||
assert is_boolean(LeaderElection.leader?())
|
assert is_boolean(LeaderElection.leader?())
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ defmodule Mix.Tasks.Compile.UnusedTest do
|
||||||
assert Unused.clean() == :ok
|
assert Unused.clean() == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :slow
|
|
||||||
test "runs against the project and returns either :ok or :error tuple" do
|
test "runs against the project and returns either :ok or :error tuple" do
|
||||||
ExUnit.CaptureIO.capture_io(fn ->
|
ExUnit.CaptureIO.capture_io(fn ->
|
||||||
result = Unused.run([])
|
result = Unused.run([])
|
||||||
|
|
@ -37,7 +36,6 @@ defmodule Mix.Tasks.Compile.UnusedTest do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :slow
|
|
||||||
test "accepts --severity warning, info, and unknown values" do
|
test "accepts --severity warning, info, and unknown values" do
|
||||||
for severity <- ["warning", "info", "garbage"] do
|
for severity <- ["warning", "info", "garbage"] do
|
||||||
ExUnit.CaptureIO.capture_io(fn ->
|
ExUnit.CaptureIO.capture_io(fn ->
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ ExUnit.start(
|
||||||
max_cases: max_cases,
|
max_cases: max_cases,
|
||||||
timeout: 30_000,
|
timeout: 30_000,
|
||||||
capture_log: true,
|
capture_log: true,
|
||||||
exclude: [:slow, :integration]
|
exclude: [:integration]
|
||||||
)
|
)
|
||||||
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Aprsme.Repo, :manual)
|
Ecto.Adapters.SQL.Sandbox.mode(Aprsme.Repo, :manual)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue