File 3084-gh-Add-testing-to-github-actions.patch of Package erlang

From 55000f7e1a99c935612df99af8c6ff886c076c6b Mon Sep 17 00:00:00 2001
From: Lukas Larsson <lukas@erlang.org>
Date: Tue, 21 Dec 2021 11:05:23 +0100
Subject: [PATCH 04/11] gh: Add testing to github actions

---
 .github/dockerfiles/Dockerfile.32-bit        |  13 +-
 .github/dockerfiles/Dockerfile.64-bit        |  24 +-
 .github/dockerfiles/Dockerfile.clang         |  31 ++
 .github/dockerfiles/Dockerfile.cross-compile |  21 +-
 .github/dockerfiles/Dockerfile.documentation |  23 --
 .github/dockerfiles/Dockerfile.ubuntu-base   |  71 +++-
 .github/dockerfiles/init.sh                  |  11 +
 .github/dockerfiles/log4j.properties         |   9 +
 .github/dockerfiles/odbc.ini                 |  19 ++
 .github/dockerfiles/odbcinst.ini             |  15 +
 .github/scripts/path-filters.sh              |  16 +
 .github/scripts/pr-comment.js                | 110 ++++++
 .github/scripts/sync-github-prs.es           | 117 +++++++
 .github/workflows/main.yaml                  | 336 +++++++++++++++----
 .github/workflows/pr-comment.yaml            | 131 ++++++++
 .github/workflows/sync-github-releases.yaml  |  46 ++-
 .github/workflows/update-base.yaml           |   4 +-
 HOWTO/DEVELOPMENT.md                         |  99 ++++++
 scripts/run-smoke-tests                      |  20 --
 19 files changed, 947 insertions(+), 169 deletions(-)
 create mode 100644 .github/dockerfiles/Dockerfile.clang
 delete mode 100644 .github/dockerfiles/Dockerfile.documentation
 create mode 100755 .github/dockerfiles/init.sh
 create mode 100644 .github/dockerfiles/log4j.properties
 create mode 100644 .github/dockerfiles/odbc.ini
 create mode 100644 .github/dockerfiles/odbcinst.ini
 create mode 100755 .github/scripts/path-filters.sh
 create mode 100644 .github/scripts/pr-comment.js
 create mode 100755 .github/scripts/sync-github-prs.es
 create mode 100644 .github/workflows/pr-comment.yaml
 delete mode 100755 scripts/run-smoke-tests

