test: improve ci merge queue integration tests (#3206)

This commit is contained in:
Hansie Odendaal 2026-07-29 13:25:24 +02:00 committed by GitHub
parent d9e6997254
commit caa47fe251
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 391 additions and 83 deletions

View 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)
);
}

View 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}`
);
}

View File

@ -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"]'

View File

@ -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"]'