Skip to content

Commit dadc006

Browse files
committed
try a db partition scheme
1 parent caa5a5d commit dadc006

2 files changed

Lines changed: 142 additions & 54 deletions

File tree

.github/workflows/ci.yml

Lines changed: 135 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -222,20 +222,19 @@ jobs:
222222
- name: Build the documentation
223223
run: mix docs --warnings-as-errors
224224

225-
integration-test-elixir:
226-
name: integration test (OTP ${{matrix.otp}} | Elixir ${{matrix.elixir}} | partition ${{matrix.partition}})
225+
# The generator, compile and formatter tests never talk to a database server,
226+
# and the sqlite3 tests use a file-backed database. Together that is the
227+
# largest slice of the suite, and it runs with no service containers at all --
228+
# nothing to pull, nothing to wait for.
229+
integration-test-no-db:
230+
name: integration test (OTP ${{matrix.otp}} | Elixir ${{matrix.elixir}} | no db)
227231

228232
runs-on: ubuntu-24.04
229233
timeout-minutes: 30
230234

231235
strategy:
232-
# Keep running the remaining partitions when one fails, otherwise a single
233-
# failing partition hides the results of all the others.
234236
fail-fast: false
235237
matrix:
236-
# Test files are split across Elixir instances via `mix test --partitions`.
237-
# Keep this list in sync with the `--partitions` count in the test step below.
238-
partition: [1, 2, 3]
239238
elixir: ["1.18.4", "1.20.4"]
240239
include:
241240
- elixir: "1.18.4"
@@ -247,7 +246,122 @@ jobs:
247246
env:
248247
ELIXIR_ASSERT_TIMEOUT: 10000
249248
PHX_CI: true
250-
MIX_TEST_PARTITION: ${{ matrix.partition }}
249+
PHX_TEST_GROUP: no-db
250+
# Set to half of the runner vCPU cores to compile deps/NIFs
251+
# concurrently across OS processes without CPU oversubscription.
252+
# See https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories
253+
# and https://elixir-lang.org/blog/2025/10/16/elixir-v1-19-0-released/
254+
MIX_OS_DEPS_COMPILE_PARTITION_COUNT: 2
255+
MAKEFLAGS: "-j2"
256+
257+
steps:
258+
- name: Checkout
259+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
260+
with:
261+
persist-credentials: false
262+
263+
- name: Mount installer/tmp on tmpfs
264+
run: |
265+
mkdir -p installer/tmp
266+
sudo mount -t tmpfs -o size=4G,uid=$(id -u),gid=$(id -g) tmpfs installer/tmp
267+
268+
- name: Set up Elixir
269+
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
270+
with:
271+
elixir-version: ${{ matrix.elixir }}
272+
otp-version: ${{ matrix.otp }}
273+
274+
- name: Restore deps and _build cache
275+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
276+
with:
277+
path: |
278+
integration_test/deps
279+
integration_test/_build
280+
key: integration-deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('integration_test/mix.lock') }}
281+
restore-keys: |
282+
integration-deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}
283+
284+
- name: Fetch dependencies
285+
working-directory: integration_test
286+
run: mix deps.get
287+
288+
- name: Compile dependencies
289+
working-directory: integration_test
290+
run: mix deps.compile
291+
env:
292+
MIX_ENV: test
293+
294+
- name: Run integration tests
295+
working-directory: integration_test
296+
run: |
297+
mix test --include database:sqlite3 \
298+
--formatter ExUnit.CLIFormatter \
299+
--formatter Phoenix.Integration.SummaryFormatter
300+
301+
# One job per database server, so each pulls exactly one image instead of all
302+
# three. `--only database:<name>` runs that server's tests and nothing else;
303+
# together with the `no-db` job above, every test runs exactly once.
304+
integration-test-db:
305+
name: integration test (OTP ${{matrix.otp}} | Elixir ${{matrix.elixir}} | ${{matrix.db.name}})
306+
307+
runs-on: ubuntu-24.04
308+
timeout-minutes: 30
309+
310+
strategy:
311+
fail-fast: false
312+
matrix:
313+
db:
314+
- name: postgresql
315+
image: postgres:18
316+
port: 5432
317+
# Probe over TCP (-h 127.0.0.1) rather than the Unix socket: initdb
318+
# runs a temporary server with `listen_addresses` empty, and a
319+
# socket probe reports ready against *that* server, before the real
320+
# one is listening.
321+
health_cmd: pg_isready -U postgres -h 127.0.0.1
322+
health_retries: 10
323+
health_start_period: 30s
324+
325+
- name: mysql
326+
image: mysql:26
327+
port: 3306
328+
# Two things to be aware of:
329+
#
330+
# 1. Initializing the data directory on first boot takes 30s+ on a
331+
# slow runner, which overruns the retry budget. Probes that fail
332+
# during --health-start-period do not consume that budget, and
333+
# the first successful probe ends the period early.
334+
# 2. The entrypoint starts a temporary server during initialization
335+
# with `port: 0` (Unix socket only). `mysqladmin ping -h
336+
# localhost` uses the socket, so it reports healthy against that
337+
# temporary server -- which is then shut down and restarted.
338+
# Probing over TCP only succeeds once the real server is up.
339+
health_cmd: mysqladmin ping -h 127.0.0.1 --protocol=TCP
340+
health_retries: 10
341+
health_start_period: 60s
342+
343+
- name: mssql
344+
image: mcr.microsoft.com/mssql/server:2019-latest
345+
port: 1433
346+
# Block scalar so the \" escapes reach docker verbatim, exactly as
347+
# they did when this was written inline.
348+
health_cmd: >-
349+
/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$SA_PASSWORD\" -C -Q 'SELECT 1' 2>/dev/null || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P \"$SA_PASSWORD\" -Q 'SELECT 1' 2>/dev/null
350+
health_retries: 25
351+
health_start_period: 30s
352+
353+
elixir: ["1.18.4", "1.20.4"]
354+
include:
355+
- elixir: "1.18.4"
356+
otp: "27.3.4.3"
357+
358+
- elixir: "1.20.4"
359+
otp: "29.0.5"
360+
361+
env:
362+
ELIXIR_ASSERT_TIMEOUT: 10000
363+
PHX_CI: true
364+
PHX_TEST_GROUP: ${{ matrix.db.name }}
251365
# Set to half of the runner vCPU cores to compile deps/NIFs
252366
# concurrently across OS processes without CPU oversubscription.
253367
# See https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories
@@ -256,56 +370,27 @@ jobs:
256370
MAKEFLAGS: "-j2"
257371

