diff --git a/server/test/burble/chat/message_store_test.exs b/server/test/burble/chat/message_store_test.exs index 93e3a831..f7029f07 100644 --- a/server/test/burble/chat/message_store_test.exs +++ b/server/test/burble/chat/message_store_test.exs @@ -14,15 +14,10 @@ defmodule Burble.Chat.MessageStoreTest do alias Burble.Chat.MessageStore - # Start a fresh MessageStore for each test so ETS state doesn't bleed. + # Shared-app strategy (burble#62): the application owns MessageStore. + # Every test uses a unique random room id (room_id/0) so ETS state + # cannot bleed between tests without restarting the process. setup do - # Stop any existing instance (e.g. from Application startup in test env). - case Process.whereis(MessageStore) do - nil -> :ok - pid -> GenServer.stop(pid) - end - - {:ok, _pid} = start_supervised!(MessageStore) :ok end diff --git a/server/test/burble/concurrency/session_concurrency_test.exs b/server/test/burble/concurrency/session_concurrency_test.exs index 11a1ab7b..ba1c0f52 100644 --- a/server/test/burble/concurrency/session_concurrency_test.exs +++ b/server/test/burble/concurrency/session_concurrency_test.exs @@ -35,9 +35,9 @@ defmodule Burble.Concurrency.SessionConcurrencyTest do # Use start_supervised! so ExUnit owns the lifecycle and restarts these # processes between tests, giving each test a clean slate. - start_supervised!({Phoenix.PubSub, name: Burble.PubSub}) - start_supervised!({Registry, keys: :unique, name: Burble.RoomRegistry}) - start_supervised!({DynamicSupervisor, name: Burble.RoomSupervisor, strategy: :one_for_one}) + ensure_started({Phoenix.PubSub, name: Burble.PubSub}) + ensure_started({Registry, keys: :unique, name: Burble.RoomRegistry}) + ensure_started({DynamicSupervisor, name: Burble.RoomSupervisor, strategy: :one_for_one}) :ok end diff --git a/server/test/burble/e2e/signaling_test.exs b/server/test/burble/e2e/signaling_test.exs index e922b9f3..445738be 100644 --- a/server/test/burble/e2e/signaling_test.exs +++ b/server/test/burble/e2e/signaling_test.exs @@ -45,15 +45,15 @@ defmodule Burble.E2E.SignalingTest do Application.ensure_all_started(:phoenix_pubsub) # start_supervised! ensures ExUnit owns the lifecycle and tears down between tests. - start_supervised!({Phoenix.PubSub, name: Burble.PubSub}) - start_supervised!({Registry, keys: :unique, name: Burble.RoomRegistry}) - start_supervised!({DynamicSupervisor, name: Burble.RoomSupervisor, strategy: :one_for_one}) + ensure_started({Phoenix.PubSub, name: Burble.PubSub}) + ensure_started({Registry, keys: :unique, name: Burble.RoomRegistry}) + ensure_started({DynamicSupervisor, name: Burble.RoomSupervisor, strategy: :one_for_one}) # Presence tracker — required by RoomChannel.handle_info(:after_join). - start_supervised!(Burble.Presence) + ensure_started(Burble.Presence) # Media.Engine — required by RoomChannel.join/3 (add_peer call). - start_supervised!(Burble.Media.Engine) + ensure_started(Burble.Media.Engine) # Start Endpoint last — depends on PubSub. # BurbleWeb.Endpoint is configured with server: false in test config so diff --git a/server/test/burble/text/nntps_backend_test.exs b/server/test/burble/text/nntps_backend_test.exs index 4ddc0027..4bd854e3 100644 --- a/server/test/burble/text/nntps_backend_test.exs +++ b/server/test/burble/text/nntps_backend_test.exs @@ -37,11 +37,11 @@ defmodule Burble.Text.NNTPSBackendTest do setup do # PubSub must be running because post_message broadcasts to "text:". Application.ensure_all_started(:phoenix_pubsub) - start_supervised!({Phoenix.PubSub, name: Burble.PubSub}) + ensure_started({Phoenix.PubSub, name: Burble.PubSub}) # Start a fresh NNTPSBackend instance. start_supervised! gives ExUnit # ownership so it is torn down (and the name released) after each test. - start_supervised!(NNTPSBackend) + ensure_started(NNTPSBackend) :ok end diff --git a/server/test/burble/timing/alignment_test.exs b/server/test/burble/timing/alignment_test.exs index c5ac1d2c..5950e7eb 100644 --- a/server/test/burble/timing/alignment_test.exs +++ b/server/test/burble/timing/alignment_test.exs @@ -20,8 +20,10 @@ defmodule Burble.Timing.AlignmentTest do opts |> Keyword.put_new(:local_node, :"local@testhost") |> Keyword.put_new(:window_ms, 30_000) + # start_link/1 forces name: __MODULE__ when :name is absent, colliding + # with the application-owned Alignment. Unique-name per test (#62). + |> Keyword.put(:name, :"alignment_test_#{System.unique_integer([:positive])}") - # Do NOT pass :name so tests get an anonymous pid and don't conflict. start_supervised!({Alignment, opts}) end diff --git a/server/test/burble_web/channels/room_channel_text_test.exs b/server/test/burble_web/channels/room_channel_text_test.exs index 0f2f0863..ef65ab05 100644 --- a/server/test/burble_web/channels/room_channel_text_test.exs +++ b/server/test/burble_web/channels/room_channel_text_test.exs @@ -29,20 +29,15 @@ defmodule BurbleWeb.Channels.RoomChannelTextTest do setup do Application.ensure_all_started(:phoenix_pubsub) - start_supervised!({Phoenix.PubSub, name: Burble.PubSub}) - start_supervised!({Registry, keys: :unique, name: Burble.RoomRegistry}) - start_supervised!({DynamicSupervisor, name: Burble.RoomSupervisor, strategy: :one_for_one}) - start_supervised!(Burble.Presence) - start_supervised!(Burble.Media.Engine) - start_supervised!(Burble.Text.NNTPSBackend) - - # MessageStore — stop any existing instance first so we get a clean ETS table. - case Process.whereis(Burble.Chat.MessageStore) do - nil -> :ok - pid -> GenServer.stop(pid) - end - - start_supervised!(Burble.Chat.MessageStore) + ensure_started({Phoenix.PubSub, name: Burble.PubSub}) + ensure_started({Registry, keys: :unique, name: Burble.RoomRegistry}) + ensure_started({DynamicSupervisor, name: Burble.RoomSupervisor, strategy: :one_for_one}) + ensure_started(Burble.Presence) + ensure_started(Burble.Media.Engine) + ensure_started(Burble.Text.NNTPSBackend) + + # MessageStore is application-owned; unique room ids isolate (#62). + ensure_started(Burble.Chat.MessageStore) case BurbleWeb.Endpoint.start_link() do {:ok, _pid} -> :ok diff --git a/server/test/support/test_helpers.ex b/server/test/support/test_helpers.ex index 6f0490da..a959eb82 100644 --- a/server/test/support/test_helpers.ex +++ b/server/test/support/test_helpers.ex @@ -54,6 +54,22 @@ defmodule Burble.TestHelpers do "user-" <> Base.encode16(:crypto.strong_rand_bytes(4), case: :lower) end + @doc """ + Start a child spec for the test, or reuse it if the application already + owns it (shared-app+reset test strategy, burble#62). Returns `{:ok, pid}` + whether freshly started or already running, so setups never crash on + `:already_started` for app-owned singletons. + """ + @spec ensure_started(tuple() | module()) :: {:ok, pid()} + def ensure_started(spec) do + case ExUnit.Callbacks.start_supervised(spec) do + {:ok, pid} -> {:ok, pid} + {:error, {:already_started, pid}} -> {:ok, pid} + {:error, {{:already_started, pid}, _spec}} -> {:ok, pid} + {:error, reason} -> raise "ensure_started failed: #{inspect(reason)}" + end + end + @doc "Build a Plug.Conn for testing API endpoints." @spec build_conn(String.t(), String.t(), map()) :: Plug.Conn.t() def build_conn(method, path, params \\ %{}) do