Speed up CI with parallel ExUnit partitions

almirsarajcic

almirsarajcic

2 hours ago

0 comments

Running your full test suite sequentially in CI is wasted time. ExUnit has built-in support for partitioning — split your suite into N slices and run them in parallel, each in its own process.

# ❌ Sequential — all tests in one process, one after another
mix test

# ✅ Parallel — 6 partitions running at the same time
for i in 1 2 3 4 5 6; do \
  MIX_TEST_PARTITION=$$i \
  MIX_TEST_DB_SUFFIX=ci$$i \
  MIX_BUILD_PATH=_build/test_part$$i \
  mix test --partitions 6 --max-failures 5 \
    > log/ci/test_part$$i.log 2>&1 & \
PIDS="$$PIDS $$!"; \
done; \
for pid in $$PIDS; do wait $$pid || PART_EXIT=1; done

ExUnit hashes each test file name to assign it to a partition. The assignment is deterministic — the same file always lands in the same partition across runs, so failures are reproducible without re-running the full suite.

Two isolation pieces are required for this to work correctly.

Build path isolation. Without MIX_BUILD_PATH=_build/test_part$i, all six parallel compilations write to the same _build/test directory and race each other. Giving each partition its own build path eliminates the race.

Database isolation. Ecto’s SQL sandbox is designed for concurrent tests within a single process, not across operating system processes. If two partitions share the same database, they can see or corrupt each other’s data. The fix is to give each partition its own database:

# config/test.exs
partition = System.get_env("MIX_TEST_PARTITION") || "0"
suffix = System.get_env("MIX_TEST_DB_SUFFIX") || partition

config :my_app, MyApp.Repo,
  database: "my_app_test#{suffix}"

MIX_TEST_DB_SUFFIX is a project-level convention, not something built into ExUnit. You set it in the shell and read it in config. With six partitions, you get six databases: my_app_test1 through my_app_test6. Run mix ecto.create and mix ecto.migrate for each before the first CI run.

MIX_TEST_PARTITION is required whenever --partitions N is set. If you omit it, mix test refuses to run and exits with an error. Setting --partitions 1 with MIX_TEST_PARTITION=1 is useful for running a single file through the partition machinery while debugging:

MIX_TEST_PARTITION=1 mix test --partitions 1 test/my_module_test.exs

mix test --partitions docs

Comments (0)

Sign in with GitHub to join the discussion