258372
services:
259-
postgres:
260-
image: postgres:18
373+
# A single, matrix-driven service: the job needs exactly one database, so
374+
# only one image is ever pulled. The service name does not matter -- the
375+
# job runs on the host, so tests reach the server through the published
376+
# port on localhost rather than a container network alias.
377+
db:
378+
image: ${{ matrix.db.image }}
261379
ports:
262-
- 5432:5432
380+
- ${{ matrix.db.port }}:${{ matrix.db.port }}
381+
# `env:` keys cannot be matrix-driven, so all three images' variables are
382+
# set unconditionally. Each image ignores the ones meant for the others.
263383
env:
264384
POSTGRES_PASSWORD: postgres
265-
# Probe over TCP (-h 127.0.0.1) rather than the Unix socket: initdb runs
266-
# a temporary server with `listen_addresses` empty, and a socket probe
267-
# reports ready against *that* server, before the real one is listening.
268-
options: >-
269-
--health-cmd "pg_isready -U postgres -h 127.0.0.1"
270-
--health-interval 2s
271-
--health-timeout 3s
272-
--health-retries 10
273-
--health-start-period 30s
274-
mysql:
275-
image: mysql:26
276-
ports:
277-
- 3306:3306
278-
env:
279385
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
280-
# Things to be aware of:
281-
#
282-
# 1. Initializing the data directory on first boot takes 30s+ on a slow
283-
# runner, which overruns the retry budget below. Probes that fail
284-
# during --health-start-period do not consume that budget, and the
285-
# first successful probe ends the period early.
286-
# 2. The entrypoint starts a temporary server during initialization with
287-
# `port: 0` (Unix socket only). `mysqladmin ping -h localhost` uses
288-
# the socket, so it reports healthy against that temporary server --
289-
# which is then shut down and restarted. Probing over TCP instead
290-
# only succeeds once the real server is listening on 3306.
291-
options: >-
292-
--health-cmd "mysqladmin ping -h 127.0.0.1 --protocol=TCP"
293-
--health-interval 2s
294-
--health-timeout 3s
295-
--health-retries 10
296-
--health-start-period 60s
297-
mssql:
298-
image: mcr.microsoft.com/mssql/server:2019-latest
299-
env:
300386
ACCEPT_EULA: Y
301387
SA_PASSWORD: some!Password
302-
ports:
303-
- 1433:1433
304388
options: >-
305-
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$SA_PASSWORD\" -C -Q 'SELECT 1' 2>/dev/null || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P \"$SA_PASSWORD\" -Q 'SELECT 1' 2>/dev/null"
389+
--health-cmd "${{ matrix.db.health_cmd }}"
306390
--health-interval 2s
307391
--health-timeout 3s
308-
--health-retries 25
392+
--health-retries ${{ matrix.db.health_retries }}
393+
--health-start-period ${{ matrix.db.health_start_period }}
309394
310395
steps:
311396
- name: Checkout
@@ -347,6 +432,6 @@ jobs:
347432
- name: Run integration tests
348433
working-directory: integration_test
349434
run: |
350-
mix test --include database --partitions 3 \
435+
mix test --only database:${{ matrix.db.name }} \
351436
--formatter ExUnit.CLIFormatter \
352437
--formatter Phoenix.Integration.SummaryFormatter

integration_test/test/support/summary_formatter.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,17 @@ defmodule Phoenix.Integration.SummaryFormatter do
130130
end)
131131
|> Enum.sort_by(& &1.total_us, :desc)
132132

133-
partition_info =
134-
case System.get_env("MIX_TEST_PARTITION") do
133+
# CI splits the suite across several jobs -- one per database, plus one that
134+
# needs no database at all -- and each writes its own summary. Label them so
135+
# the summaries for a single Elixir version can be told apart.
136+
group_info =
137+
case System.get_env("PHX_TEST_GROUP") do
135138
nil -> ""
136139
"" -> ""
137-
partition -> " / partition #{partition}"
140+
group -> " / #{group}"
138141
end
139142

140-
env_info = "Elixir #{System.version()} / OTP #{System.otp_release()}#{partition_info}"
143+
env_info = "Elixir #{System.version()} / OTP #{System.otp_release()}#{group_info}"
141144

142145
sections = [
143146
"""

0 commit comments

Comments
 (0)