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:
Graham McIntire 2026-05-09 09:05:16 -05:00
parent 001af22286
commit 1ddc817f50
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
4 changed files with 34 additions and 24 deletions

View file

@ -8,9 +8,16 @@ defmodule Aprsme.Cluster.LeaderElection do
require Logger
@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)
@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,
leader_node: nil,
@ -63,7 +70,7 @@ defmodule Aprsme.Cluster.LeaderElection do
schedule_initial_election(cluster_enabled)
# Schedule periodic checks
Process.send_after(self(), :check_leadership, @check_interval)
Process.send_after(self(), :check_leadership, check_interval())
# Initialize cached leadership state
: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.
defp schedule_initial_election(true) do
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(), :force_election_timeout, @max_cluster_wait)
Process.send_after(self(), :check_cluster_and_elect, cluster_check_interval())
Process.send_after(self(), :force_election_timeout, max_cluster_wait())
end
# Non-clustered mode: elect immediately.
@ -118,12 +125,12 @@ defmodule Aprsme.Cluster.LeaderElection do
if connected_nodes == [] do
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."
)
else
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
@ -165,7 +172,7 @@ defmodule Aprsme.Cluster.LeaderElection do
end
# Schedule next check
_ = Process.send_after(self(), :check_leadership, @check_interval)
_ = Process.send_after(self(), :check_leadership, check_interval())
{:noreply, state}
end

View file

@ -161,15 +161,17 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
assert :global.whereis_name(@election_key) == :undefined
end
@tag :slow
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([])
# Initial state - might not be leader yet
_initial_leader = LeaderElection.leader?()
# Wait for periodic check
Process.sleep(6000)
# Wait for periodic check (50ms interval, plus buffer)
Process.sleep(150)
# Should still be able to query leadership
current_leader = LeaderElection.leader?()
@ -208,12 +210,11 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
end
describe "stale registration cleanup" do
@tag :slow
test "cleans up registration when process dies" do
# Register a dead process
dead_pid = spawn(fn -> :ok end)
# Ensure it's dead
Process.sleep(50)
Process.sleep(10)
# Force register it globally (simulating stale registration)
:global.re_register_name(@election_key, dead_pid)
@ -222,8 +223,8 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
Application.put_env(:aprsme, :cluster_enabled, false)
{:ok, _pid} = LeaderElection.start_link([])
# Wait longer for cleanup and election to occur
Process.sleep(500)
# Wait for cleanup and election to occur
Process.sleep(150)
# Should have taken over leadership
assert LeaderElection.leader?() == true
@ -236,16 +237,20 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
:ok
end
@tag :slow
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([])
# Immediately after start, might not be leader yet
Process.sleep(100)
# In cluster mode with no other nodes, it will eventually become leader
# but might take longer than non-clustered mode
Process.sleep(3000)
# after the (now shortened) max_cluster_wait timeout fires
Process.sleep(250)
# Should eventually attempt election
assert is_boolean(LeaderElection.leader?())

View file

@ -22,7 +22,6 @@ defmodule Mix.Tasks.Compile.UnusedTest do
assert Unused.clean() == :ok
end
@tag :slow
test "runs against the project and returns either :ok or :error tuple" do
ExUnit.CaptureIO.capture_io(fn ->
result = Unused.run([])
@ -37,7 +36,6 @@ defmodule Mix.Tasks.Compile.UnusedTest do
end)
end
@tag :slow
test "accepts --severity warning, info, and unknown values" do
for severity <- ["warning", "info", "garbage"] do
ExUnit.CaptureIO.capture_io(fn ->

View file

@ -10,7 +10,7 @@ ExUnit.start(
max_cases: max_cases,
timeout: 30_000,
capture_log: true,
exclude: [:slow, :integration]
exclude: [:integration]
)
Ecto.Adapters.SQL.Sandbox.mode(Aprsme.Repo, :manual)