diff --git a/config/runtime.exs b/config/runtime.exs index 6b8b9585..fa12a3dd 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -5,6 +5,18 @@ alias Oban.Plugins.Cron require Logger +# Elixir 1.20's mix test starts the OTP application before test_helper +# runs, so the Repo child is already initialized by the time any per-test +# pool override in test_helper takes effect. Use Application.put_env +# (not `config`) to directly set the application environment value before +# the Repo supervisor reads it during app start. +if config_env() == :test do + for repo <- [Microwaveprop.Repo, Microwaveprop.AprsRepo] do + existing = Application.get_env(:microwaveprop, repo, []) + Application.put_env(:microwaveprop, repo, Keyword.put(existing, :pool, DBConnection.Ownership)) + end +end + # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the # system starts, so it is typically used to load production configuration diff --git a/config/test.exs b/config/test.exs index 74acc15d..d0106471 100644 --- a/config/test.exs +++ b/config/test.exs @@ -26,7 +26,7 @@ config :microwaveprop, Microwaveprop.AprsRepo, password: "postgres", hostname: "localhost", database: "aprsme_test#{System.get_env("MIX_TEST_PARTITION")}", - pool: Sandbox, + pool: DBConnection.Ownership, pool_size: 2, ownership_timeout: 60_000, timeout: 60_000 @@ -49,7 +49,7 @@ config :microwaveprop, Microwaveprop.Repo, password: "postgres", hostname: "localhost", database: "microwaveprop_test#{System.get_env("MIX_TEST_PARTITION")}", - pool: Sandbox, + pool: DBConnection.Ownership, pool_size: System.schedulers_online() * 2, ownership_timeout: 60_000, timeout: 60_000 diff --git a/mix.exs b/mix.exs index 447ca7cd..5d7e4fd3 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule Microwaveprop.MixProject do "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], "test.setup": ["ecto.create --quiet", "ecto.migrate --quiet"], - test: ["test"], + test: ["test --no-start"], "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], "assets.build": [ "compile", diff --git a/test/test_helper.exs b/test/test_helper.exs index fe115e04..9b61ac53 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,8 +1,49 @@ alias Ecto.Adapters.SQL.Sandbox -ExUnit.start(capture_log: true) -Sandbox.mode(Microwaveprop.Repo, :manual) -Sandbox.mode(Microwaveprop.AprsRepo, :manual) +# Elixir 1.20 loads test_helper before dependency ebin dirs are on the +# code path. Add them manually so `use` / `import` in test files can +# resolve modules from :only test deps (Mox, ExUnitProperties, etc.). +for dep_dir <- Path.wildcard("_build/test/lib/*/ebin") do + :code.add_patha(String.to_charlist(dep_dir)) +end + +# Ensure test support modules are compiled and loaded before ExUnit +# starts lazily compiling the test suite. The parallel compiler workers +# inherit the code server state, so loading them here makes them visible +# everywhere. +Code.require_file("test/support/data_case.ex") +Code.require_file("test/support/conn_case.ex") +Code.require_file("test/support/fixtures/accounts_fixtures.ex") +Code.require_file("test/support/fixtures/beacons_fixtures.ex") + +# ExUnit.start/1 with capture_log: true starts :logger, which cascades +# into the full OTP application. mix.exs now runs `mix test --no-start` +# so *we* control when the app boots. Initialize ExUnit first (no logger +# cascade), force the Sandbox pool into the application environment, +# then start the application manually. +ExUnit.start() + +# With ExUnit initialized but no app running, the Repo supervisor hasn't +# started yet. Force the Sandbox ownership pool into the app env before +# the Repo child reads it. +for repo <- [Microwaveprop.Repo, Microwaveprop.AprsRepo] do + existing = Application.get_env(:microwaveprop, repo, []) + + if existing[:url] || existing[:database] do + Application.put_env(:microwaveprop, repo, Keyword.put(existing, :pool, DBConnection.Ownership)) + end +end + +# Now start the full OTP app. The Repo supervisor reads pool: +# DBConnection.Ownership from the app env and initializes with it. +{:ok, _} = Application.ensure_all_started(:microwaveprop) + +# :auto mode lets every process (including background Tasks started by +# Application.init and Oban workers) auto-checkout from the sandbox. +# DataCase.setup_sandbox/1 still calls start_owner!/2 for per-test +# isolation, which is compatible with :auto mode. +Sandbox.mode(Microwaveprop.Repo, :auto) +Sandbox.mode(Microwaveprop.AprsRepo, :auto) # Silence sandbox-cleanup disconnect noise. When a test process exits # while still owning a Postgrex connection, the protocol logs an @@ -16,4 +57,6 @@ Logger.put_module_level( :critical ) -Mox.defmock(Microwaveprop.Valkey.MockAdapter, for: Microwaveprop.Valkey.Adapter) +if Code.ensure_loaded?(Mox) do + Mox.defmock(Microwaveprop.Valkey.MockAdapter, for: Microwaveprop.Valkey.Adapter) +end