Skip to content

test(parity): run setup_cmd through the lane's CLI binary, not a hard… #75

test(parity): run setup_cmd through the lane's CLI binary, not a hard…

test(parity): run setup_cmd through the lane's CLI binary, not a hard… #75

Workflow file for this run

name: Go CLI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch: {}
schedule:
# Daily 06:00 UTC: the full runtime parity matrix + publish parity. On
# push/PR only the cases affected by the change run (see parity-runtime).
- cron: "0 6 * * *"
jobs:
lint-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
code-quality: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: task lint
- run: task spec:compliance
- run: task vuln
- run: task test:race
- run: task coverage
- name: Coverage summary
if: always()
run: bash scripts/coverage-report.sh coverage.out "unit (hermetic)" >> "$GITHUB_STEP_SUMMARY"
- run: task test:integration
- run: task build
- run: ./devcontainer --version
# Convert the Go coverage profile (coverage.out) to Cobertura XML for
# GitHub's native Code Quality feature.
- name: Convert coverage to Cobertura XML
run: |
go install github.com/boumenot/gocover-cobertura@latest
gocover-cobertura < coverage.out > coverage.xml
- name: Upload coverage report
uses: actions/upload-code-coverage@v1
with:
file: coverage.xml
language: Go
label: code-coverage/go
# Binary covdata for the cross-lane merge (coverage-report job).
- uses: actions/upload-artifact@v7
if: always()
with:
name: covdata-unit
path: artifacts/coverage/data/unit
if-no-files-found: error
e2e:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: task coverage:e2e
- name: Coverage summary
if: always()
run: bash scripts/coverage-report.sh artifacts/coverage/data/e2e "e2e" >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v7
if: always()
with:
name: covdata-e2e
path: artifacts/coverage/data/e2e
if-no-files-found: error
- if: always()
run: task clean
# Hermetic parity gate: read-configuration golden comparison + the contract
# lane of the parity matrix (flag validation / output contract, no Docker).
parity:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: actions/setup-node@v6
with:
node-version: "20"
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: task reference
- run: task parity:contract
- run: task parity:network
# Coverage is measured separately with an instrumented Go binary. Keep the
# functional parity run above release-like, and avoid conflating subprocess
# coverage with the hermetic unit profile.
- run: task coverage:parity-contract
- name: Coverage summary
if: always()
run: bash scripts/coverage-report.sh artifacts/coverage/data/contract "parity:contract" >> "$GITHUB_STEP_SUMMARY"
- if: always()
run: |
mkdir -p artifacts
git -C reference rev-parse HEAD > artifacts/reference-commit.txt
- uses: actions/upload-artifact@v7
if: always()
with:
name: parity-contract-network-v0.88.0
path: artifacts/
if-no-files-found: error
- uses: actions/upload-artifact@v7
if: always()
with:
name: covdata-contract
path: artifacts/coverage/data/contract
if-no-files-found: error
# Runtime lane of the parity matrix (creates real containers via Docker) on
# push/PR, SCOPED to the commands affected by the change: the full matrix takes
# ~45 min, so scripts/parity-affected.sh maps the diff to a
# PARITY_COMMAND allowlist (or "all"/"none"). The daily parity-runtime-full job
# is the backstop that always runs the whole thing.
parity-runtime-shard:
if: github.event_name == 'push' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
needs: lint-and-test
strategy:
# Split the affected runtime cases across independent runners (each with its
# own docker daemon). fail-fast off so one shard's failure still reports the
# others. The harness selects this shard's slice via PARITY_SHARD_INDEX.
fail-fast: false
matrix:
shard: [0, 1]
env:
PARITY_RUNTIME_TIMEOUT: "10m"
# Two shards, each running two cases at a time: the per-case isolation
# (unique id-labels / COMPOSE_PROJECT_NAME / BUILDX_BUILDER) makes -parallel 2
# safe on a 2-core runner now that each runner only owns half the cases.
PARITY_PARALLEL: "2"
PARITY_SHARD_INDEX: ${{ matrix.shard }}
PARITY_SHARD_TOTAL: "2"
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
# Full history so the diff base (PR base / previous push) is present.
fetch-depth: 0
- name: Determine affected parity commands
id: affected
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"; head="${{ github.event.pull_request.head.sha }}"
else
base="${{ github.event.before }}"; head="${{ github.sha }}"
fi
bash scripts/parity-affected-commands.sh "$base" "$head" >> "$GITHUB_OUTPUT"
# Toolchain + runner prep + reference oracle, shared with parity-runtime-full.
- if: steps.affected.outputs.run == 'true'
uses: ./.github/actions/setup-parity-runtime
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- if: steps.affected.outputs.run == 'true'
env:
PARITY_COMMAND: ${{ steps.affected.outputs.commands }}
run: task parity:runtime
- if: always() && steps.affected.outputs.run == 'true'
run: |
mkdir -p artifacts
git -C reference rev-parse HEAD > artifacts/reference-commit.txt
- uses: actions/upload-artifact@v7
if: always() && steps.affected.outputs.run == 'true'
with:
name: parity-runtime-v0.88.0-shard-${{ matrix.shard }}
path: artifacts/
if-no-files-found: error
- if: always() && steps.affected.outputs.run == 'true'
run: task clean
# Stable required-check name aggregating the sharded runtime lane, so sharding
# does not change the check name branch protection depends on.
parity-runtime:
if: always() && (github.event_name == 'push' || github.event_name == 'pull_request')
runs-on: ubuntu-latest
needs: parity-runtime-shard
steps:
- name: Gate on all runtime shards
run: |
result="${{ needs.parity-runtime-shard.result }}"
echo "parity-runtime-shard aggregate result: $result"
case "$result" in
success|skipped) exit 0 ;;
*) echo "one or more runtime shards failed"; exit 1 ;;
esac
# Gate the expensive daily lanes on "did the repo change today?". A manual dispatch
# always runs; a scheduled run only proceeds if HEAD has a commit within the last
# day (scripts/daily-changed.sh). On a private/billed repo this skips the full
# runtime matrix + publish on quiet days. Runs on schedule/dispatch so it is never
# skipped for those events — the heavy jobs `needs:` it, and a skipped need would
# skip them regardless of its output.
daily-changes:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
run: ${{ steps.check.outputs.run }}
steps:
- uses: actions/checkout@v7
- id: check
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "manual dispatch → always run" >&2
echo "run=true" >> "$GITHUB_OUTPUT"
else
bash scripts/daily-changed.sh >> "$GITHUB_OUTPUT"
fi
# Full runtime parity matrix. Runs once a day (schedule, only when the repo changed
# — see daily-changes) and on manual dispatch — the exhaustive backstop for the
# scoped push/PR job above. Sharded across independent runners (same
# PARITY_SHARD_INDEX/TOTAL harness as the push/PR lane) so the 175-case matrix no
# longer contends for one runner's CPU/disk/daemon, which was starving the
# container setups and failing downstream exec cases. PARITY_SHARD_TOTAL MUST equal
# the length of matrix.shard — parity_sharding_workflow_test.go enforces that, so a
# mismatch can't silently drop cases.
parity-runtime-full:
needs: [lint-and-test, daily-changes]
if: needs.daily-changes.outputs.run == 'true'
runs-on: ubuntu-latest
strategy:
# fail-fast off so one shard's failure still reports the others.
fail-fast: false
matrix:
shard: [0, 1, 2, 3]
env:
PARITY_RUNTIME_TIMEOUT: "10m"
# Each shard owns ~a quarter of the matrix, so -parallel 2 per runner matches
# the push/PR shard lane's proven-safe load instead of the old single-runner 3.
PARITY_PARALLEL: "2"
PARITY_SHARD_INDEX: ${{ matrix.shard }}
PARITY_SHARD_TOTAL: "4"
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: ./.github/actions/setup-parity-runtime
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Instrumented binary: measures coverage AND gates parity on this shard's slice.
- run: task coverage:parity-runtime
- name: Coverage summary
if: always()
run: bash scripts/coverage-report.sh artifacts/coverage/data/runtime "parity:runtime shard ${{ matrix.shard }}" >> "$GITHUB_STEP_SUMMARY"
- if: always()
run: |
mkdir -p artifacts
git -C reference rev-parse HEAD > artifacts/reference-commit.txt
- uses: actions/upload-artifact@v7
if: always()
with:
name: parity-runtime-full-v0.88.0-shard-${{ matrix.shard }}
path: artifacts/
if-no-files-found: error
# covdata slice for the cross-lane merge (distinct name per shard so the merge
# job unions them instead of clobbering).
- uses: actions/upload-artifact@v7
if: always()
with:
name: covdata-runtime-shard-${{ matrix.shard }}
path: artifacts/coverage/data/runtime
if-no-files-found: error
- if: always()
run: task clean
# Publish parity (features/templates → ephemeral OCI registry). Independent of the
# runtime matrix, so it runs as its own job rather than stealing a runtime shard's
# runner. Was folded into the old monolithic parity-runtime-full.
parity-publish-full:
needs: [lint-and-test, daily-changes]
if: needs.daily-changes.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: ./.github/actions/setup-parity-runtime
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: task coverage:parity-publish
- name: Coverage summary
if: always()
run: bash scripts/coverage-report.sh artifacts/coverage/data/publish "parity:publish" >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v7
if: always()
with:
name: covdata-publish
path: artifacts/coverage/data/publish
if-no-files-found: error
- if: always()
run: task clean
# Daily gate + cross-lane coverage. Aggregates the sharded runtime lane and the
# publish lane into one pass/fail (mirrors the push/PR parity-runtime gate) and
# merges every lane's covdata — unit + e2e + contract (which also run on schedule)
# plus the runtime shards + publish — into the one true cross-lane number.
parity-runtime-full-report:
if: always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
needs: [lint-and-test, e2e, parity, parity-runtime-full, parity-publish-full]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download all lane covdata
uses: actions/download-artifact@v8
with:
pattern: covdata-*
path: artifacts/coverage/data
# download-artifact nests each artifact under covdata-<lane>/; flatten to <lane>/
# so coverage:merge sees one dir per lane (the runtime shards stay distinct).
- name: Normalize lane dirs
run: |
cd artifacts/coverage/data
for d in covdata-*; do [ -d "$d" ] && mv "$d" "${d#covdata-}"; done
ls -R . || true
- name: Merged coverage report
run: task coverage:merge | tee -a "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v7
with:
name: covdata-merged-full
path: artifacts/coverage/data/merged.out
if-no-files-found: warn
# Final verdict last, so the coverage report is posted even when a shard failed.
- name: Gate on all runtime shards + publish
run: |
runtime="${{ needs.parity-runtime-full.result }}"
publish="${{ needs.parity-publish-full.result }}"
echo "runtime shards: $runtime, publish: $publish"
for r in "$runtime" "$publish"; do
case "$r" in
success|skipped) ;;
*) echo "runtime/publish parity failed"; exit 1 ;;
esac
done
# Experimental, NON-gating: arm64 runtime via QEMU emulation. arm64 runtime is
# unsupported for now, so its cases are skipped-arm64 in the gate above. This job
# is SLOW (QEMU) and only runs on a manual trigger (workflow_dispatch), not on
# every push/PR — enable it when validating arm64.
parity-runtime-arm64-experimental:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
needs: lint-and-test
continue-on-error: true
env:
PARITY_ARM64: "true"
PARITY_RUNTIME_TIMEOUT: "15m"
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Set up QEMU (arm64 emulation)
uses: docker/setup-qemu-action@v4
with:
platforms: arm64
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: actions/setup-node@v6
with:
node-version: "20"
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: task ci:prepare-runner
- run: task reference
- run: task parity:runtime
- if: always()
run: task clean
# Cross-lane coverage: merge the per-PR lanes' covdata (unit + e2e + contract)
# into the one number no single lane shows, and post it to the run summary.
coverage-report:
if: always() && (github.event_name == 'push' || github.event_name == 'pull_request')
runs-on: ubuntu-latest
needs: [lint-and-test, e2e, parity]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download lane covdata
uses: actions/download-artifact@v8
with:
pattern: covdata-*
path: artifacts/coverage/data
# download-artifact nests each artifact under covdata-<lane>/; flatten to <lane>/.
- name: Normalize lane dirs
run: |
cd artifacts/coverage/data
for d in covdata-*; do [ -d "$d" ] && mv "$d" "${d#covdata-}"; done
ls -R . || true
- name: Merged coverage report
run: task coverage:merge | tee -a "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v7
with:
name: covdata-merged
path: artifacts/coverage/data/merged.out
if-no-files-found: warn
cross-compile:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: go-task/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: task build:cross
- run: ls -lh dist/