speed up slow tests: remove Process.sleep, add batch fixture helper

- Replace Process.sleep(N) with explicit synchronization:
  render(view) to force LV mailbox drain, PubSub assert_receive for
  leader election, and render_hook sync guarantees
- Add batch_packet_fixtures/2 using Repo.insert_all for bulk test
  fixtures (200 inserts -> 1 round trip)
- Remove sleeps from historical_loading_test — tests query DB
  directly, don't need LV async processing
This commit is contained in:
Graham McIntire 2026-06-02 15:02:07 -05:00
parent 73c6703102
commit e8d48f8058
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
7 changed files with 83 additions and 91 deletions

View file

@ -246,14 +246,15 @@ defmodule Aprsme.Cluster.LeaderElectionTest do
Application.delete_env(:aprsme, :election_cluster_check_interval_ms) Application.delete_env(:aprsme, :election_cluster_check_interval_ms)
end) end)
Phoenix.PubSub.subscribe(Aprsme.PubSub, "cluster:leadership")
{:ok, _pid} = LeaderElection.start_link([]) {:ok, _pid} = LeaderElection.start_link([])
# In cluster mode with no other nodes, it will eventually become leader # Wait for force_election_timeout (max_cluster_wait_ms: 100) to fire
# after the (now shortened) max_cluster_wait timeout fires # and the process to become leader. The :force_election_timeout message
Process.sleep(250) # triggers election and publishes {:leadership_change, _, true}.
assert_receive {:leadership_change, _, true}, 150
# Should eventually attempt election assert LeaderElection.leader?() == true
assert is_boolean(LeaderElection.leader?())
end end
end end

View file

@ -117,8 +117,7 @@ defmodule AprsmeWeb.Integration.AprsStatusTest do
# Should not crash when handling events without APRS connection # Should not crash when handling events without APRS connection
assert render_hook(view, "bounds_changed", bounds_params) assert render_hook(view, "bounds_changed", bounds_params)
# Wait a bit for any async processes render(view)
Process.sleep(50)
# Test map ready event # Test map ready event
assert render_hook(view, "map_ready", %{}) assert render_hook(view, "map_ready", %{})
@ -143,9 +142,6 @@ defmodule AprsmeWeb.Integration.AprsStatusTest do
# This should not cause any updates since APRS.Is is not running # This should not cause any updates since APRS.Is is not running
Endpoint.broadcast("aprs_messages", "packet", test_packet) Endpoint.broadcast("aprs_messages", "packet", test_packet)
# Give a moment for any potential updates
Process.sleep(5)
# View should remain stable (no crashes from missing APRS connection) # View should remain stable (no crashes from missing APRS connection)
assert has_element?(view, "#aprs-map") assert has_element?(view, "#aprs-map")
end end

View file

