mirror of
https://github.com/logos-blockchain/logos-blockchain.git
synced 2026-07-30 11:03:27 +00:00
test: improve ci merge queue integration tests (#3206)
This commit is contained in:
parent
d9e6997254
commit
caa47fe251
165
.github/actions/check-any-lane-result/action.yml
vendored
Normal file
165
.github/actions/check-any-lane-result/action.yml
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
name: Check any lane result
|
||||
description: Pass when any configured matrix lane reports full success
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
description: Token used to inspect jobs in the current workflow run
|
||||
required: true
|
||||
result-name:
|
||||
description: Human-readable suite name used in logs and errors
|
||||
required: true
|
||||
job-name-prefix:
|
||||
description: Matrix job name before the lane label
|
||||
required: true
|
||||
lane-labels-json:
|
||||
description: JSON array containing all acceptable lane labels
|
||||
required: false
|
||||
default: '["self-hosted-linux", "self-hosted-macos"]'
|
||||
success-step-name:
|
||||
description: Step that proves the lane ran fully and passed
|
||||
required: false
|
||||
default: Report success
|
||||
poll-interval-seconds:
|
||||
description: Delay between workflow-job API requests
|
||||
required: false
|
||||
default: "30"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Wait for any lane to pass
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # Version 7
|
||||
env:
|
||||
RESULT_NAME: ${{ inputs.result-name }}
|
||||
JOB_NAME_PREFIX: ${{ inputs.job-name-prefix }}
|
||||
LANE_LABELS_JSON: ${{ inputs.lane-labels-json }}
|
||||
SUCCESS_STEP_NAME: ${{ inputs.success-step-name }}
|
||||
POLL_INTERVAL_SECONDS: ${{ inputs.poll-interval-seconds }}
|
||||
with:
|
||||
github-token: ${{ inputs.github-token }}
|
||||
script: |
|
||||
const resultName = process.env.RESULT_NAME;
|
||||
const jobNamePrefix = process.env.JOB_NAME_PREFIX;
|
||||
const successStepName = process.env.SUCCESS_STEP_NAME;
|
||||
|
||||
let laneLabels;
|
||||
try {
|
||||
laneLabels = JSON.parse(process.env.LANE_LABELS_JSON);
|
||||
} catch (error) {
|
||||
core.setFailed(`Invalid lane-labels-json: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(laneLabels) || laneLabels.length === 0) {
|
||||
core.setFailed('lane-labels-json must contain at least one lane label');
|
||||
return;
|
||||
}
|
||||
|
||||
const pollIntervalSeconds = Number.parseInt(
|
||||
process.env.POLL_INTERVAL_SECONDS,
|
||||
10
|
||||
);
|
||||
|
||||
if (
|
||||
!Number.isFinite(pollIntervalSeconds) ||
|
||||
pollIntervalSeconds < 5
|
||||
) {
|
||||
core.setFailed('poll-interval-seconds must be at least 5');
|
||||
return;
|
||||
}
|
||||
|
||||
const laneNames = laneLabels.map(
|
||||
label => `${jobNamePrefix} (${label})`
|
||||
);
|
||||
|
||||
const laneReportedSuccess = lane =>
|
||||
lane.status === 'completed' &&
|
||||
lane.conclusion === 'success' &&
|
||||
lane.steps?.some(step =>
|
||||
step.name === successStepName &&
|
||||
step.conclusion === 'success'
|
||||
);
|
||||
|
||||
let previousState = '';
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const response =
|
||||
await github.rest.actions.listJobsForWorkflowRun({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.runId,
|
||||
filter: 'latest',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const lanes = laneNames
|
||||
.map(name =>
|
||||
response.data.jobs.find(job => job.name === name)
|
||||
)
|
||||
.filter(Boolean);
|
||||
|
||||
const winner = lanes.find(laneReportedSuccess);
|
||||
|
||||
if (winner) {
|
||||
core.notice(
|
||||
`${winner.name} passed; ${resultName} result is successful`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
lanes.length === laneNames.length &&
|
||||
lanes.every(lane => lane.status === 'completed')
|
||||
) {
|
||||
const results = lanes
|
||||
.map(lane => {
|
||||
const reportedSuccess = lane.steps?.some(step =>
|
||||
step.name === successStepName &&
|
||||
step.conclusion === 'success'
|
||||
);
|
||||
|
||||
return (
|
||||
`${lane.name}: conclusion=${lane.conclusion}, ` +
|
||||
`${successStepName}=${reportedSuccess ? 'success' : 'not-reported'}`
|
||||
);
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
core.setFailed(
|
||||
`None of the ${resultName} lanes passed. ${results}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = laneNames
|
||||
.map(name => {
|
||||
const lane = lanes.find(candidate => candidate.name === name);
|
||||
|
||||
return (
|
||||
`${name}: ` +
|
||||
`${lane?.status ?? 'not-created'}/` +
|
||||
`${lane?.conclusion ?? '-'}`
|
||||
);
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
if (state !== previousState) {
|
||||
core.info(
|
||||
`Waiting for a ${resultName} lane to pass. ${state}`
|
||||
);
|
||||
previousState = state;
|
||||
}
|
||||
} catch (error) {
|
||||
// A transient API failure should not immediately fail the
|
||||
// required result. Retry until a lane passes, all lanes finish,
|
||||
// or the calling job reaches its timeout.
|
||||
core.warning(
|
||||
`Could not inspect ${resultName} jobs; retrying: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
await new Promise(resolve =>
|
||||
setTimeout(resolve, pollIntervalSeconds * 1_000)
|
||||
);
|
||||
}
|
||||
64
.github/actions/check-peer-success/action.yml
vendored
Normal file
64
.github/actions/check-peer-success/action.yml
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
name: Check peer success
|
||||
description: Stop this lane cooperatively when its peer has fully passed
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
description: Token used to inspect jobs in the current workflow run
|
||||
required: true
|
||||
current-label:
|
||||
description: Label identifying the current matrix lane
|
||||
required: true
|
||||
peer-label:
|
||||
description: Label identifying the other matrix lane
|
||||
required: true
|
||||
job-name-prefix:
|
||||
description: Job name before the matrix label
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Check whether peer lane passed
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # Version 7
|
||||
env:
|
||||
CURRENT_LABEL: ${{ inputs.current-label }}
|
||||
PEER_LABEL: ${{ inputs.peer-label }}
|
||||
JOB_NAME_PREFIX: ${{ inputs.job-name-prefix }}
|
||||
with:
|
||||
github-token: ${{ inputs.github-token }}
|
||||
script: |
|
||||
const currentLabel = process.env.CURRENT_LABEL;
|
||||
const peerLabel = process.env.PEER_LABEL;
|
||||
const peerName = `${process.env.JOB_NAME_PREFIX} (${peerLabel})`;
|
||||
|
||||
try {
|
||||
const response = await github.rest.actions.listJobsForWorkflowRun({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.runId,
|
||||
filter: 'latest',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const peer = response.data.jobs.find(job => job.name === peerName);
|
||||
|
||||
const peerReportedSuccess =
|
||||
peer?.status === 'completed' &&
|
||||
peer?.conclusion === 'success' &&
|
||||
peer.steps?.some(step =>
|
||||
step.name === 'Report success' &&
|
||||
step.conclusion === 'success'
|
||||
);
|
||||
|
||||
if (peerReportedSuccess) {
|
||||
core.notice(
|
||||
`${peerName} passed; skipping the remaining work on ${currentLabel}`
|
||||
);
|
||||
core.exportVariable('PEER_SUCCEEDED', 'true');
|
||||
}
|
||||
} catch (error) {
|
||||
// Coordination failure must not fail the test lane.
|
||||
core.warning(
|
||||
`Could not check peer job; continuing this lane: ${error.message}`
|
||||
);
|
||||
}
|
||||
131
.github/workflows/cucumber-integration-tests.yml
vendored
131
.github/workflows/cucumber-integration-tests.yml
vendored
@ -16,7 +16,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Run cucumber integration tests
|
||||
name: Run cucumber integration tests (${{ matrix.platform.label }})
|
||||
# Heavy, self-hosted suite: run ONLY inside the merge queue so the pull
|
||||
# request phase reports only the lightweight `Cucumber result` check below.
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
@ -26,29 +26,25 @@ jobs:
|
||||
actions: write
|
||||
checks: write
|
||||
|
||||
# Each OS reports its own result so `cucumber-result` below can require
|
||||
# that at least one OS passed. A failed run never reaches the reporting
|
||||
# step, leaving its output empty.
|
||||
outputs:
|
||||
self-hosted-linux: ${{ steps.report.outputs.self-hosted-linux }}
|
||||
self-hosted-macos: ${{ steps.report.outputs.self-hosted-macos }}
|
||||
|
||||
strategy:
|
||||
# All OSes should be tested even if one fails (default: true)
|
||||
# A failed OS must not cancel the other OS because only one OS needs to pass.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
# Using self-hosted runners because we were running out of memory on GitHub-hosted runners
|
||||
- os: [ self-hosted, Linux ]
|
||||
label: self-hosted-linux
|
||||
peer_label: self-hosted-macos
|
||||
- os: [ self-hosted, macOS ]
|
||||
label: self-hosted-macos
|
||||
peer_label: self-hosted-linux
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
env:
|
||||
# Heavy Rust integration tests become less stable when oversubscribing the
|
||||
# self-hosted runners. Keep these lanes single-job to reduce retries.
|
||||
E2E_NEXTEST_JOBS: 1
|
||||
LOGOS_BLOCKCHAIN_NODE_BIN: ${{ github.workspace }}/target/release/logos-blockchain-node
|
||||
PEER_SUCCEEDED: "false"
|
||||
|
||||
steps:
|
||||
# Remove any rustup override to ensure default toolchain is used
|
||||
@ -81,9 +77,23 @@ jobs:
|
||||
id: verify_local_ntp
|
||||
uses: ./.github/actions/verify-local-ntp-server
|
||||
|
||||
# This cooperative checkpoint is reused before each expensive phase.
|
||||
# It does not cancel the workflow. If the other OS has completed the
|
||||
# entire lane and its `Report success` step passed, this lane skips all
|
||||
# remaining expensive work and proceeds to cleanup.
|
||||
- &check_peer_success
|
||||
name: Stop if another Cucumber OS has already passed
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' }}
|
||||
uses: ./.github/actions/check-peer-success
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
current-label: ${{ matrix.platform.label }}
|
||||
peer-label: ${{ matrix.platform.peer_label }}
|
||||
job-name-prefix: Run cucumber integration tests
|
||||
|
||||
# Build release binaries for the cucumber tests.
|
||||
- name: Build binaries for cucumber tests
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72
|
||||
with:
|
||||
command: build
|
||||
@ -91,6 +101,8 @@ jobs:
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0 # Disable incremental compilation
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Run cucumber integration tests with environment variables for binaries and test params
|
||||
# Special exclusion tags (we want to run the rest of the tests and not fail the workflow):
|
||||
# - @broken = tests that are broken and must still be fixed
|
||||
@ -98,7 +110,7 @@ jobs:
|
||||
|
||||
# Feature: Smoke (@smoke_ci)
|
||||
- name: Run cucumber suite - smoke
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -107,9 +119,11 @@ jobs:
|
||||
artifact_subdir: Smoke
|
||||
max_concurrent_scenarios: "1"
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Feature: Cryptarchia (@cryptarchia_ci)
|
||||
- name: Run cucumber suite - cryptarchia
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -118,9 +132,11 @@ jobs:
|
||||
artifact_subdir: Cryptarchia
|
||||
max_concurrent_scenarios: "1"
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Feature: Blend (@blend_ci)
|
||||
- name: Run cucumber suite - blend
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -129,9 +145,11 @@ jobs:
|
||||
artifact_subdir: Blend
|
||||
max_concurrent_scenarios: "1"
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Feature: Transactions (@transactions_ci)
|
||||
- name: Run cucumber suite - transactions
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 120
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -141,9 +159,11 @@ jobs:
|
||||
verbose_console: "true"
|
||||
max_concurrent_scenarios: "1"
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Feature: Mempool (@mempool_ci)
|
||||
- name: Run cucumber suite - mempool
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -152,9 +172,11 @@ jobs:
|
||||
artifact_subdir: Mempool lifecycle
|
||||
verbose_console: "true"
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Feature: Zone SDK (@zone_ci)
|
||||
- name: Run cucumber suite - zone
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -163,9 +185,11 @@ jobs:
|
||||
artifact_subdir: Zone
|
||||
max_concurrent_scenarios: "1"
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Feature: CLI utility binary (@cli_ci)
|
||||
- name: Run cucumber suite - CLI utilities
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/run-cucumber-suite
|
||||
with:
|
||||
@ -174,23 +198,23 @@ jobs:
|
||||
artifact_subdir: Cli
|
||||
max_concurrent_scenarios: "1"
|
||||
|
||||
# Always upload JUnit test results for reporting, even if tests fail
|
||||
# Upload JUnit test results only for a lane that ran to completion.
|
||||
- name: Upload cucumber JUnit results
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 # v4.6.2
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 # Version 4.6.2
|
||||
with:
|
||||
name: cucumber-junit-${{ runner.os }} # ✅ Unique per OS
|
||||
name: cucumber-junit-${{ runner.os }}
|
||||
path: ${{ github.workspace }}/tests/cucumber_tests/temp/cucumber-output-junit.xml
|
||||
if-no-files-found: warn
|
||||
|
||||
# Always publish a test report for visibility, do not fail workflow if this step fails
|
||||
# Publish a test report only for a lane that ran to completion.
|
||||
- name: Publish cucumber test report
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
continue-on-error: true
|
||||
uses: dorny/test-reporter@v1
|
||||
with:
|
||||
name: Cucumber-${{ runner.os }} # Optional: distinguish Linux vs macOS
|
||||
name: Cucumber-${{ runner.os }}
|
||||
path: ${{ github.workspace }}/tests/cucumber_tests/temp/cucumber-output-junit.xml
|
||||
reporter: java-junit
|
||||
|
||||
@ -204,33 +228,50 @@ jobs:
|
||||
if: success() || failure()
|
||||
run: rm -rf ${{ github.workspace }}/tests/cucumber_tests/temp/cucumber_artefacts
|
||||
|
||||
# Runs only if every step above succeeded (default `if: success()`),
|
||||
# marking this OS as passed for the `cucumber-result` job.
|
||||
# This step is the unambiguous signal consumed by the peer lane and the
|
||||
# required result job. A cooperatively stopped lane skips this step.
|
||||
- name: Report success
|
||||
id: report
|
||||
run: echo "${{ matrix.platform.label }}=success" >> "$GITHUB_OUTPUT"
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' }}
|
||||
run: echo "All Cucumber tests passed on ${{ matrix.platform.label }}"
|
||||
|
||||
# This is the stable required check for the Cucumber suite.
|
||||
# On pull requests it succeeds without starting either self-hosted runner.
|
||||
# In the merge queue it passes if at least one OS passed, so a single flaky
|
||||
# OS run does not eject the PR. Both OS runs still execute and remain visible.
|
||||
#
|
||||
# It intentionally has no `needs: tests`: on merge_group it starts alongside
|
||||
# the matrix and polls the current workflow run. It succeeds as soon as either
|
||||
# OS has completed the entire lane and its `Report success` step passed.
|
||||
# It fails once both lanes have completed without reporting success.
|
||||
#
|
||||
# On pull requests it succeeds immediately without starting self-hosted jobs.
|
||||
cucumber-result:
|
||||
name: Cucumber result
|
||||
needs: tests
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 230
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
# On pull requests, report the required check without running or checking
|
||||
# either self-hosted lane.
|
||||
- name: Report pull-request result
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
run: echo "Cucumber integration tests run only in the merge queue"
|
||||
|
||||
# A local composite action must be checked out before it can be invoked.
|
||||
# Only the required action directory is fetched.
|
||||
- name: Checkout result action
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 # Version 4.2.2
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
sparse-checkout: .github/actions/check-any-lane-result
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Check Cucumber result
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" != "merge_group" ]; then
|
||||
echo "Cucumber runs only in the merge queue"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${{ needs.tests.outputs.self-hosted-linux }}" = "success" ] || \
|
||||
[ "${{ needs.tests.outputs.self-hosted-macos }}" = "success" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Both Cucumber OS runs failed"
|
||||
exit 1
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
uses: ./.github/actions/check-any-lane-result
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-name: Cucumber
|
||||
job-name-prefix: Run cucumber integration tests
|
||||
lane-labels-json: '["self-hosted-linux", "self-hosted-macos"]'
|
||||
114
.github/workflows/end-to-end-integration-tests.yml
vendored
114
.github/workflows/end-to-end-integration-tests.yml
vendored
@ -16,7 +16,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Run end-to-end integration tests
|
||||
name: Run end-to-end integration tests (${{ matrix.platform.label }})
|
||||
# Heavy, self-hosted suite: run ONLY inside the merge queue so the pull
|
||||
# request phase reports only the lightweight `E2E result` check below.
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
@ -26,29 +26,25 @@ jobs:
|
||||
actions: write
|
||||
checks: write
|
||||
|
||||
# Each OS reports its own result so `e2e-result` below can require that at
|
||||
# least one OS passed. A failed run never reaches the reporting step,
|
||||
# leaving its output empty.
|
||||
outputs:
|
||||
self-hosted-linux: ${{ steps.report.outputs.self-hosted-linux }}
|
||||
self-hosted-macos: ${{ steps.report.outputs.self-hosted-macos }}
|
||||
|
||||
strategy:
|
||||
# All OSes should be tested even if one fails (default: true)
|
||||
# A failed OS must not cancel the other OS because only one OS needs to pass.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
# Using self-hosted runners because we were running out of memory on GitHub-hosted runners
|
||||
- os: [ self-hosted, Linux ]
|
||||
label: self-hosted-linux
|
||||
peer_label: self-hosted-macos
|
||||
- os: [ self-hosted, macOS ]
|
||||
label: self-hosted-macos
|
||||
peer_label: self-hosted-linux
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
env:
|
||||
# Heavy Rust e2e tests become less stable when nextest oversubscribes the
|
||||
# self-hosted runners. Keep these lanes single-job to reduce retries.
|
||||
E2E_NEXTEST_JOBS: 1
|
||||
LOGOS_BLOCKCHAIN_NODE_BIN: ${{ github.workspace }}/target/release/logos-blockchain-node
|
||||
PEER_SUCCEEDED: "false"
|
||||
|
||||
steps:
|
||||
# Remove any rustup override to ensure default toolchain is used
|
||||
@ -86,9 +82,23 @@ jobs:
|
||||
id: verify_local_ntp
|
||||
uses: ./.github/actions/verify-local-ntp-server
|
||||
|
||||
# This cooperative checkpoint is reused before each expensive phase.
|
||||
# It does not cancel the workflow. If the other OS has completed the
|
||||
# entire lane and its `Report success` step passed, this lane skips all
|
||||
# remaining expensive work and proceeds to cleanup.
|
||||
- &check_peer_success
|
||||
name: Stop if another E2E OS has already passed
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' }}
|
||||
uses: ./.github/actions/check-peer-success
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
current-label: ${{ matrix.platform.label }}
|
||||
peer-label: ${{ matrix.platform.peer_label }}
|
||||
job-name-prefix: Run end-to-end integration tests
|
||||
|
||||
# Build the testing-featured node binary required by mantle_sdp.
|
||||
- name: Build testing binary for mantle_sdp
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72
|
||||
with:
|
||||
command: build
|
||||
@ -96,10 +106,12 @@ jobs:
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0 # Disable incremental compilation
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Run the isolated SDP ops target that requires the testing-featured node binary.
|
||||
- name: End-to-end integration tests - mantle_sdp
|
||||
id: mantle_sdp_end_to_end
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 90
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72 # Version 1.0.1
|
||||
env:
|
||||
@ -108,9 +120,11 @@ jobs:
|
||||
command: nextest
|
||||
args: run --locked --jobs ${{ env.E2E_NEXTEST_JOBS }} --retries 2 -p logos-blockchain-tests --features mantle_sdp --test test_mantle_sdp_ops --test test_mantle_sdp_blend --no-fail-fast
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Build the testing-featured node binary required by tip_poll_self_heal.
|
||||
- name: Build testing binary for tip_poll_self_heal
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72
|
||||
with:
|
||||
command: build
|
||||
@ -118,10 +132,12 @@ jobs:
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0 # Disable incremental compilation
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Run the isolated tip-poll self-heal test that requires the testing-featured node binary.
|
||||
- name: End-to-end integration tests - tip_poll_self_heal
|
||||
id: tip_poll_self_heal_end_to_end
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 90
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72 # Version 1.0.1
|
||||
env:
|
||||
@ -130,9 +146,11 @@ jobs:
|
||||
command: nextest
|
||||
args: run --locked --jobs ${{ env.E2E_NEXTEST_JOBS }} --retries 2 -p logos-blockchain-tests --features tip_poll_self_heal --test test_cryptarchia_tip_poll_self_heal --no-fail-fast
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Build release binaries for the remaining end-to-end tests.
|
||||
- name: Build binaries for remaining end-to-end tests
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72
|
||||
with:
|
||||
command: build
|
||||
@ -140,17 +158,20 @@ jobs:
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0 # Disable incremental compilation
|
||||
|
||||
- *check_peer_success
|
||||
|
||||
# Run the remaining end-to-end integration tests using cargo-nextest for better performance and reliability
|
||||
- name: End-to-end integration tests
|
||||
id: end_to_end
|
||||
if: ${{ success() && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' && steps.verify_local_ntp.conclusion == 'success' }}
|
||||
timeout-minutes: 90
|
||||
uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72 # Version 1.0.1
|
||||
env:
|
||||
USE_LOCAL_HOST_NTP_TIME_CONFIG: true # Ensure tests use local NTP server for time sync
|
||||
with:
|
||||
command: nextest
|
||||
# - We don't test benches as they take 6h+, leading to a timeout
|
||||
# We keep `--no-fail-fast` so a running nextest phase can report
|
||||
# multiple failures before the lane concludes.
|
||||
args: run --locked --jobs ${{ env.E2E_NEXTEST_JOBS }} --retries 2 -p logos-blockchain-tests --no-fail-fast
|
||||
|
||||
- name: Upload integration tests results on failure
|
||||
@ -168,33 +189,50 @@ jobs:
|
||||
with:
|
||||
command: clean
|
||||
|
||||
# Runs only if every step above succeeded (default `if: success()`),
|
||||
# marking this OS as passed for the `e2e-result` job.
|
||||
# This step is the unambiguous signal consumed by the peer lane and the
|
||||
# required result job. A cooperatively stopped lane skips this step.
|
||||
- name: Report success
|
||||
id: report
|
||||
run: echo "${{ matrix.platform.label }}=success" >> "$GITHUB_OUTPUT"
|
||||
if: ${{ success() && env.PEER_SUCCEEDED != 'true' }}
|
||||
run: echo "All E2E tests passed on ${{ matrix.platform.label }}"
|
||||
|
||||
# This is the stable required check for the end-to-end suite.
|
||||
# On pull requests it succeeds without starting either self-hosted runner.
|
||||
# In the merge queue it passes if at least one OS passed, so a single flaky
|
||||
# OS run does not eject the PR. Both OS runs still execute and remain visible.
|
||||
# This is the stable required check for the E2E suite.
|
||||
#
|
||||
# It intentionally has no `needs: tests`: on merge_group it starts alongside
|
||||
# the matrix and polls the current workflow run. It succeeds as soon as either
|
||||
# OS has completed the entire lane and its `Report success` step passed.
|
||||
# It fails once both lanes have completed without reporting success.
|
||||
#
|
||||
# On pull requests it succeeds immediately without starting self-hosted jobs.
|
||||
e2e-result:
|
||||
name: E2E result
|
||||
needs: tests
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 230
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
# On pull requests, report the required check without running or checking
|
||||
# either self-hosted lane.
|
||||
- name: Report pull-request result
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
run: echo "End-to-end integration tests run only in the merge queue"
|
||||
|
||||
# A local composite action must be checked out before it can be invoked.
|
||||
# Only the required action directory is fetched.
|
||||
- name: Checkout result action
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 # Version 4.2.2
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
sparse-checkout: .github/actions/check-any-lane-result
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Check E2E result
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" != "merge_group" ]; then
|
||||
echo "End-to-end integration tests run only in the merge queue"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${{ needs.tests.outputs.self-hosted-linux }}" = "success" ] || \
|
||||
[ "${{ needs.tests.outputs.self-hosted-macos }}" = "success" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Both end-to-end OS runs failed"
|
||||
exit 1
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
uses: ./.github/actions/check-any-lane-result
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-name: E2E
|
||||
job-name-prefix: Run end-to-end integration tests
|
||||
lane-labels-json: '["self-hosted-linux", "self-hosted-macos"]'
|
||||
Loading…
x
Reference in New Issue
Block a user