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: false enablecoverage: required: false type: boolean default: false elasticsearch-version: required: false type: string default: "9.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 permissions: id-token: write contents: read jobs: test: name: ${{ inputs.name }} runs-on: ubuntu-latest-8-cores continue-on-error: ${{ inputs.fullyparallel }} # Used to avoid blocking PRs in case of flakiness env: COMPOSE_PROJECT_NAME: ghactions steps: - name: Checkout mattermost project uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - 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. key: server-test-timing-master restore-keys: | server-test-timing- - name: Calculate Golang Version id: go working-directory: ./server run: echo GO_VERSION=$(cat .go-version) >> "${GITHUB_OUTPUT}" - name: Setup Go uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version: ${{ steps.go.outputs.GO_VERSION }} cache-dependency-path: server/go.sum - name: Store required variables for publishing results run: | echo "${{ inputs.name }}" > server/test-name echo "${{ github.event.pull_request.number }}" > server/pr-number - name: Run docker compose env: ELASTICSEARCH_VERSION: ${{ inputs.elasticsearch-version }} 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 || true'; cat ../tests/custom-schema-cpa.ldif | docker compose --ansi never exec -T openldap bash -c 'ldapadd -Y EXTERNAL -H ldapi:/// -w mostest || 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'; 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: ${{ steps.go.outputs.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 }} 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 # ── Run shard solver ── node scripts/shard-split.js echo "has_packages=true" >> "$GITHUB_OUTPUT" - name: Run Tests env: BUILD_IMAGE: mattermost/mattermost-build-server:${{ steps.go.outputs.GO_VERSION }} run: | if [[ ${{ github.ref_name }} == 'master' && ${{ inputs.fullyparallel }} != true && "${{ inputs.test-target }}" == "test-server" ]]; then export RACE_MODE="-race" fi TEST_TARGET="${{ inputs.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 [[ "${{ inputs.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="${{ inputs.drivername }}" \ --env MM_SQLSETTINGS_DATASOURCE="${{ inputs.datasource }}" \ --env TEST_DATABASE_MYSQL_DSN="${{ inputs.datasource }}" \ --env TEST_DATABASE_POSTGRESQL_DSN="${{ inputs.datasource }}" \ --env ENABLE_FULLY_PARALLEL_TESTS="${{ inputs.fullyparallel }}" \ --env ENABLE_COVERAGE="${{ inputs.enablecoverage }}" \ -v $(go env GOCACHE):/go/cache \ -e GOCACHE=/go/cache \ --env RACE_MODE \ --env TEST_TARGET \ --env BUILD_NUMBER \ -v $PWD:/mattermost \ -w /mattermost/server \ $BUILD_IMAGE \ $DOCKER_CMD - name: Upload coverage to Codecov if: ${{ inputs.enablecoverage }} uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} disable_search: true files: server/cover.out - name: Stop docker compose if: ${{ always() }} run: | cd server/build docker compose --ansi never logs --no-color > ../../docker-compose.log 2>&1 docker compose --ansi never stop - name: Archive logs if: ${{ always() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ inputs.logsartifact }} path: | server/gotestsum.json server/report.xml server/cover.out server/test-name server/pr-number docker-compose.log