@ -65,11 +65,7 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
assert render_hook(view, "bounds_changed", %{"bounds" => bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => bounds})
# Wait for historical loading to process # Verify the packets are in the database (queried directly, not through LV)
Process.sleep(30)
# The view should have pushed events to add the historical packets
# We can verify by checking that the packets were queried from the database
recent_packets = recent_packets =
Packets.get_recent_packets(%{ Packets.get_recent_packets(%{
bounds: [bounds["west"], bounds["south"], bounds["east"], bounds["north"]], bounds: [bounds["west"], bounds["south"], bounds["east"], bounds["north"]],
@ -115,9 +111,6 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
assert render_hook(view, "bounds_changed", %{"bounds" => bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => bounds})
# Wait for historical loading
Process.sleep(30)
# Verify the old packet would be included with 6-hour window # Verify the old packet would be included with 6-hour window
recent_packets = recent_packets =
Packets.get_recent_packets(%{ Packets.get_recent_packets(%{
@ -173,9 +166,6 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
assert render_hook(view, "bounds_changed", %{"bounds" => bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => bounds})
# Wait for historical loading
Process.sleep(30)
# Both packets should be loaded since they're in the bounds # Both packets should be loaded since they're in the bounds
recent_packets = recent_packets =
Packets.get_recent_packets(%{ Packets.get_recent_packets(%{
@ -193,18 +183,12 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
now = DateTime.utc_now() now = DateTime.utc_now()
# Create many packets to test limit enforcement # Create many packets to test limit enforcement
packets = created_count =
for i <- 1..200 do batch_packet_fixtures(200,
packet_fixture(%{ lat: 40.7,
sender: "TEST#{i}", lon: -74.0,
base_callsign: "TEST#{i}", received_at: now
ssid: "0", )
lat: 40.7 + i * 0.0001,
lon: -74.0 + i * 0.0001,
received_at: DateTime.add(now, -rem(i, 60) * 60, :second),
comment: "Test station #{i}"
})
end
# Connect with low zoom level (zoom=5) # Connect with low zoom level (zoom=5)
{:ok, view, _html} = live(conn, "/?z=5", on_error: :warn) {:ok, view, _html} = live(conn, "/?z=5", on_error: :warn)
@ -221,9 +205,6 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
assert render_hook(view, "bounds_changed", %{"bounds" => bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => bounds})
# Wait for historical loading
Process.sleep(50)
# At zoom level 5, the limit should be 100 packets # At zoom level 5, the limit should be 100 packets
# This is defined in HistoricalLoader's @zoom_packet_limits # This is defined in HistoricalLoader's @zoom_packet_limits
# The actual loading would have been limited by the zoom level # The actual loading would have been limited by the zoom level
@ -231,7 +212,7 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
# should respect the zoom-based limits # should respect the zoom-based limits
# Confirm we created 200 packets # Confirm we created 200 packets
assert length(packets) == 200 assert created_count == 200
# The view would have loaded only up to 100 based on zoom limits # The view would have loaded only up to 100 based on zoom limits
end end
end end
@ -271,9 +252,6 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
# Send bounds_changed event # Send bounds_changed event
assert render_hook(view, "bounds_changed", %{"bounds" => bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => bounds})
# Wait for historical loading
Process.sleep(50)
# Verify all packets would be included in the query # Verify all packets would be included in the query
recent_packets = recent_packets =
Packets.get_recent_packets(%{ Packets.get_recent_packets(%{
@ -320,9 +298,6 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
# This should not cause an error, but bounds won't be processed # This should not cause an error, but bounds won't be processed
assert render_hook(view, "bounds_changed", %{"bounds" => invalid_bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => invalid_bounds})
# Wait briefly
Process.sleep(30)
# Since we can't check internal state, we verify the view is still functional # Since we can't check internal state, we verify the view is still functional
# by sending valid bounds and checking it still works # by sending valid bounds and checking it still works
valid_bounds = %{ valid_bounds = %{
@ -341,17 +316,11 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
now = DateTime.utc_now() now = DateTime.utc_now()
# Create packets for testing progressive loading # Create packets for testing progressive loading
for i <- 1..50 do batch_packet_fixtures(50,
packet_fixture(%{ lat: 40.7,
sender: "BATCH#{i}", lon: -74.0,
base_callsign: "BATCH#{i}", received_at: now
ssid: "0", )
lat: 40.7 + i * 0.001,
lon: -74.0 + i * 0.001,
received_at: DateTime.add(now, -(i * 30), :second),
comment: "Batch test station #{i}"
})
end
# Connect with medium zoom level # Connect with medium zoom level
{:ok, view, _html} = live(conn, "/?z=8", on_error: :warn) {:ok, view, _html} = live(conn, "/?z=8", on_error: :warn)
@ -368,8 +337,7 @@ defmodule AprsmeWeb.MapLive.HistoricalLoadingTest do
assert render_hook(view, "bounds_changed", %{"bounds" => bounds}) assert render_hook(view, "bounds_changed", %{"bounds" => bounds})
# Allow time for progressive loading batches render(view)
Process.sleep(30)
# The loading should have completed in multiple batches # The loading should have completed in multiple batches
# This tests that the progressive loading mechanism works # This tests that the progressive loading mechanism works

View file

@ -755,7 +755,6 @@ defmodule AprsmeWeb.MapLive.IntegrationTest do
end) end)
assert render_hook(view, "clear_and_reload_markers", %{}) assert render_hook(view, "clear_and_reload_markers", %{})
Process.sleep(50)
# Also at low zoom — exercises the heat map branch. # Also at low zoom — exercises the heat map branch.
:sys.replace_state(view.pid, fn channel_state -> :sys.replace_state(view.pid, fn channel_state ->

View file

@ -36,8 +36,6 @@ defmodule AprsmeWeb.MapLive.MovementTest do
} }
assert render_hook(view, "bounds_changed", bounds_params) assert render_hook(view, "bounds_changed", bounds_params)
# Reduced sleep from 100ms to 10ms
Process.sleep(10)
# Simulate initial packet # Simulate initial packet
initial_packet = %{ initial_packet = %{
@ -52,9 +50,6 @@ defmodule AprsmeWeb.MapLive.MovementTest do
send(view.pid, {:postgres_packet, initial_packet}) send(view.pid, {:postgres_packet, initial_packet})
# Reduced sleep from 100ms to 10ms
Process.sleep(10)
# Simulate GPS drift (5 meters movement) # Simulate GPS drift (5 meters movement)
drift_packet = %{ drift_packet = %{
id: "TEST-1", id: "TEST-1",
@ -70,6 +65,9 @@ defmodule AprsmeWeb.MapLive.MovementTest do
# Send the drift packet # Send the drift packet
send(view.pid, {:postgres_packet, drift_packet}) send(view.pid, {:postgres_packet, drift_packet})
# Force LV to process pending postgres_packet messages
render(view)
# The view should not push a new_packet event for GPS drift # The view should not push a new_packet event for GPS drift
refute_push_event(view, "new_packet", %{id: "TEST-1"}, 50) refute_push_event(view, "new_packet", %{id: "TEST-1"}, 50)
end end
@ -99,10 +97,7 @@ defmodule AprsmeWeb.MapLive.MovementTest do
assert render_hook(view, "bounds_changed", bounds_params) assert render_hook(view, "bounds_changed", bounds_params)
# Wait for initial load to complete render(view)
Process.sleep(20)
# Clear any events from initial load
flush_push_events(view) flush_push_events(view)
# Simulate initial packet with atom keys # Simulate initial packet with atom keys
@ -118,24 +113,10 @@ defmodule AprsmeWeb.MapLive.MovementTest do
send(view.pid, {:postgres_packet, initial_packet}) send(view.pid, {:postgres_packet, initial_packet})
# Wait for the initial packet to be processed # Force LV to process pending messages, then flush initial push events
Process.sleep(20) render(view)
# Should receive new_packet for the initial packet
# First flush any other events
flush_push_events(view) flush_push_events(view)
# Try receiving with shorter timeout
receive do
{ref, {:push_event, "new_packet", _}} when ref == view.ref ->
:ok
after
30 ->
# If no new_packet event, the test should pass anyway since the main
# goal is to test marker updates, not event timing
:ok
end
# Simulate significant movement (20+ meters) # Simulate significant movement (20+ meters)
moved_packet = %{ moved_packet = %{
id: "TEST-2", id: "TEST-2",
@ -151,17 +132,14 @@ defmodule AprsmeWeb.MapLive.MovementTest do
# Send the moved packet # Send the moved packet
send(view.pid, {:postgres_packet, moved_packet}) send(view.pid, {:postgres_packet, moved_packet})
# Wait a bit for processing # Force LV to process the moved packet, then check for push event
Process.sleep(20) render(view)
# The view should push a new_packet event for significant movement
# Using receive directly since push events seem unreliable in test env
receive do receive do
{ref, {:push_event, "new_packet", _}} when ref == view.ref -> {ref, {:push_event, "new_packet", _}} when ref == view.ref ->
:ok :ok
after after
100 -> 100 ->
# If the event isn't received, it's okay - the main test is about movement filtering
:ok :ok
end end
end end

View file

@ -68,9 +68,6 @@ defmodule AprsmeWeb.MapLive.PerformanceTest do
|> element("#aprs-map") |> element("#aprs-map")
|> render_hook("map_ready", %{}) |> render_hook("map_ready", %{})
# Wait for historical packets to load
:timer.sleep(200)
queries_executed = Process.get(:query_count) || 0 queries_executed = Process.get(:query_count) || 0
# Should execute only a few queries: # Should execute only a few queries:

View file

@ -64,4 +64,57 @@ defmodule Aprsme.PacketsFixtures do
packet packet
end end
@doc """
Batch-insert multiple packet fixtures in a single Repo.insert_all call.
Useful for tests that need many packets without the overhead of individual
changeset + insert round trips.
"""
def batch_packet_fixtures(count, base_attrs \\ %{}) do
base_attrs = Map.new(base_attrs)
now = DateTime.truncate(DateTime.utc_now(), :second)
lat_num = base_attrs[:lat] || 40.7
lon_num = base_attrs[:lon] || -74.0
lat_str = "#{lat_num}"
lon_str = "#{lon_num}"
location = %Geo.Point{
coordinates: {lon_num, lat_num},
srid: 4326
}
attrs_list =
for i <- 1..count do
sender = Map.get(base_attrs, :sender, "TEST#{i}")
{base, ssid} = Callsign.extract_parts(sender)
received_at =
DateTime.truncate(Map.get(base_attrs, :received_at) || DateTime.add(now, -rem(i, 60) * 60, :second), :second)
%{
sender: sender,
base_callsign: base,
ssid: ssid,
destination: Map.get(base_attrs, :destination, "APRS"),
received_at: received_at,
lat: Decimal.new(lat_str),
lon: Decimal.new(lon_str),
location: location,
has_position: true,
raw_packet: Map.get(base_attrs, :raw_packet, "#{sender}>APRS:=4042.77N/07400.36W>Test packet"),
data_type: Map.get(base_attrs, :data_type, "position"),
path: Map.get(base_attrs, :path, "APRS"),
comment: Map.get(base_attrs, :comment, "Test station #{i}"),
symbol_code: ">",
symbol_table_id: "/",
inserted_at: now,
updated_at: now
}
end
Repo.insert_all(Packet, attrs_list)
count
end
end end