From 4717ee6e80d15ccbaf6ba48010548e59f2a5850c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dobranowski?= Date: Tue, 21 Jul 2026 10:39:22 +0200 Subject: [PATCH 1/5] Sync GitHub Actions big test presets with CircleCI --- .github/workflows/ci.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b6bece4ce..1c05280ba7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,17 +65,17 @@ jobs: strategy: fail-fast: false matrix: - preset: [internal_mnesia, pgsql_mnesia, mysql_cets, - ldap_mnesia, elasticsearch_and_cassandra_mnesia] + # Every supported database is tested with CETS, every internal database with PostgreSQL. + # internal_mnesia is special (Mnesia for everything, plus MinIO and RabbitMQ). + # ldap_mnesia and elasticsearch_and_cassandra_mnesia are not migrated to CETS yet. + preset: [cockroachdb_cets, mysql_cets, pgsql_cets, ldap_mnesia, + elasticsearch_and_cassandra_mnesia, internal_mnesia, + pgsql_mnesia, pgsql_redis] otp: [ '29.0.3' ] include: - test-spec: "default.spec" - - preset: elasticsearch_and_cassandra_mnesia - test-spec: "mam.spec" - - preset: ldap_mnesia - test-spec: "default.spec" - otp: '28.4.2' - - preset: pgsql_mnesia + # Legacy OTP must be tested on PostgreSQL and CETS + - preset: pgsql_cets test-spec: "default.spec" otp: '28.4.2' runs-on: ubuntu-22.04 @@ -102,11 +102,12 @@ jobs: strategy: fail-fast: false matrix: - preset: [pgsql_mnesia, mysql_cets] + # Dynamic domains must be tested for PostgreSQL and CETS + preset: [pgsql_cets] otp: [ '29.0.3' ] test-spec: ["dynamic_domains.spec"] include: - - preset: pgsql_mnesia + - preset: pgsql_cets otp: '28.4.2' test-spec: "dynamic_domains.spec" runs-on: ubuntu-22.04 From 2414a4b2ab52461a6305b59fd0dca17668d0f63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dobranowski?= Date: Tue, 21 Jul 2026 11:36:13 +0200 Subject: [PATCH 2/5] Run CI on PRs that modify its config --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c05280ba7..cb118166ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,11 @@ name: CI on: push: branches: [ master ] + # Validate changes to the CI's own config on pull requests that touch it + pull_request: + paths: + - '.github/workflows/ci.yml' + - '.github/actions/big-tests/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From 24ea89406d58609cc4b283ad687b4f972da57d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dobranowski?= Date: Tue, 21 Jul 2026 15:03:47 +0200 Subject: [PATCH 3/5] Generate big-test presets for both CIs --- .github/workflows/ci.yml | 60 ++++++++----------------- tools/circle-generate-config.sh | 2 +- tools/circle-generate-presets | 43 ------------------ tools/generate-presets | 78 +++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 86 deletions(-) delete mode 100755 tools/circle-generate-presets create mode 100755 tools/generate-presets diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb118166ab..6601910b1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,8 @@ on: paths: - '.github/workflows/ci.yml' - '.github/actions/big-tests/**' + - '.circleci/presets.config' + - 'tools/generate-presets' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -65,56 +67,30 @@ jobs: if: ${{ failure() }} run: tools/gh-upload-to-s3.sh _build/test/logs test_logs - big_tests: - name: ${{matrix.preset}} on OTP ${{matrix.otp}} - strategy: - fail-fast: false - matrix: - # Every supported database is tested with CETS, every internal database with PostgreSQL. - # internal_mnesia is special (Mnesia for everything, plus MinIO and RabbitMQ). - # ldap_mnesia and elasticsearch_and_cassandra_mnesia are not migrated to CETS yet. - preset: [cockroachdb_cets, mysql_cets, pgsql_cets, ldap_mnesia, - elasticsearch_and_cassandra_mnesia, internal_mnesia, - pgsql_mnesia, pgsql_redis] - otp: [ '29.0.3' ] - include: - - test-spec: "default.spec" - # Legacy OTP must be tested on PostgreSQL and CETS - - preset: pgsql_cets - test-spec: "default.spec" - otp: '28.4.2' + # The big-test presets are defined once in .circleci/presets.config and shared with + # CircleCI; this job turns them into a GitHub Actions matrix consumed by big_tests below. + generate_matrix: + name: generate big tests matrix runs-on: ubuntu-22.04 + outputs: + matrix: ${{ steps.gen.outputs.matrix }} steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 persist-credentials: false - - uses: ./.github/actions/big-tests + - uses: erlef/setup-beam@v1.16.0 with: - otp: ${{matrix.otp}} - preset: ${{matrix.preset}} - gh-token: ${{secrets.GITHUB_TOKEN}} - test-spec: ${{matrix.test-spec}} - - name: upload common test results on failure - if: ${{ failure() }} - run: tools/gh-upload-to-s3.sh big_tests/ct_report - - name: upload big_tests results to GA4 - if: ${{ !cancelled() && github.ref_name == 'master' }} - run: tools/gh-report-failing-testcases-to-ga4.sh + otp-version: '28.4.2' + - id: gen + # Arguments map the presets' latest/legacy executors to OTP versions + run: echo "matrix=$(tools/generate-presets .circleci/presets.config github 29.0.3 28.4.2)" >> "$GITHUB_OUTPUT" - dynamic_domains_big_tests: - name: dynamic domains ${{matrix.preset}} on OTP ${{matrix.otp}} + big_tests: + name: ${{matrix.name}} + needs: generate_matrix strategy: fail-fast: false - matrix: - # Dynamic domains must be tested for PostgreSQL and CETS - preset: [pgsql_cets] - otp: [ '29.0.3' ] - test-spec: ["dynamic_domains.spec"] - include: - - preset: pgsql_cets - otp: '28.4.2' - test-spec: "dynamic_domains.spec" + matrix: ${{ fromJSON(needs.generate_matrix.outputs.matrix) }} runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 @@ -135,7 +111,7 @@ jobs: run: tools/gh-report-failing-testcases-to-ga4.sh coveralls_webhook: - needs: [big_tests, small_tests, dynamic_domains_big_tests] + needs: [big_tests, small_tests] runs-on: ubuntu-22.04 steps: - name: Finish coveralls parallel build diff --git a/tools/circle-generate-config.sh b/tools/circle-generate-config.sh index d75a0164d5..37281f8030 100755 --- a/tools/circle-generate-config.sh +++ b/tools/circle-generate-config.sh @@ -45,7 +45,7 @@ CERTS_CACHE_KEY=$(cat certs_cache_key) # Matches plugins list in the rebar.config REBAR_PLUGINS_HASH=$(cat rebar.config | sed -n '/^{plugins/,/]}./p' | sha1sum | awk '{print $1}') -BIG_TESTS="$(./tools/circle-generate-presets .circleci/presets.config | "$SED" 's/^/ /')" +BIG_TESTS="$(./tools/generate-presets .circleci/presets.config circleci | "$SED" 's/^/ /')" BIG_TESTS_LIST="$(awk '$1 == "name:" {print "-", $2}' <<< "$BIG_TESTS" | "$SED" 's/^/ /')" env \ diff --git a/tools/circle-generate-presets b/tools/circle-generate-presets deleted file mode 100755 index 2f7d995d06..0000000000 --- a/tools/circle-generate-presets +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env escript - -main([Path]) -> - {ok, Presets} = file:consult(Path), - Presets1 = parse_presets(Presets), - print_presets(Presets1); -main(_Args) -> - halt(1). - -parse_presets(Presets) -> - maps:from_list([maps:take(name, maps:from_list(P)) || P <- Presets]). - -print_presets(Presets) -> - maps:foreach( - fun(Name, Opts) -> - FullOpts = - case maps:take(include, Opts) of - error -> Opts; - {Include, Opts1} -> maps:merge(maps:get(Include, Presets), Opts1) - end, - print_preset(Name, FullOpts) - end, - Presets - ). - -print_preset(Name, #{executor := Executor, preset := Preset, requires := Requires} = Opts) -> - MaybeSpec = - case maps:get(spec, Opts, undefined) of - undefined -> ""; - Spec -> [io_lib:nl(), " spec: ", Spec] - end, - io:format( - """ - - big_tests_in_docker: - name: ~s - executor: ~s - preset: ~s - requires: ~p~s - context: mongooseim-org - filters: *all_tags~n - """, - [Name, Executor, Preset, Requires, MaybeSpec] - ). diff --git a/tools/generate-presets b/tools/generate-presets new file mode 100755 index 0000000000..4a6efeb62c --- /dev/null +++ b/tools/generate-presets @@ -0,0 +1,78 @@ +#!/usr/bin/env escript + +%% Generates the big-test job list from the presets file for both CIs, so they share a +%% single source of truth. +%% +%% generate-presets circleci +%% generate-presets github + +main([Path, "circleci"]) -> + print_circleci(load(Path)); +main([Path, "github", LatestOtp, LegacyOtp]) -> + print_github(load(Path), #{latest => LatestOtp, legacy => LegacyOtp}); +main(_Args) -> + halt(1). + +load(Path) -> + {ok, Presets} = file:consult(Path), + parse_presets(Presets). + +parse_presets(Presets) -> + maps:from_list([maps:take(name, maps:from_list(P)) || P <- Presets]). + +resolve_include(Opts, Presets) -> + case maps:take(include, Opts) of + error -> Opts; + {Include, Opts1} -> maps:merge(maps:get(Include, Presets), Opts1) + end. + +%% CircleCI: workflow job YAML fragment + +print_circleci(Presets) -> + maps:foreach( + fun(Name, Opts) -> + print_circleci_preset(Name, resolve_include(Opts, Presets)) + end, + Presets + ). + +print_circleci_preset(Name, #{executor := Executor, preset := Preset, requires := Requires} = Opts) -> + MaybeSpec = + case maps:get(spec, Opts, undefined) of + undefined -> ""; + Spec -> [io_lib:nl(), " spec: ", Spec] + end, + io:format( + """ + - big_tests_in_docker: + name: ~s + executor: ~s + preset: ~s + requires: ~p~s + context: mongooseim-org + filters: *all_tags~n + """, + [Name, Executor, Preset, Requires, MaybeSpec] + ). + +%% GitHub Actions: strategy matrix JSON. The presets' latest/legacy executors are +%% mapped to the OTP versions given as arguments. + +print_github(Presets, Otp) -> + Objs = [github_object(Name, resolve_include(maps:get(Name, Presets), Presets), Otp) + || Name <- lists:sort(maps:keys(Presets))], + io:format("~s", [["{\"include\":[", lists:join(",", Objs), "]}"]]). + +github_object(Name, #{executor := Executor, preset := Preset} = Opts, Otp) -> + Fields = [kv("name", Name), kv("preset", Preset), + kv("otp", otp_version(Executor, Otp)), + kv("test-spec", maps:get(spec, Opts, "default.spec"))], + ["{", lists:join(",", Fields), "}"]. + +kv(Key, Value) -> ["\"", Key, "\":\"", to_str(Value), "\""]. + +to_str(A) when is_atom(A) -> atom_to_list(A); +to_str(L) -> L. + +otp_version("latest_otp" ++ _, #{latest := V}) -> V; +otp_version("legacy_otp" ++ _, #{legacy := V}) -> V. From 80187d56cbbb54c4249ad19f8339bd8098a883c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dobranowski?= Date: Thu, 23 Jul 2026 15:49:10 +0200 Subject: [PATCH 4/5] Apply suggestions --- .github/workflows/ci.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6601910b1e..e6d0bf6601 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,13 +3,6 @@ name: CI on: push: branches: [ master ] - # Validate changes to the CI's own config on pull requests that touch it - pull_request: - paths: - - '.github/workflows/ci.yml' - - '.github/actions/big-tests/**' - - '.circleci/presets.config' - - 'tools/generate-presets' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -82,7 +75,6 @@ jobs: with: otp-version: '28.4.2' - id: gen - # Arguments map the presets' latest/legacy executors to OTP versions run: echo "matrix=$(tools/generate-presets .circleci/presets.config github 29.0.3 28.4.2)" >> "$GITHUB_OUTPUT" big_tests: From 2ae73dbe8b1b6fbd4776877edb487cd7a383cd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dobranowski?= Date: Thu, 23 Jul 2026 16:14:38 +0200 Subject: [PATCH 5/5] Resolve preset includes in `parse_presets` and use `json:encode` --- tools/generate-presets | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/tools/generate-presets b/tools/generate-presets index 4a6efeb62c..7b777e3011 100755 --- a/tools/generate-presets +++ b/tools/generate-presets @@ -18,7 +18,8 @@ load(Path) -> parse_presets(Presets). parse_presets(Presets) -> - maps:from_list([maps:take(name, maps:from_list(P)) || P <- Presets]). + Map = maps:from_list([maps:take(name, maps:from_list(P)) || P <- Presets]), + maps:map(fun(_Name, Opts) -> resolve_include(Opts, Map) end, Map). resolve_include(Opts, Presets) -> case maps:take(include, Opts) of @@ -29,12 +30,7 @@ resolve_include(Opts, Presets) -> %% CircleCI: workflow job YAML fragment print_circleci(Presets) -> - maps:foreach( - fun(Name, Opts) -> - print_circleci_preset(Name, resolve_include(Opts, Presets)) - end, - Presets - ). + maps:foreach(fun print_circleci_preset/2, Presets). print_circleci_preset(Name, #{executor := Executor, preset := Preset, requires := Requires} = Opts) -> MaybeSpec = @@ -59,20 +55,14 @@ print_circleci_preset(Name, #{executor := Executor, preset := Preset, requires : %% mapped to the OTP versions given as arguments. print_github(Presets, Otp) -> - Objs = [github_object(Name, resolve_include(maps:get(Name, Presets), Presets), Otp) - || Name <- lists:sort(maps:keys(Presets))], - io:format("~s", [["{\"include\":[", lists:join(",", Objs), "]}"]]). + Include = [github_object(Name, Opts, Otp) || {Name, Opts} <- lists:sort(maps:to_list(Presets))], + io:format("~s", [json:encode(#{include => Include})]). github_object(Name, #{executor := Executor, preset := Preset} = Opts, Otp) -> - Fields = [kv("name", Name), kv("preset", Preset), - kv("otp", otp_version(Executor, Otp)), - kv("test-spec", maps:get(spec, Opts, "default.spec"))], - ["{", lists:join(",", Fields), "}"]. - -kv(Key, Value) -> ["\"", Key, "\":\"", to_str(Value), "\""]. - -to_str(A) when is_atom(A) -> atom_to_list(A); -to_str(L) -> L. + #{name => atom_to_binary(Name), + preset => list_to_binary(Preset), + otp => list_to_binary(otp_version(Executor, Otp)), + 'test-spec' => list_to_binary(maps:get(spec, Opts, "default.spec"))}. otp_version("latest_otp" ++ _, #{latest := V}) -> V; otp_version("legacy_otp" ++ _, #{legacy := V}) -> V.