mirror of
https://github.com/mattermost/mattermost.git
synced 2026-11-03 20:56:57 -06:00
* chore: extract install-<tool> Makefile targets
Pull each `go install` of a build tool into its own `install-<tool>`
target so the installs are declared once and reused as prerequisites,
and add `install-ci-tools` plus a `go-cache` target that pre-warms the
Go module and build caches.
Also collapse the duplicate `vet-api` target declarations into one.
* chore(ci): add restore-go-cache composite action
* chore(ci): mount warmed Go cache into run-in-buildenv
* chore(ci): add daily Go module/build cache warm workflow
* chore(ci): restore warmed Go cache read-only across server CI jobs
* Simplify Go cache key to glob-match all go.mod and go.sum files
* make GOBIN explicit
* Pass Go cache paths as step outputs, not GITHUB_ENV
restore-go-cache wrote GOMODCACHE/GOCACHE/GOBIN to GITHUB_ENV so later
steps would pick them up. zizmor flags any GITHUB_ENV write as a possible
code-execution vector, since an attacker-controlled value there can set
LD_PRELOAD or NODE_OPTIONS for every subsequent step.
Nothing untrusted reaches these particular values, but the write is also
unnecessary: expose them as action outputs and map them into the one step
that actually needs them, matching what 8f6d17d875 did for the buildenv
actions.
Only server-test-template's shard split consumes them outside the action.
Everything else either runs in the buildenv container, which gets the
container paths via explicit -e, or reads RUNNER_TEMP directly. That step
runs `go work` and shard-split.js's `go test -list` on the host, so it
takes GOMODCACHE and GOCACHE via env; GOBIN is omitted because the host
never invokes the tools in server/bin.
Also drops the `${{ github.workspace }}` interpolation in favour of the
runner's GITHUB_WORKSPACE, so no expression is expanded into the script.
zizmor now reports no findings for the action, with no ignore comment or
config entry needed.
* Note that server/bin is only a warm start
The cache key covers the Go version and go.mod/go.sum, so it doesn't
invalidate on a tools/mattermost-govet source edit or a Makefile version
pin bump. That reads like a staleness bug until you notice every consumer
re-runs its install-<tool> prerequisite, and `go install` rebuilds on
content, so a stale binary is overwritten before anything uses it.
Say so where the key is described, since it came up in review.
306 lines
12 KiB
YAML
306 lines
12 KiB
YAML
name: Server Test Template
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
name:
|
|
required: true
|
|
type: string
|
|
datasource:
|
|
required: true
|
|
type: string
|
|
drivername:
|
|
required: true
|
|
type: string
|
|
logsartifact:
|
|
required: true
|
|
type: string
|
|
fullyparallel:
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
allow-failure:
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
enablecoverage:
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
build-server-tag:
|
|
description: "Build image tag (e.g. go1.26.3-node24.11.1)"
|
|
required: true
|
|
type: string
|
|
fips-enabled:
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
elasticsearch-version:
|
|
required: false
|
|
type: string
|
|
default: "9.0.0"
|
|
opensearch-version:
|
|
required: false
|
|
type: string
|
|
default: "3.0.0"
|
|
test-target:
|
|
required: false
|
|
type: string
|
|
default: "test-server"
|
|
# -- Test sharding inputs (leave defaults for non-sharded callers) --
|
|
shard-index:
|
|
required: false
|
|
type: number
|
|
default: -1 # -1 = no sharding; run all tests
|
|
shard-total:
|
|
required: false
|
|
type: number
|
|
default: 1
|
|
runner:
|
|
description: "GitHub-hosted runner label (default: ubuntu-latest-8-cores)"
|
|
required: false
|
|
type: string
|
|
default: "ubuntu-latest-8-cores"
|
|
race-enabled:
|
|
description: "Run tests with Go race detector (-race)"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
secrets:
|
|
# Only if enablecoverage is true
|
|
CODECOV_TOKEN:
|
|
required: false
|
|
# Only if fips-enabled is true
|
|
DOCKERHUB_USERNAME:
|
|
required: false
|
|
DOCKERHUB_TOKEN:
|
|
required: false
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read # for load-buildenv artifact listing
|
|
|
|
jobs:
|
|
test:
|
|
name: ${{ inputs.name }}
|
|
runs-on: ${{ inputs.runner }}
|
|
continue-on-error: ${{ inputs.allow-failure }} # Used to avoid blocking PRs in case of flakiness
|
|
env:
|
|
COMPOSE_PROJECT_NAME: ghactions
|
|
INPUT_DATASOURCE: ${{ inputs.datasource }}
|
|
INPUT_DRIVERNAME: ${{ inputs.drivername }}
|
|
INPUT_ENABLECOVERAGE: ${{ inputs.enablecoverage }}
|
|
INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }}
|
|
INPUT_FULLYPARALLEL: ${{ inputs.fullyparallel }}
|
|
INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }}
|
|
INPUT_NAME: ${{ inputs.name }}
|
|
INPUT_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
INPUT_RACE_ENABLED: ${{ inputs.race-enabled }}
|
|
INPUT_SHARD_TOTAL: ${{ inputs.shard-total }}
|
|
INPUT_TEST_TARGET: ${{ inputs.test-target }}
|
|
steps:
|
|
- name: buildenv/docker-login
|
|
# Only FIPS requires login for private build container. (Forks won't have credentials.)
|
|
if: inputs.fips-enabled
|
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Checkout mattermost project
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- name: buildenv/load
|
|
id: buildenv
|
|
uses: ./.github/actions/load-buildenv
|
|
with:
|
|
build-server-tag: ${{ inputs.build-server-tag }}
|
|
fips-enabled: ${{ inputs.fips-enabled }}
|
|
|
|
- name: Restore Go cache
|
|
id: go-cache
|
|
uses: ./.github/actions/restore-go-cache
|
|
|
|
- name: Restore test timing data
|
|
if: inputs.shard-total > 1
|
|
id: timing-cache
|
|
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
with:
|
|
path: |
|
|
server/prev-report.xml
|
|
server/prev-gotestsum.json
|
|
# Always restore from master — timing is only saved on the default
|
|
# branch and is stable enough for shard balancing.
|
|
# NOTE: the v2 prefix invalidates pre-existing caches that were
|
|
# poisoned by shard failures (a killed shard loses its gotestsum.json,
|
|
# so the merged report was missing those packages' timings; on the
|
|
# next run they all defaulted to 1ms and bin-packed onto the lightest
|
|
# shard, overloading it and perpetuating the cycle). See also the
|
|
# all-shards-passed guard in server-test-merge-template.yml.
|
|
key: server-test-timing-v2-master
|
|
restore-keys: |
|
|
server-test-timing-v2-
|
|
|
|
- name: Setup log artifact name
|
|
id: log-artifact
|
|
run: |
|
|
if [[ "$INPUT_FIPS_ENABLED" == 'true' ]]; then
|
|
echo "LOG_ARTIFACT_NAME=${INPUT_LOGSARTIFACT}-fips" >> "${GITHUB_OUTPUT}"
|
|
else
|
|
echo "LOG_ARTIFACT_NAME=${INPUT_LOGSARTIFACT}" >> "${GITHUB_OUTPUT}"
|
|
fi
|
|
|
|
- name: Store required variables for publishing results
|
|
run: |
|
|
echo "$INPUT_NAME" > server/test-name
|
|
echo "$INPUT_PR_NUMBER" > server/pr-number
|
|
|
|
- name: Run docker compose
|
|
env:
|
|
ELASTICSEARCH_VERSION: ${{ inputs.elasticsearch-version }}
|
|
OPENSEARCH_VERSION: ${{ inputs.opensearch-version }}
|
|
POSTGRES_PASSWORD: 'mostest_password'
|
|
run: |
|
|
cd server/build
|
|
docker compose --ansi never run --rm start_dependencies
|
|
cat ../tests/custom-schema-objectID.ldif | docker compose --ansi never exec -T openldap bash -c 'ldapadd -Y EXTERNAL -H ldapi:/// -w mostest_password || true';
|
|
cat ../tests/custom-schema-cpa.ldif | docker compose --ansi never exec -T openldap bash -c 'ldapadd -Y EXTERNAL -H ldapi:/// -w mostest_password || true';
|
|
cat ../tests/test-data.ldif | docker compose --ansi never exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest_password';
|
|
docker compose --ansi never exec -T minio sh -c 'mkdir -p /data/mattermost-test';
|
|
docker compose --ansi never ps
|
|
|
|
# ── Test-level sharding ────────────────────────────────────────────
|
|
# When shard-total > 1, we split tests across N parallel runners.
|
|
#
|
|
# Two-tier splitting strategy:
|
|
# - "Light" packages (< 5 min): assigned whole to a shard
|
|
# - "Heavy" packages (≥ 5 min, e.g. api4, app): individual tests
|
|
# are distributed across shards using -run regex filters
|
|
#
|
|
# See server/scripts/shard-split.js for the full algorithm.
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
- name: Setup Go for test discovery
|
|
if: inputs.shard-total > 1
|
|
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
|
with:
|
|
go-version-file: server/.go-version
|
|
|
|
- name: Split tests across shards
|
|
if: inputs.shard-total > 1
|
|
id: test_split
|
|
working-directory: server
|
|
env:
|
|
SHARD_INDEX: ${{ inputs.shard-index }}
|
|
SHARD_TOTAL: ${{ inputs.shard-total }}
|
|
# `go work` and shard-split.js's `go test -list` run on the host, so
|
|
# they need the restored caches that the container gets via -e.
|
|
GOMODCACHE: ${{ steps.go-cache.outputs.gomodcache }}
|
|
GOCACHE: ${{ steps.go-cache.outputs.gocache }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# ── List all test packages ──
|
|
echo "::group::Listing test packages"
|
|
TE_PKGS=$(find ./public/ ./ -name '*_test.go' -not -path './enterprise/*' -not -path './cmd/mmctl/*' 2>/dev/null \
|
|
| sed 's|/[^/]*$||' | sort -u \
|
|
| sed 's|^\./|github.com/mattermost/mattermost/server/v8/|' \
|
|
| sed 's|github.com/mattermost/mattermost/server/v8/public/|github.com/mattermost/mattermost/server/public/|')
|
|
EE_PKGS=$(find ./enterprise/ -name '*_test.go' 2>/dev/null \
|
|
| sed 's|/[^/]*$||' | sort -u \
|
|
| sed 's|^\./|github.com/mattermost/mattermost/server/v8/|')
|
|
ALL_PKGS=$(printf '%s\n%s' "$TE_PKGS" "$EE_PKGS" | grep -v '^$' | sort -u)
|
|
TOTAL_PKGS=$(echo "$ALL_PKGS" | wc -l)
|
|
echo "Found $TOTAL_PKGS test packages"
|
|
echo "::endgroup::"
|
|
|
|
if [[ "$TOTAL_PKGS" -eq 0 ]]; then
|
|
echo "WARNING: No test packages found"
|
|
echo "has_packages=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
echo "$ALL_PKGS" > all-packages.txt
|
|
|
|
# ── Create go.work for test discovery ──
|
|
# Without it, `go test -list` in shard-split.js resolves
|
|
# github.com/mattermost/mattermost/server/public against the published
|
|
# module version from server/go.mod, which lags HEAD and causes build
|
|
# failures when heavy packages reference new symbols in ./public.
|
|
go work init
|
|
go work use . ./public
|
|
|
|
# ── Run shard solver ──
|
|
node scripts/shard-split.js
|
|
|
|
echo "has_packages=true" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Run Tests
|
|
env:
|
|
BUILD_IMAGE: ${{ steps.buildenv.outputs.image }}
|
|
run: |
|
|
RACE_MODE=""
|
|
if [[ "$INPUT_RACE_ENABLED" == "true" ]]; then
|
|
RACE_MODE="-race"
|
|
fi
|
|
|
|
TEST_TARGET="${INPUT_TEST_TARGET}${RACE_MODE}"
|
|
BUILD_NUMBER="${GITHUB_HEAD_REF}-${GITHUB_RUN_ID}"
|
|
DOCKER_CMD="make ${TEST_TARGET}"
|
|
|
|
# When sharding is active, use the multi-run wrapper script.
|
|
# run-shard-tests.sh detects heavy runs itself and falls back to
|
|
# light-only mode when shard-heavy-runs.txt is absent or empty.
|
|
if [[ "$INPUT_SHARD_TOTAL" -gt 1 && -f server/shard-te-packages.txt ]]; then
|
|
cp server/scripts/run-shard-tests.sh server/run-shard-tests.sh
|
|
chmod +x server/run-shard-tests.sh
|
|
DOCKER_CMD="/mattermost/server/run-shard-tests.sh"
|
|
fi
|
|
|
|
docker run --net ghactions_mm-test \
|
|
--ulimit nofile=8096:8096 \
|
|
--env-file=server/build/dotenv/test.env \
|
|
--env MM_SQLSETTINGS_DRIVERNAME="$INPUT_DRIVERNAME" \
|
|
--env MM_SQLSETTINGS_DATASOURCE="$INPUT_DATASOURCE" \
|
|
--env TEST_DATABASE_POSTGRESQL_DSN="$INPUT_DATASOURCE" \
|
|
--env ENABLE_FULLY_PARALLEL_TESTS="$INPUT_FULLYPARALLEL" \
|
|
--env ENABLE_COVERAGE="$INPUT_ENABLECOVERAGE" \
|
|
--env FIPS_ENABLED="$INPUT_FIPS_ENABLED" \
|
|
--env RACE_MODE \
|
|
--env TEST_TARGET \
|
|
--env BUILD_NUMBER \
|
|
--env GOMODCACHE=/gocache/mod \
|
|
--env GOCACHE=/gocache/build \
|
|
--env GOBIN=/mattermost/server/bin \
|
|
-v "${RUNNER_TEMP}/go-cache:/gocache" \
|
|
-v $PWD:/mattermost \
|
|
-w /mattermost/server \
|
|
$BUILD_IMAGE \
|
|
$DOCKER_CMD
|
|
- name: Upload coverage to Codecov
|
|
if: ${{ inputs.enablecoverage }}
|
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
disable_search: true
|
|
files: server/cover.out
|
|
flags: server
|
|
|
|
- name: Stop docker compose
|
|
run: |
|
|
cd server/build
|
|
docker compose --ansi never stop
|
|
|
|
- name: Archive logs
|
|
if: ${{ always() }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: ${{ steps.log-artifact.outputs.LOG_ARTIFACT_NAME }}
|
|
path: |
|
|
server/gotestsum.json
|
|
server/report.xml
|
|
server/cover.out
|
|
server/test-name
|
|
server/pr-number
|
|
|