From d214c8f69cd29ff11fa9956f552e10e7c7272ff7 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Mon, 8 Sep 2025 16:42:43 +0800 Subject: [PATCH] Support OTP 28 in CI This also fixes errors caused by different internal references in compiled regex objects. --- .github/workflows/ci.yml | 22 +++++++------ Earthfile | 62 +++++++++++++++++++----------------- test/ecto/changeset_test.exs | 60 +++++++++++++++++++++------------- 3 files changed, 82 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30283506f0..11b18bffb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,15 +19,17 @@ jobs: fail-fast: false matrix: include: - - elixir: "1.18.1" - otp: "27.2" + - elixir: "1.18.4" + otp: "28.0" lint: lint - elixir: "1.17.3" - otp: "27.1" + otp: "27.3" - elixir: "1.17.3" - otp: "25.0.4" + otp: "26.2" + - elixir: "1.17.3" + otp: "25.3" - elixir: "1.14.5" - otp: "24.3.4.17" + otp: "24.3" steps: - name: Checkout @@ -68,11 +70,13 @@ jobs: fail-fast: false matrix: elixirbase: - - "1.17.3-erlang-27.1-alpine-3.17.9" - - "1.17.3-erlang-25.0.4-alpine-3.17.9" - - "1.14.5-erlang-23.3.4.20-alpine-3.16.9" + - "1.18.4-erlang-28.0.2-alpine-3.22.1" + - "1.17.3-erlang-27.3.4.2-alpine-3.22.1" + - "1.17.3-erlang-26.2.5.14-alpine-3.22.1" + - "1.17.3-erlang-25.3.2.21-alpine-3.22.1" + - "1.14.5-erlang-24.3.4.17-alpine-3.22.1" steps: - uses: earthly/actions-setup@v1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: ecto integration-test under ${{matrix.elixirbase}} run: earthly -P --ci --build-arg ELIXIR_BASE=${{matrix.elixirbase}} +integration-test diff --git a/Earthfile b/Earthfile index 098e83092c..940727d11c 100644 --- a/Earthfile +++ b/Earthfile @@ -2,12 +2,14 @@ VERSION 0.6 all: BUILD \ - --build-arg ELIXIR_BASE=1.15.6-erlang-25.3.2.6-alpine-3.18.4 \ - --build-arg ELIXIR_BASE=1.15.6-erlang-24.3.4.14-alpine-3.18.4 \ + --build-arg ELIXIR_BASE=1.18.4-erlang-28.0.2-alpine-3.22.1 \ + --build-arg ELIXIR_BASE=1.17.3-erlang-27.3.4.2-alpine-3.22.1 \ + --build-arg ELIXIR_BASE=1.17.3-erlang-26.2.5.14-alpine-3.22.1 \ + --build-arg ELIXIR_BASE=1.14.5-erlang-24.3.4.17-alpine-3.22.1 \ +integration-test integration-test-base: - ARG ELIXIR_BASE=1.15.6-erlang-25.3.2.6-alpine-3.18.4 + ARG ELIXIR_BASE=1.18.4-erlang-28.0.2-alpine-3.22.1 ARG TARGETARCH FROM hexpm/elixir:$ELIXIR_BASE RUN apk add --no-progress --update git build-base @@ -44,37 +46,37 @@ integration-test: ARG MCR_IMG="mcr.microsoft.com/mssql/server:2019-latest" ARG MYSQL_IMG="mysql:5.7" - # then run the tests +# then run the tests WITH DOCKER --pull "$PG_IMG" --pull "$MCR_IMG" --pull "$MYSQL_IMG" --platform linux/amd64 - RUN set -e; \ - timeout=$(expr $(date +%s) + 60); \ + RUN set -e; \ + timeout=$(expr $(date +%s) + 60); \ - # start databases - docker run --name mssql --network=host -d -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=some!Password' "$MCR_IMG"; \ - docker run --name pg --network=host -d -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres "$PG_IMG"; \ - docker run --name mysql --network=host -d -e MYSQL_ROOT_PASSWORD=root "$MYSQL_IMG"; \ + # start databases + docker run --name mssql --network=host -d -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=some!Password' "$MCR_IMG"; \ + docker run --name pg --network=host -d -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres "$PG_IMG"; \ + docker run --name mysql --network=host -d -e MYSQL_ROOT_PASSWORD=root "$MYSQL_IMG"; \ - # wait for mssql to start - while ! sqlcmd -C -S tcp:127.0.0.1,1433 -U sa -P 'some!Password' -Q "SELECT 1" >/dev/null 2>&1; do \ - test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for mssql"; exit 1); \ - echo "waiting for mssql"; \ - sleep 1; \ - done; \ + # wait for mssql to start + while ! sqlcmd -C -S tcp:127.0.0.1,1433 -U sa -P 'some!Password' -Q "SELECT 1" >/dev/null 2>&1; do \ + test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for mssql"; exit 1); \ + echo "waiting for mssql"; \ + sleep 1; \ + done; \ - # wait for postgres to start - while ! pg_isready --host=127.0.0.1 --port=5432 --quiet; do \ - test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for postgres"; exit 1); \ - echo "waiting for postgres"; \ - sleep 1; \ - done; \ + # wait for postgres to start + while ! pg_isready --host=127.0.0.1 --port=5432 --quiet; do \ + test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for postgres"; exit 1); \ + echo "waiting for postgres"; \ + sleep 1; \ + done; \ - # wait for mysql to start - while ! mysqladmin ping --host=127.0.0.1 --port=3306 --protocol=TCP --silent; do \ - test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for mysql"; exit 1); \ - echo "waiting for mysql"; \ - sleep 1; \ - done; \ + # wait for mysql to start + while ! mysqladmin ping --host=127.0.0.1 --port=3306 --protocol=TCP --silent; do \ + test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for mysql"; exit 1); \ + echo "waiting for mysql"; \ + sleep 1; \ + done; \ - # run test - MSSQL_URL='sa:some!Password@127.0.0.1' MYSQL_URL='root:root@127.0.0.1' PG_URL='postgres:postgres@127.0.0.1' ECTO_PATH='/src/ecto' mix test.all; + # run test + MSSQL_URL='sa:some!Password@127.0.0.1' MYSQL_URL='root:root@127.0.0.1' PG_URL='postgres:postgres@127.0.0.1' ECTO_PATH='/src/ecto' mix test.all; END diff --git a/test/ecto/changeset_test.exs b/test/ecto/changeset_test.exs index a7b966fa86..57a3b9482c 100644 --- a/test/ecto/changeset_test.exs +++ b/test/ecto/changeset_test.exs @@ -1317,7 +1317,7 @@ defmodule Ecto.ChangesetTest do assert changeset.valid? assert changeset.errors == [] - assert validations(changeset) == [title: {:format, ~r/@/}] + assert match?([title: {:format, %Regex{source: "@"}}], validations(changeset)) changeset = changeset(%{"title" => "foobar"}) @@ -1325,7 +1325,7 @@ defmodule Ecto.ChangesetTest do refute changeset.valid? assert changeset.errors == [title: {"has invalid format", [validation: :format]}] - assert validations(changeset) == [title: {:format, ~r/@/}] + assert match?([title: {:format, %Regex{source: "@"}}], validations(changeset)) changeset = changeset(%{"title" => "foobar"}) @@ -2681,17 +2681,19 @@ defmodule Ecto.ChangesetTest do message: "cannot be more than 15 characters" ) - assert constraints(changeset) == + assert match?( [ %{ type: :check, field: :title, - constraint: ~r/title_must_be_short\d+/, + constraint: %Regex{source: "title_must_be_short\\d+"}, match: :exact, error_message: "cannot be more than 15 characters", error_type: :check } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Post{}) @@ -2769,17 +2771,19 @@ defmodule Ecto.ChangesetTest do changeset = change(%Post{}) |> unique_constraint(:title, name: ~r/whatever\d+/, message: "is taken") - assert constraints(changeset) == + assert match?( [ %{ type: :unique, field: :title, - constraint: ~r/whatever\d+/, + constraint: %Regex{source: "whatever\\d+"}, match: :exact, error_message: "is taken", error_type: :unique } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Post{}) @@ -2836,17 +2840,19 @@ defmodule Ecto.ChangesetTest do changeset = change(%Post{}) |> unique_constraint(:permalink, name: ~r/whatever\d+/, message: "is taken") - assert constraints(changeset) == + assert match?( [ %{ type: :unique, field: :permalink, - constraint: ~r/whatever\d+/, + constraint: %Regex{source: "whatever\\d+"}, match: :exact, error_message: "is taken", error_type: :unique } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Post{}) @@ -2979,17 +2985,19 @@ defmodule Ecto.ChangesetTest do change(%Comment{}) |> foreign_key_constraint(:post, name: ~r/whatever\d+/, message: "is not available") - assert constraints(changeset) == + assert match?( [ %{ type: :foreign_key, field: :post, - constraint: ~r/whatever\d+/, + constraint: %Regex{source: "whatever\\d+"}, match: :exact, error_message: "is not available", error_type: :foreign } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Comment{}) @@ -3100,17 +3108,19 @@ defmodule Ecto.ChangesetTest do change(%Comment{}) |> assoc_constraint(:post, name: ~r/whatever\d+/, message: "is not available") - assert constraints(changeset) == + assert match?( [ %{ type: :foreign_key, field: :post, - constraint: ~r/whatever\d+/, + constraint: %Regex{source: "whatever\\d+"}, match: :exact, error_message: "is not available", error_type: :assoc } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Comment{}) @@ -3229,17 +3239,19 @@ defmodule Ecto.ChangesetTest do change(%Post{}) |> no_assoc_constraint(:comments, name: ~r/comments_post_id_fkey\d+/, message: "exists") - assert constraints(changeset) == + assert match?( [ %{ type: :foreign_key, field: :comments, - constraint: ~r/comments_post_id_fkey\d+/, + constraint: %Regex{source: "comments_post_id_fkey\\d+"}, match: :exact, error_message: "exists", error_type: :no_assoc } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Post{}) @@ -3409,17 +3421,19 @@ defmodule Ecto.ChangesetTest do change(%Post{}) |> exclusion_constraint(:title, name: ~r/whatever\d+/, message: "is invalid") - assert constraints(changeset) == + assert match?( [ %{ type: :exclusion, field: :title, - constraint: ~r/whatever\d+/, + constraint: %Regex{source: "whatever\\d+"}, match: :exact, error_message: "is invalid", error_type: :exclusion } - ] + ], + constraints(changeset) + ) assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn -> change(%Post{})