mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Merge protected Pulse main at batch frontier
Change-source: pulse-maintainer
This commit is contained in:
@@ -267,9 +267,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# internal/api is by far the slowest package under -race (~10m), so it
|
||||
# gets a dedicated shard; every other package splits across the rest.
|
||||
shard: [api, rest-0, rest-1]
|
||||
# internal/api has its own sharded job below; every other package
|
||||
# splits across these two.
|
||||
shard: [rest-0, rest-1]
|
||||
|
||||
steps:
|
||||
- name: Report documentation-only change
|
||||
@@ -304,15 +304,11 @@ jobs:
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
env:
|
||||
PULSE_DATA_DIR: /tmp/pulse-test-data
|
||||
# internal/api alone takes ~22m under -race on a fast machine since
|
||||
# the demo estate scaled to 50 nodes, and CI runners have needed
|
||||
# roughly double the fast-machine time, hence the large per-binary
|
||||
# budget. Remaining packages are split deterministically by list
|
||||
# position.
|
||||
# Packages other than internal/api are split deterministically by
|
||||
# list position.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "${{ matrix.shard }}" in
|
||||
api) pkgs="./internal/api" ;;
|
||||
rest-0) pkgs=$(go list ./... | grep -v '/internal/api$' | awk 'NR % 2 == 0') ;;
|
||||
rest-1) pkgs=$(go list ./... | grep -v '/internal/api$' | awk 'NR % 2 == 1') ;;
|
||||
*) echo "unknown shard ${{ matrix.shard }}" >&2; exit 1 ;;
|
||||
@@ -324,6 +320,113 @@ jobs:
|
||||
printf 'Packages in this shard:\n%s\n' "$pkgs"
|
||||
go test -race -timeout 50m $pkgs
|
||||
|
||||
# internal/api alone took ~27 of the ~31 minutes of the old single
|
||||
# `Backend tests (api)` job under -race, which made it the critical path of
|
||||
# every pull request. Its top-level tests are split across these shards
|
||||
# as contiguous runs of the package's own test order, and the
|
||||
# `Backend tests (api)` job below keeps the required check name by passing
|
||||
# only when every shard passed.
|
||||
backend-api:
|
||||
name: Backend tests (api-${{ matrix.index }})
|
||||
needs: changes
|
||||
# Like the rest shards, expand even for documentation-only changes so the
|
||||
# verdict job sees real shard results rather than skipped jobs.
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Keep API_SHARD_COUNT equal to the number of indexes listed here.
|
||||
index: [0, 1, 2, 3]
|
||||
env:
|
||||
API_SHARD_COUNT: 4
|
||||
|
||||
steps:
|
||||
- name: Report documentation-only change
|
||||
if: needs.changes.outputs.code != 'true'
|
||||
run: echo "No backend code changed. Tests are not required for this shard."
|
||||
|
||||
- name: Checkout repository
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Go
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Provide frontend embed stub
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
run: |
|
||||
mkdir -p internal/api/frontend-modern/dist
|
||||
[ -f internal/api/frontend-modern/dist/index.html ] || \
|
||||
printf '<!doctype html><title>ci embed stub</title>\n' > internal/api/frontend-modern/dist/index.html
|
||||
|
||||
- name: Go unit tests (internal/api shard ${{ matrix.index }})
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
env:
|
||||
PULSE_DATA_DIR: /tmp/pulse-test-data
|
||||
API_SHARD_INDEX: ${{ matrix.index }}
|
||||
# The test list is computed from the checked-out commit at run time,
|
||||
# so every shard sees the same list and a test added later lands in
|
||||
# exactly one shard with no workflow edit. The list keeps go test's
|
||||
# own order (file name, then declaration), which is also run order,
|
||||
# and each shard takes one contiguous slice of it. Some internal/api
|
||||
# tests still lean on package state left by the tests just before
|
||||
# them (for example admin bypass or session store globals), and an
|
||||
# interleaved split by name broke dozens of them, while contiguous
|
||||
# slices keep those neighbours together. Listing with -race builds
|
||||
# the same test binary the run then reuses from cache. Benchmarks
|
||||
# are left out because plain go test never runs them.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
listing=$(go test -race -list . ./internal/api)
|
||||
tests=$(printf '%s\n' "$listing" | grep -E '^(Test|Fuzz|Example)[^[:space:]]*$' || true)
|
||||
if [ -z "$tests" ]; then
|
||||
echo "::error::go test -list found no tests in ./internal/api"
|
||||
exit 1
|
||||
fi
|
||||
total=$(printf '%s\n' "$tests" | wc -l | tr -d ' ')
|
||||
selected=$(printf '%s\n' "$tests" | awk -v n="$API_SHARD_COUNT" -v i="$API_SHARD_INDEX" -v total="$total" \
|
||||
'NR > int(total * i / n) && NR <= int(total * (i + 1) / n)')
|
||||
if [ -z "$selected" ]; then
|
||||
echo "::error::internal/api shard ${API_SHARD_INDEX} of ${API_SHARD_COUNT} resolved to an empty test list"
|
||||
exit 1
|
||||
fi
|
||||
pattern="^($(printf '%s\n' "$selected" | paste -sd '|' -))\$"
|
||||
# Linux rejects a single argument over 128 KiB. Fail with a clear
|
||||
# instruction instead of an opaque exec error.
|
||||
if [ "${#pattern}" -gt 120000 ]; then
|
||||
echo "::error::internal/api shard pattern is ${#pattern} bytes; add a shard index and raise API_SHARD_COUNT"
|
||||
exit 1
|
||||
fi
|
||||
echo "internal/api shard ${API_SHARD_INDEX} of ${API_SHARD_COUNT} runs $(printf '%s\n' "$selected" | wc -l | tr -d ' ') of ${total} tests"
|
||||
go test -race -timeout 50m -run "$pattern" ./internal/api
|
||||
|
||||
# Required check name. Branch protection matches `Backend tests (api)`
|
||||
# exactly, so this job must keep that name and must fail unless every
|
||||
# internal/api shard succeeded (a skipped or cancelled shard fails it too).
|
||||
backend-api-verdict:
|
||||
name: Backend tests (api)
|
||||
needs: backend-api
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Require every internal/api shard
|
||||
env:
|
||||
API_SHARDS_RESULT: ${{ needs.backend-api.result }}
|
||||
run: |
|
||||
if [ "$API_SHARDS_RESULT" != success ]; then
|
||||
echo "::error::internal/api shards did not all pass (result: ${API_SHARDS_RESULT})"
|
||||
exit 1
|
||||
fi
|
||||
echo "Every internal/api shard passed."
|
||||
|
||||
scripts-and-build:
|
||||
name: Script smoke tests & backend build
|
||||
needs: changes
|
||||
|
||||
@@ -203,9 +203,12 @@ jobs:
|
||||
# Runs even when the stable tier failed (its data still counts toward
|
||||
# promotion), but not when the stable tier was skipped — that means the
|
||||
# environment never came up and every probation spec would fail on it.
|
||||
# Pull requests skip it. It cannot gate a PR, and the promotion rule in
|
||||
# e2e-tiering.mjs counts only main runs, so on a PR it only held each
|
||||
# of the eight shard runners for about eight more minutes.
|
||||
- name: Run probation-tier E2E suite (non-gating)
|
||||
id: probation
|
||||
if: ${{ !cancelled() && steps.stable.conclusion != 'skipped' }}
|
||||
if: ${{ !cancelled() && github.event_name != 'pull_request' && steps.stable.conclusion != 'skipped' }}
|
||||
continue-on-error: true
|
||||
# A broken probation surface can otherwise consume the entire 60m job
|
||||
# budget through retries and cancel the job before continue-on-error
|
||||
|
||||
Reference in New Issue
Block a user