diff --git a/.github/dockerfiles/Dockerfile.documentation b/.github/dockerfiles/Dockerfile.documentation
deleted file mode 100644
index b0c2eb8015..0000000000
--- a/.github/dockerfiles/Dockerfile.documentation
+++ /dev/null
@@ -1,23 +0,0 @@
-FROM docker.pkg.github.com/erlang/otp/ubuntu-base
-
-ARG MAKEFLAGS=-j4
-ENV MAKEFLAGS=$MAKEFLAGS \
-        ERLC_USE_SERVER=yes \
-        ERL_TOP=/buildroot/otp \
-        PATH=/buildroot/otp/bin:$PATH
-
-ARG ARCHIVE=./otp.tar.gz
-COPY $ARCHIVE /buildroot/otp.tar.gz
-RUN cd /buildroot && tar -xzf ./otp.tar.gz
-
-WORKDIR /buildroot/otp/
-
-ENV RELEASE_ROOT=/otp
-
-RUN ./configure --prefix=/otp && make && make release
-
-RUN ./configure && make && make release
-
-RUN make docs release_docs
-
-ENTRYPOINT ["bash","-c"]
diff --git a/.github/scripts/pr-comment.js b/.github/scripts/pr-comment.js
new file mode 100644
index 0000000000..baabee1f6f
--- /dev/null
+++ b/.github/scripts/pr-comment.js
@@ -0,0 +1,110 @@
+module.exports = async({ github, context, state, pr_number }) => {
+
+    console.log(`Workflow: ${JSON.stringify(context.payload.workflow_run,null,2)}`);
+
+    /* We use this link service as github does not (yet) expose an API where
+       you can download an artifact.
+       https://github.com/actions/upload-artifact/issues/50
+    */
+    var nightlyURL = (artifact) => {
+        return `https://nightly.link/${context.repo.owner}/${context.repo.repo}/actions/artifacts/${artifact.id}.zip`
+    };
+
+    const artifacts = await github.paginate(
+        github.rest.actions.listWorkflowRunArtifacts,
+        {
+            owner: context.repo.owner,
+            repo: context.repo.repo,
+            run_id: context.payload.workflow_run.id,
+            per_page: 100
+        });
+
+    const ct_logs = artifacts.find(
+        (a) => { return a.name == 'test_results'; });
+    const html_docs = artifacts.find(
+        (a) => { return a.name == 'otp_doc_html'; });
+    const win_exe = artifacts.find(
+        (a) => { return a.name.match(/win32/); });
+
+    let gh_comments = await github.paginate(
+        github.rest.issues.listComments,
+        {
+            owner: context.repo.owner,
+            repo: context.repo.repo,
+            issue_number: pr_number,
+            per_page: 100
+        });
+
+    /* We find the newest comment by "github-actions[bot]". There really should
+       only be one, but the PR scripts may be buggy and if so we want to updated
+       the latest post. */
+    gh_comments.reverse();
+    const gh_comment = gh_comments.find(c => c.user.login == "github-actions[bot]");
+
+    console.log(`Comment to update: ${JSON.stringify(gh_comment,null,2)}`);
+
+    let ct_body;
+
+    /* The EnricoMi/publish-unit-test-result-action@v1 action creates/updates a comment
+       to always start with "## CT Test Results". Below we append some data to that
+       comment to further help the user.
+    */
+
+    if (gh_comment && !gh_comment.body.match("<!-- marker -->")) {
+        /* If the comment does not have a marker, it has been touched by
+           EnricoMi/publish-unit-test-result-action@v1 and then we need
+           to update the comment */
+        ct_body = gh_comment.body;
+    } else if (gh_comment && state == 'starting') {
+        /* If the comment exists and we are just starting the workflow we do nothing */
+        return;
+    } else {
+        /* if the comment does not exist we use a place holder comment. This
+           needs to start with "## CT Test Results" and
+           contain "Results for commit" as otherwise
+           EnricoMi/publish-unit-test-result-action@v1 will create a new comment. */
+        ct_body = "## CT Test Results\n\n";
+        if (state == 'starting') {
+            ct_body += `Tests are running... ${context.payload.workflow_run.html_url}\n\n`;
+        } else {
+            ct_body += "No tests were run for this PR. This is either because the build failed, or the PR is based on a branch without GH actions tests configured.\n\n";
+        }
+        ct_body += `Results for commit ${context.payload.workflow_run.head_sha}`;
+    }
+
+    console.log(`ct_body: ${ct_body}`);
+
+    const body = `${ct_body}
+
+<!-- marker -->
+
+To speed up review, make sure that you have read [Contributing to Erlang/OTP](/${context.repo.owner}/${context.repo.repo}/blob/master/CONTRIBUTING.md) and that all [checks](/${context.repo.owner}/${context.repo.repo}/pull/${pr_number}/checks) pass.
+
+See the [TESTING](/${context.repo.owner}/${context.repo.repo}/blob/master/HOWTO/TESTING.md) and [DEVELOPMENT](/${context.repo.owner}/${context.repo.repo}/blob/master/HOWTO/DEVELOPMENT.md) HowTo guides for details about how to run test locally.
+
+## Artifacts
+* ` + (ct_logs ? `[Complete CT logs](https://erlang.github.io/prs/${pr_number}/ct_logs/index.html) ([Download Logs](${nightlyURL(ct_logs)}))` : "No CT logs found") + `
+* ` + (html_docs ? `[HTML Documentation](https://erlang.github.io/prs/${pr_number}/doc/index.html) ([Download HTML Docs](${nightlyURL(html_docs)}))` : "No HTML docs found") + `
+* ` + (win_exe ? `[Windows Installer](${nightlyURL(win_exe)})` : "No Windows Installer found") + `
+
+// Erlang/OTP Github Action Bot
+`;
+    if (gh_comment) {
+        if (gh_comment.body != body) {
+            console.log("Update comment: " + gh_comment.id);
+            await github.rest.issues.updateComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                comment_id: gh_comment.id,
+                body: body
+            });
+        }
+    } else {
+        await github.rest.issues.createComment({
+            owner: context.repo.owner,
+            repo: context.repo.repo,
+            issue_number: context.payload.workflow_run.pull_requests[0].number,
+            body: body
+        });
+    }
+};
diff --git a/.github/scripts/sync-github-prs.es b/.github/scripts/sync-github-prs.es
new file mode 100755
index 0000000000..503195b423
--- /dev/null
+++ b/.github/scripts/sync-github-prs.es
@@ -0,0 +1,117 @@
+#!/usr/bin/env escript
+%%! -pa jsx/_build/default/lib/jsx/ebin/
+%%
+%% This scripts downloads the docs + test results from an otp repo
+%% into the Target folder. It tries its best to not create too large
+%% files so that gh will still be happy with us when this is published to
+%% gh pages
+-mode(compile).
+
+main([Repo, Target]) ->
+    AllOpenPrs = ghapi("gh api --paginate -X GET /repos/"++Repo++"/pulls -f state=open"),
+    %% Download all updates, there really should not be any to download as they
+    %% are updated when a PR is updated, but we do it anyways just to be safe.
+    handle_prs(Repo, Target,AllOpenPrs),
+
+    %% Delete any PRs that have been closed
+    {ok, AllPrs} = file:list_dir(Target),
+    lists:foreach(
+      fun(PRNo) ->
+              case lists:search(
+                     fun(#{ <<"number">> := No }) ->
+                        No =:= list_to_integer(PRNo)
+                     end, AllOpenPrs) of
+                  {value, _} ->
+                      ok;
+                  false ->
+                      cmd("rm -rf " ++ filename:join(Target,PRNo))
+              end
+      end, AllPrs);
+main([Repo, Target, PRNo]) ->
+    handle_prs(Repo, Target, [ghapi("gh api /repos/"++Repo++"/pulls/"++PRNo)]).
+
+handle_prs(Repo, Target, AllPRs) ->
+
+    [handle_pr(Repo, Target, PR) || PR <- AllPRs],
+
+    %% Remove all links and files > 50MB
+    cmd(["find ",Target," -type l -exec rm -f {} \\;"]),
+    cmd(["find ",Target," -type f -size +50M -exec rm -f {} \\;"]),
+
+    ok.
+
+%% In order to get the latest gh actions run for a PR, we have to first list
+%% all workflow runs for that branch, and then look for a matching sha with the
+%% current top of the PR. Github does not have any API to find it any other way.
+%% See https://github.community/t/retrieve-workflow-id-for-a-given-pr/199745/4
+%%   for a discussion about this.
+%%
+handle_pr(Repo, Target,
+          #{ <<"number">> := Number,
+             <<"head">> := #{ <<"ref">> := Ref, <<"sha">> := Sha } }) ->
+    PRDir = filename:join(Target,integer_to_list(Number)),
+    Runs = ghapi(["gh api --paginate -X GET /repos/"++Repo++"/actions/runs -f event=pull_request -f 'branch=",Ref,"'"]),
+    case lists:search(
+           fun(#{ <<"head_sha">> := HeadSha, <<"status">> := Status }) ->
+                   string:equal(HeadSha, Sha) andalso string:equal(Status, <<"completed">>)
+           end, maps:get(<<"workflow_runs">>, Runs)) of
+        {value, Run} ->
+            Ident = integer_to_list(maps:get(<<"id">>,Run)),
+            io:format("Checking for ~ts~n", [filename:join(PRDir, Ident)]),
+            case file:read_file_info(filename:join(PRDir, Ident)) of
+                {error, enoent} ->
+                    cmd("rm -rf "++PRDir),
+                    ok = file:make_dir(PRDir),
+                    ok = file:write_file(filename:join(PRDir,Ident), integer_to_list(Number)),
+
+                    #{ <<"artifacts">> := Artifacts } =
+                        ghapi(["gh api --paginate -X GET ",maps:get(<<"artifacts_url">>, Run)]),
+
+                    lists:foreach(
+                      fun(#{ <<"name">> := <<"test_results">>, <<"archive_download_url">> := Url }) ->
+                              cmd(["gh api ", unicode:characters_to_list(Url), " > /tmp/test_results.zip"]),
+                              cmd("unzip -d /tmp/test_results /tmp/test_results.zip"),
+                              cmd(["tar xvzf /tmp/test_results/test_results.tar.gz "
+                                   "-C ",PRDir," make_test_dir/ct_logs --strip-components=1"]),
+                              cmd("rm -rf /tmp/test_results*");
+                         (#{ <<"name">> := <<"otp_doc_html">>, <<"archive_download_url">> := Url }) ->
+                              cmd(["gh api ", unicode:characters_to_list(Url), " > /tmp/otp_doc_html.zip"]),
+                              cmd("unzip -d /tmp/otp_doc_html /tmp/otp_doc_html.zip"),
+                              cmd(["tar xvzf /tmp/otp_doc_html/otp_doc_html.tar.gz -C ",PRDir]),
+                              cmd(["find ",PRDir," -name '*.pdf' -exec rm -f {} \\;"]),
+                              cmd("rm -rf /tmp/otp_doc_html*");
+                         (_) ->
+                              ok
+                      end, Artifacts),
+                    CTLogsIndex = filename:join([PRDir,"ct_logs","index.html"]),
+                    case file:read_file_info(CTLogsIndex) of
+                        {ok, _} -> ok;
+                        _ ->
+                             ok = filelib:ensure_dir(CTLogsIndex),
+                             ok = file:write_file(CTLogsIndex, ["No test logs found for ", Sha])
+                    end,
+                    DocIndex = filename:join([PRDir,"doc","index.html"]),
+                    case file:read_file_info(DocIndex) of
+                        {ok, _} -> ok;
+                        _ -> ok = filelib:ensure_dir(DocIndex),
+                             ok = file:write_file(DocIndex, ["No documentation found for ", Sha])
+                    end;
+                {ok,_} ->
+                    ok
+            end;
+        false ->
+            ok
+    end.
+
+ghapi(CMD) ->
+    Data = cmd(CMD),
+    try jsx:decode(Data,[{return_maps, true}])
+    catch E:R:ST ->
+            io:format("Failed to decode: ~ts",[Data]),
+            erlang:raise(E,R,ST)
+    end.
+
+cmd(CMD) ->
+    ListCmd = unicode:characters_to_list(CMD),
+    io:format("cmd: ~ts~n",[ListCmd]),
+    unicode:characters_to_binary(os:cmd(ListCmd)).
diff --git a/.github/workflows/pr-comment.yaml b/.github/workflows/pr-comment.yaml
new file mode 100644
index 0000000000..fd8b72cf2f
--- /dev/null
+++ b/.github/workflows/pr-comment.yaml
@@ -0,0 +1,131 @@
+name: Update PR details
+
+# read-write repo token
+# access to secrets
+on:
+  workflow_run:
+    workflows: ["Build and check Erlang/OTP"]
+    types:
+      - requested
+      - completed
+
+# Limit concurrency so that we don't get any races between parallel actions
+concurrency: pr-comment
+
+jobs:
+  pr-number:
+    runs-on: ubuntu-latest
+    if: github.repository == 'erlang/otp'
+    outputs:
+      result: ${{ steps.pr-number.outputs.result }}
+    steps:
+      - uses: actions/github-script@v5
+        id: pr-number
+        with:
+            result-encoding: string
+            script: |
+                for (const workflow_pr of context.payload.workflow_run.pull_requests) {
+                  let pr = await github.request("GET " + workflow_pr.url);
+
+                  console.log(`PR: ${JSON.stringify(pr,null,2)}`);
+
+                  if (pr.data.base.repo.name == context.repo.repo &&
+                    pr.data.base.repo.owner.login == context.repo.owner) {
+                    return `${workflow_pr.number}`;
+                  }
+                }
+                return '';
+
+  starting-tests:
+    runs-on: ubuntu-latest
+    needs: pr-number
+    if: github.event.action == 'requested' && needs.pr-number.outputs.result != ''
+    steps:
+      - uses: actions/checkout@v2
+      ## We create an initial comment with some useful help to the user
+      - uses: actions/github-script@v5
+        with:
+          script: |
+            const script = require('./.github/scripts/pr-comment.js');
+            return await script({github, context, state: 'starting',
+                   pr_number: ${{ needs.pr-number.outputs.result }} });
+
+  finished-tests:
+    runs-on: ubuntu-latest
+    needs: pr-number
+    ## Limit concurrency so that only one job deploys to erlang.github.io
+    concurrency: erlang.github.io-deploy
+    if: >-
+          github.event.action == 'completed' &&
+          needs.pr-number.outputs.result != '' &&
+          github.event.workflow_run.conclusion != 'skipped'
+    steps:
+      - uses: actions/checkout@v2
+      - name: Download and Extract Artifacts
+        id: extract
+        env:
+          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+        run: |
+           mkdir -p artifacts && cd artifacts
+
+           artifacts_url=${{ github.event.workflow_run.artifacts_url }}
+
+           gh api "$artifacts_url" --paginate -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact
+           do
+             IFS=$'\t' read name url <<< "$artifact"
+             if [ "$name" = "Unit Test Results" ] || [ "$name" = "Event File" ]; then
+               gh api $url > "$name.zip"
+               unzip -d "$name" "$name.zip"
+             fi
+           done
+
+           if [ -d "Unit Test Results" ]; then
+             echo "::set-output name=HAS_TEST_ARTIFACTS::true"
+           else
+             echo "::set-output name=HAS_TEST_ARTIFACTS::false"
+           fi
+
+      - uses: actions/checkout@v2
+        with:
+          token: ${{ secrets.ERLANG_TOKEN }}
+          repository: 'erlang/erlang.github.io'
+          path: erlang.github.io
+
+      - name: Upload PR to github pages
+        env:
+          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+        run: |
+           git clone https://github.com/talentdeficit/jsx
+           (cd jsx && rebar3 compile)
+           mkdir -p "${GITHUB_WORKSPACE}/erlang.github.io/prs/"
+           .github/scripts/sync-github-prs.es erlang/otp \
+             "${GITHUB_WORKSPACE}/erlang.github.io/prs/" \
+             "${{ needs.pr-number.outputs.result }}"
+
+      - name: Deploy to github pages 🚀
+        uses: JamesIves/github-pages-deploy-action@v4.2.2
+        with:
+          token: ${{ secrets.ERLANG_TOKEN }}
+          branch: master # The branch the action should deploy to.
+          folder: erlang.github.io # The folder the action should deploy.
+          repository-name: erlang/erlang.github.io
+          single-commit: true
+
+      - name: Publish CT Test Results
+        uses: EnricoMi/publish-unit-test-result-action@v1
+        if: steps.extract.outputs.HAS_TEST_ARTIFACTS == 'true'
+        with:
+          commit: ${{ github.event.workflow_run.head_sha }}
+          event_file: artifacts/Event File/event.json
+          event_name: ${{ github.event.workflow_run.event }}
+          check_name: "CT Test Results"
+          files: "artifacts/**/*.xml"
+
+        ## Append some usefull links and tips to the test results posted by
+        ## Publish CT Test Results
+      - uses: actions/github-script@v5
+        with:
+          script: |
+            const script = require('./.github/scripts/pr-comment.js');
+            return await script({github, context, state: 'finished',
+                pr_number: ${{ needs.pr-number.outputs.result }} });
diff --git a/.github/workflows/sync-github-releases.yaml b/.github/workflows/sync-github-releases.yaml
index b69b6d5cdc..80d59cd31f 100644
--- a/.github/workflows/sync-github-releases.yaml
+++ b/.github/workflows/sync-github-releases.yaml
@@ -1,6 +1,6 @@
 name: Sync all github releases with erlang.org
 
-## Update the base image every day
+## Sync all github releases + prs every hour
 on:
   workflow_dispatch:
   schedule:
@@ -10,22 +10,9 @@ on:
 ## Build base images to be used by other github workflows
 jobs:
 
-  # Wait for up to a minute for previous runs to complete, abort if not done by then
-  pre-ci:
-    if: github.repository == 'erlang/otp'
-    runs-on: ubuntu-latest
-    timeout-minutes: 1
-    steps:
-      - name: 'Block Concurrent Executions'
-        uses: softprops/turnstyle@v1
-        with:
-          poll-interval-seconds: 10
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
   sync-releases:
-    needs: pre-ci
     if: github.repository == 'erlang/otp'
+    concurrency: sync-github-releases
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
@@ -39,3 +26,32 @@ jobs:
         run: >
           .github/scripts/sync-github-releases.sh ${{ github.repository }}
           "Bearer ${{ secrets.GITHUB_TOKEN }}" "^[2-9][1-9]\\..*" 25m
+
+  sync-prs:
+    if: github.repository == 'erlang/otp'
+    concurrency: erlang.github.io-deploy
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          token: ${{ secrets.ERLANG_TOKEN }}
+          repository: 'erlang/erlang.github.io'
+          path: erlang.github.io
+      - uses: actions/checkout@v2
+      - name: Update PRs
+        env:
+          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+        run: |
+            git clone https://github.com/talentdeficit/jsx
+            (cd jsx && rebar3 compile)
+            rm -rf "${GITHUB_WORKSPACE}/erlang.github.io/.git"
+            mkdir -p "${GITHUB_WORKSPACE}/erlang.github.io/prs/"
+            .github/scripts/sync-github-prs.es erlang/otp "${GITHUB_WORKSPACE}/erlang.github.io/prs/"
+      - name: Deploy to github pages 🚀
+        uses: JamesIves/github-pages-deploy-action@v4.2.2
+        with:
+          token: ${{ secrets.ERLANG_TOKEN }}
+          branch: master # The branch the action should deploy to.
+          folder: erlang.github.io # The folder the action should deploy.
+          repository-name: erlang/erlang.github.io
+          single-commit: true
diff --git a/.github/workflows/update-base.yaml b/.github/workflows/update-base.yaml
index 6ded954c2a..0cd0be8102 100644
--- a/.github/workflows/update-base.yaml
+++ b/.github/workflows/update-base.yaml
@@ -37,6 +37,8 @@ jobs:
           docker build --pull --tag ${{ steps.base.outputs.BASE_TAG }} \
             --cache-from ${{ steps.base.outputs.BASE_TAG }} \
             --file .github/dockerfiles/Dockerfile.${{ steps.base.outputs.BASE_TYPE }} \
-            --build-arg BASE=${{ steps.base.outputs.BASE }} .
+            --build-arg USER=otptest --build-arg uid=$(id -u) \
+            --build-arg GROUP=uucp --build-arg gid=$(id -g uucp) \
+            --build-arg BASE=${{ steps.base.outputs.BASE }} .github
       - name: Push base image
         run: docker push ${{ steps.base.outputs.BASE_TAG }}
diff --git a/HOWTO/DEVELOPMENT.md b/HOWTO/DEVELOPMENT.md
index c4534d2c79..0124a17f5d 100644
--- a/HOWTO/DEVELOPMENT.md
+++ b/HOWTO/DEVELOPMENT.md
@@ -38,6 +38,9 @@ with.
     5. [Static analysis](#static-analysis)
 5. [Running test cases](#running-test-cases)
 6. [Writing and building documentation](#writing-and-building-documentation)
+7. [Github Actions](#github-actions)
+    1. [Debugging github actions failures](#debugging-github-actions-failures)
+8. [Using Docker](#using-docker)
 
 ## Short version
 
@@ -61,6 +64,9 @@ make docs       # Build the docs
 make xmllint    # Run xmllint on the docs
 ```
 
+Then enable [Github Actions](#github-actions) and push the changes to your fork
+of Erlang/OTP to check that you have not missed anything.
+
 ## Preparations
 
 Before you start working you need to clone the Erlang/OTP git repository
@@ -421,3 +427,96 @@ cd lib/stdlib/doc/src && make local_docs DOC_TARGETS=html
 ```
 
 and then view the results at `lib/stdlib/doc/html/index.html`.
+
+## Github Actions
+
+Erlang/OTP uses [Github Actions](https://github.com/features/actions) as a
+preliminary CI to check that nothing fundamental has been broken by the change.
+
+You can enable Github Actions on your own github fork in order to run the tests
+before opening a PR to the main repository.
+
+Github Actions does too many checks to list them all but the primary ones are:
+
+* Build on Ubuntu Linux and Windows
+* Cross build to Debian Linux on powerpc and iOS
+* Build and validate documentation
+* Run dialyzer on all of Erlang/OTP
+* Run the tests of the changed application
+
+Each run generates a bunch of artifacts. The most important ones are:
+
+* `test_results`
+  * An archive containing all the logs from all tests that have been run.
+    Navigate to `make_test_dir/ct_logs/index.html` within the archive to
+    view the Common Test summary of the tests.
+* `otp_win32_installer`
+  * A windows installer with the changes you have made.
+* `otp_doc_html`
+  * The HTML docs with the changes you have made.
+
+### Debugging Github Actions failures
+
+Debugging Github Actions is at best a very time-consuming endevour. So if there
+is an error in the build or tests that you don't easily understand I would
+recommend that you try to reproduce it locally.
+
+This is of course not always possible, for instance if it only fails on Windows
+and you do not have access to a Windows machine, but it may the worth it as the
+leadtime of re-running a test is roughly 30 minutes. See the [other sections of
+this guide](#developing-erlang-otp) for details on how to build and run tests
+locally.
+
+If testcases fail when running Github Actions, it is best to start by inspecting
+the logs of the test runs. The logs are attached to the finished run as
+`test_results`. You will find more details about why a testcase failed in
+the logs.
+
+## Using Docker
+
+In order to get a reproduceable environment for building and testing you can use
+[docker](https//www.docker.com). If you are not familiar with how to use it I
+would recommend [reading up a bit](https://www.docker.com/get-started) and trying
+some simple examples yourself before using it to build and test Erlang/OTP.
+
+There is a pre-built ubuntu base image available on github, but you can also
+build it locally if you want to.
+
+Using the pre-built base you build an image like this:
+
+```bash
+docker login ghcr.io
+git archive --prefix otp/ -o .github/otp.tar.gz HEAD
+docker build -t my_otp_image -f .github/dockerfiles/Dockerfile.64-bit .github/
+```
+
+This will fetch the ubuntu base image and build a 64-bit Erlang/OTP. You need to
+[login to the github container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry)
+in order to fetch the base image. If you want to build the base image locally
+you can do that like this:
+
+```bash
+docker built -t docker.pkg.github.com/erlang/otp/ubuntu-base \
+  --build-arg BASE=ubuntu --build-arg USER=otptest --build-arg uid=$(id -u) \
+  --build-arg GROUP=uucp --build-arg gid=$(id -g) \
+  -f .github/dockerfiles/Dockerfile.ubuntu-base .github/
+```
+
+Which approach is fastest depends on the speed of your internet connection.
+
+When you have built the docker image you can run tests in it like this:
+
+```bash
+docker run my_otp_image "make stdlib_test"
+```
+
+or if you want to persist the test results outside the container:
+
+```bash
+mkdir -m 777 make_test_dir   ## The 777 mode is needed to avoid permission problems
+docker run --init -v $PWD/make_test_dir:/buildroot/otp/lib/stdlib/make_test_dir \
+  my_otp_image "make stdlib_test"
+```
+
+The Common Test logs will be placed in `make_test_dir/ct_logs`.
+
diff --git a/scripts/run-smoke-tests b/scripts/run-smoke-tests
deleted file mode 100755
index 82231a2b81..0000000000
--- a/scripts/run-smoke-tests
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-set -ev
-
-if [ -d $ERL_TOP/release/tests/test_server ]; then
-    cd $ERL_TOP/release/tests/test_server
-elif [ -d test_server ]; then
-    cd test_server
-else
-    echo "Could not find tests"
-    exit 1;
-fi
-
-erl -noshell -s ts install -s ts smoke_test batch -s init stop
-
-if grep -q '=failed *[1-9]' ct_run.test_server@*/*/run.*/suite.log; then
-    echo "One or more tests failed."
-    exit 1
-fi
-
-rm -rf ct_run.test_server@*
-- 
2.31.1

openSUSE Build Service is sponsored by