Files
parrhesia/test/support/integration_case.ex
Steffen Beyer 7b2d92b714
Some checks failed
CI / Test (OTP 27.2 / Elixir 1.18.2) (push) Failing after 0s
CI / Test (OTP 28.4 / Elixir 1.19.4 + Marmot E2E) (push) Failing after 0s
fix: Sandbox owner checks in DB connection before exiting
The shared sandbox owner process exited without releasing its Postgrex
connection, causing intermittent "client exited" error logs on CI. The
owner now calls Sandbox.checkin before exiting, and on_exit waits for
the owner to finish before switching to manual mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:11:31 +01:00

80 lines
2.0 KiB
Elixir

defmodule Parrhesia.IntegrationCase do
@moduledoc false
use ExUnit.CaseTemplate
alias Ecto.Adapters.SQL.Sandbox
alias ExUnit.Case
alias Parrhesia.Repo
alias Parrhesia.TestSupport.Runtime
using opts do
quote bind_quoted: [opts: opts] do
use Case, async: Keyword.get(opts, :async, false)
alias Ecto.Adapters.SQL.Sandbox
alias Parrhesia.Repo
@moduletag parrhesia_sandbox: Keyword.get(opts, :sandbox, false)
end
end
setup tags do
Runtime.ensure_started!()
case tags[:parrhesia_sandbox] do
false ->
:ok
true ->
:ok = Sandbox.checkout(Repo)
:shared ->
# Delegate sandbox ownership to a dedicated process that outlives
# the test process. ExUnit terminates start_supervised! children
# (in reverse start order) before running on_exit callbacks. With
# the test process as sandbox owner, the connection dies the
# moment the test process exits — supervised children that make
# DB calls during their shutdown sequence would hit a dead
# connection. The separate owner stays alive through the entire
# supervised-child shutdown, and is only stopped in on_exit
# (which runs afterwards).
test_pid = self()
owner =
spawn(fn ->
:ok = Sandbox.checkout(Repo)
send(test_pid, {:sandbox_ready, self()})
receive do
:stop ->
Sandbox.checkin(Repo)
end
end)
receive do
{:sandbox_ready, ^owner} -> :ok
after
5_000 -> raise "Sandbox owner checkout timed out"
end
Sandbox.mode(Repo, {:shared, owner})
on_exit(fn ->
ref = Process.monitor(owner)
send(owner, :stop)
receive do
{:DOWN, ^ref, :process, ^owner, _reason} -> :ok
after
5_000 -> :ok
end
Sandbox.mode(Repo, :manual)
end)
end
:ok
end
end