diff --git a/.github/workflows/phpunit-feature.yml b/.github/workflows/phpunit-feature.yml index 177f18693..676bd0319 100644 --- a/.github/workflows/phpunit-feature.yml +++ b/.github/workflows/phpunit-feature.yml @@ -5,6 +5,7 @@ on: branches: - main pull_request: + merge_group: workflow_dispatch: inputs: corpus_base_ref: @@ -77,6 +78,9 @@ jobs: - name: Require safe PHP image build context run: node --test tests/Unit/PhpImageContextTest.mjs + - name: Run the required checks for merge queue groups + run: node --test tests/Unit/MergeGroupTriggersTest.mjs + - name: Validate embedded Workflow package authority run: php scripts/ci/test-workflow-package-authority.php @@ -91,7 +95,7 @@ jobs: - name: Require versioned worker OpenAPI evolution env: - OPENAPI_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} + OPENAPI_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} run: php scripts/ci/check-worker-openapi-evolution.php "$OPENAPI_BASE_REF" - name: Audit locked PHP dependencies @@ -99,7 +103,7 @@ jobs: - name: Require durable codec evidence env: - CORPUS_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} + CORPUS_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} run: | set -euo pipefail if [[ ! "$CORPUS_BASE_REF" =~ ^[0-9a-f]{40}$ ]] || [[ "$CORPUS_BASE_REF" =~ ^0+$ ]]; then @@ -173,7 +177,7 @@ jobs: - name: Run feature, Nexus, and regression corpus suites shell: bash env: - CORPUS_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} + CORPUS_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} run: | set -euo pipefail if [[ ! "$CORPUS_BASE_REF" =~ ^[0-9a-f]{40}$ ]] || [[ "$CORPUS_BASE_REF" =~ ^0+$ ]]; then @@ -268,7 +272,7 @@ jobs: - name: Select production image qualification for changed build inputs id: image_inputs run: | - if git diff --quiet '${{ github.event.pull_request.base.sha || github.event.before || inputs.corpus_base_ref }}' HEAD -- Dockerfile Dockerfile.dockerignore .dockerignore composer.json composer.lock ':(glob)docker/**'; then + if git diff --quiet '${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || inputs.corpus_base_ref }}' HEAD -- Dockerfile Dockerfile.dockerignore .dockerignore composer.json composer.lock ':(glob)docker/**'; then echo 'required=false' >> "$GITHUB_OUTPUT" else echo 'required=true' >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/public-boundary.yml b/.github/workflows/public-boundary.yml index 89bfb4a83..763b55446 100644 --- a/.github/workflows/public-boundary.yml +++ b/.github/workflows/public-boundary.yml @@ -2,6 +2,7 @@ name: Public Boundary on: pull_request: + merge_group: push: workflow_dispatch: @@ -28,6 +29,8 @@ jobs: if [[ "${{ github.event_name }}" == "pull_request" ]]; then range="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}" + elif [[ "${{ github.event_name }}" == "merge_group" ]]; then + range="${{ github.event.merge_group.base_sha }}..${{ github.event.merge_group.head_sha }}" elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then range="-1 ${{ github.sha }}" elif [[ "${{ github.event.before }}" =~ ^0+$ ]]; then diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index bdb5bcec3..1416deba5 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -114,6 +114,7 @@ on: - tests/Support/ServerParityAdmissionObservation.mjs - tests/Support/ServerParityVisibilityObservation.mjs - rust/** + merge_group: workflow_dispatch: # A newer push to the same pull request supersedes the run in progress, so a diff --git a/tests/Unit/MergeGroupTriggersTest.mjs b/tests/Unit/MergeGroupTriggersTest.mjs new file mode 100644 index 000000000..e8efba169 --- /dev/null +++ b/tests/Unit/MergeGroupTriggersTest.mjs @@ -0,0 +1,49 @@ +// The Actions checks a GitHub merge queue waits for must run on merge_group. +// Source-only: enabling the queue is a separate repository setting, and its +// Sonar gate and terminal Shared Server fixtures result are separate +// prerequisites (Server #725). +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; + +const workflow = name => readFileSync(`.github/workflows/${name}`, 'utf8'); +// The top-level `on:` block, up to the next top-level key. +const triggers = text => { + const start = text.indexOf('\non:\n'); + assert.notEqual(start, -1, 'workflow has an on: block'); + const rest = text.slice(start + 5); + const end = rest.search(/\n[a-z_-]+:/); + return end === -1 ? rest : rest.slice(0, end); +}; + +for (const name of ['phpunit-feature.yml', 'server-parity.yml', 'public-boundary.yml']) { + test(`${name} runs for merge queue groups`, () => { + assert.match(triggers(workflow(name)), /^ {2}merge_group:\s*$/m); + }); +} + +// The Sonar job receives SONAR_TOKEN and its fork guard is always true for a +// merge group, so candidate source must not reach it through the queue. +test('SonarQube does not run for merge queue groups', () => { + assert.doesNotMatch(triggers(workflow('sonar.yml')), /merge_group/); +}); + +test('PHPUnit Feature diffs a queue group against its base', () => { + const text = workflow('phpunit-feature.yml'); + const bases = text.match(/github\.event_name == 'merge_group' && github\.event\.merge_group\.base_sha/g) ?? []; + assert.equal(bases.length, 3, 'OpenAPI and both corpus base revisions'); + assert.match(text, /github\.event\.pull_request\.base\.sha \|\| github\.event\.merge_group\.base_sha \|\| github\.event\.before/); +}); + +test('Public Boundary scans a queue group over its exact range and still scans every push', () => { + const text = workflow('public-boundary.yml'); + assert.match(text, /"merge_group" \]\]; then\n\s+range="\$\{\{ github\.event\.merge_group\.base_sha \}\}\.\.\$\{\{ github\.event\.merge_group\.head_sha \}\}"/); + assert.match(triggers(text), /^ {2}push:\s*$/m); + assert.doesNotMatch(triggers(text), /branches-ignore|branches:|paths/); +}); + +test('superseded-run cancellation never cancels a queue group', () => { + for (const name of ['phpunit-feature.yml', 'server-parity.yml']) { + assert.match(workflow(name), /cancel-in-progress: \$\{\{ github\.event_name == 'pull_request' \}\}/, name); + } +}); diff --git a/tests/Unit/OpenApiDocumentEvolutionTest.php b/tests/Unit/OpenApiDocumentEvolutionTest.php index 135121125..97499930c 100644 --- a/tests/Unit/OpenApiDocumentEvolutionTest.php +++ b/tests/Unit/OpenApiDocumentEvolutionTest.php @@ -55,7 +55,7 @@ public function test_candidate_structure_and_feature_ci_compare_to_the_base_revi $this->assertCount(1, $preflightStep); $this->assertSame( - '${{ github.event_name == \'pull_request\' && github.event.pull_request.base.sha || github.event_name == \'push\' && github.event.before || inputs.corpus_base_ref }}', + '${{ github.event_name == \'pull_request\' && github.event.pull_request.base.sha || github.event_name == \'merge_group\' && github.event.merge_group.base_sha || github.event_name == \'push\' && github.event.before || inputs.corpus_base_ref }}', $preflightStep[0]['env']['OPENAPI_BASE_REF'] ?? null, ); $this->assertSame( diff --git a/tests/Unit/PhpunitFeatureWorkflowContractTest.php b/tests/Unit/PhpunitFeatureWorkflowContractTest.php index 14c48570a..b404d7c95 100644 --- a/tests/Unit/PhpunitFeatureWorkflowContractTest.php +++ b/tests/Unit/PhpunitFeatureWorkflowContractTest.php @@ -106,7 +106,7 @@ public function test_regression_corpus_validation_uses_git_capable_ci_and_an_exa $this->assertSame(0, $checkout['with']['fetch-depth'] ?? null); $this->assertFalse($checkout['with']['persist-credentials'] ?? null); $this->assertSame( - '${{ github.event_name == \'pull_request\' && github.event.pull_request.base.sha || github.event_name == \'push\' && github.event.before || inputs.corpus_base_ref }}', + '${{ github.event_name == \'pull_request\' && github.event.pull_request.base.sha || github.event_name == \'merge_group\' && github.event.merge_group.base_sha || github.event_name == \'push\' && github.event.before || inputs.corpus_base_ref }}', $execution['env']['CORPUS_BASE_REF'] ?? null, ); $this->assertArrayNotHasKey('if', $execution);