Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/dco.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: DCO

on:
pull_request:
# A required check must also report on merge-queue candidates.
merge_group:

permissions:
contents: read
Expand All @@ -19,12 +21,15 @@ jobs:

- name: Require DCO trailers on contribution commits
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BASE_REF: ${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
# merge_group reports the base as a full ref (refs/heads/main).
BASE_REF="${BASE_REF#refs/heads/}"
test -n "${BASE_REF}" && test -n "${HEAD_SHA}"

# The event's base SHA can predate upstream commits already in the PR.
# Refresh the exact target branch before selecting PR-only commits.
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Python Tests
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
# Inert until the main ruleset enables a merge queue; queue runs are full.
merge_group:
workflow_dispatch:
push:
branches:
Expand Down Expand Up @@ -363,7 +365,7 @@ jobs:
needs: changes
# The unsharded full suite outlasts a PR's qualification window, so the
# non-blocking probe runs on main and on demand where it can finish.
if: github.event_name != 'pull_request' && needs.changes.outputs.core_tests == 'true'
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.changes.outputs.core_tests == 'true'
continue-on-error: true
runs-on: ubuntu-latest
# This runs the full core conformance suite. Allow forward-runtime/runner
Expand Down Expand Up @@ -503,6 +505,8 @@ jobs:

sonar:
needs: pytest
# Queue refs are temporary; analyse PRs and main, not gh-readonly-queue/*.
if: github.event_name != 'merge_group'
uses: ./.github/workflows/sonarcloud.yml
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand Down
13 changes: 13 additions & 0 deletions docs/development/testing-and-quality.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,24 @@ is authoritative for activation. The lead maintainer alone retains the
existing bypass exception; record the exact head, reason, validation and known
failures whenever using it. A bypass does not turn failed tests into a pass.

Both required workflows also run on `merge_group`, so a GitHub merge queue can
qualify the exact candidate that would land on `main`. Queue candidates never
receive a job exemption: the classifier plans them as full, exactly like
`main`. `Sign-off` checks the same contribution range and exempts only
verified GitHub-generated two-parent merges, so configure the queue with the
merge method `merge`. The trigger is inert until the live ruleset enables a
merge queue. Enabling the queue, and then relaxing the up-to-date-branch
requirement, is a ruleset decision for the lead maintainer.

每个 PR 都会收到 `merge-gate` 结果。代码、工作流、治理规则和未知路径必须通过
原有核心测试;失败、取消、缺失或意外跳过均不能通过。仅白名单根目录 Markdown
或 `docs/**/*.md` 的修改可显式跳过昂贵测试;运行时 prompt、可执行文档、代码删除
及代码移入文档均不享受豁免。实际启用状态以在线规则为准,使用 owner bypass
必须留下版本、原因、验证和已知失败的记录。
两个必需工作流同样响应 `merge_group`,合并队列可在合入 `main` 前验证确切候选;
队列候选一律全量验证、不享受豁免。队列合并方式应设为 `merge`,因为 `Sign-off`
只豁免已验证的 GitHub 双父合并提交。在线规则启用合并队列前该触发不生效;启用
队列并放宽“分支必须最新”要求由首席维护者决定。

To validate or change the classifier locally:

Expand Down
63 changes: 55 additions & 8 deletions tests/test_dco_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,37 @@ def history(tmp_path: Path, git_env: dict[str, str]):
return runner, old_base, upstream


def expression(value: str, context: dict[str, str]) -> str:
"""Evaluate the workflow's `${{ a || b }}` env expressions over one event."""
inner = value.removeprefix("${{").removesuffix("}}")
assert value != inner, value
return next((context[name] for name in (part.strip() for part in inner.split("||"))
if context.get(name)), "")


def check_dco(
repo: Path, env: dict[str, str], old_base: str, head: str,
*, base_ref: str = "release/next",
*, base_ref: str = "release/next", event: str = "pull_request",
) -> subprocess.CompletedProcess[str]:
workflow = yaml.safe_load((ROOT / ".github/workflows/dco.yml").read_text(encoding="utf-8"))
event_values = {
"${{ github.event.pull_request.base.sha }}": old_base,
"${{ github.event.pull_request.base.ref }}": base_ref,
"${{ github.event.pull_request.head.sha }}": head,
"${{ github.token }}": "synthetic-read-only-token",
}
assert event in workflow[True]
if event == "pull_request":
context = {
"github.event.pull_request.base.sha": old_base,
"github.event.pull_request.base.ref": base_ref,
"github.event.pull_request.head.sha": head,
}
else:
# A merge-queue event carries a full base ref and no pull_request.
context = {
"github.event.merge_group.base_sha": old_base,
"github.event.merge_group.base_ref": f"refs/heads/{base_ref}",
"github.event.merge_group.head_sha": head,
}
context["github.token"] = "synthetic-read-only-token"
steps = [step for step in workflow["jobs"]["signoff"]["steps"] if "run" in step]
for step in steps:
step_env = {key: event_values[value] for key, value in step.get("env", {}).items()}
step_env = {key: expression(value, context) for key, value in step.get("env", {}).items()}
result = subprocess.run(
["bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", step["run"]],
cwd=repo, env={**env, **step_env}, check=False,
Expand Down Expand Up @@ -270,3 +287,33 @@ def test_signed_pr_with_current_event_base_passes(history, git_env):
head = commit(runner, git_env, "Signed contribution")
result = check_dco(runner, git_env, upstream, head)
assert result.returncode == 0, result.stdout + result.stderr


@pytest.mark.parametrize("signed", [False, True])
def test_merge_queue_candidate_checks_the_same_contribution_range(history, git_env, signed):
runner, old_base, upstream = history
head = commit(runner, git_env, "Queued contribution", signed=signed)
result = check_dco(runner, git_env, old_base, head, event="merge_group")
assert (result.returncode == 0) == signed, result.stdout + result.stderr
assert (f"Commit {head} is missing" in result.stdout) == (not signed)
assert f"Commit {upstream} is missing" not in result.stdout


def test_merge_queue_platform_merge_is_exempt_only_with_verified_provenance(history, github_api):
runner, old_base, _ = history
env, calls = github_api
head = github_merge(runner, env)
metadata = verified_merge_record(runner, env, head)
result = check_dco(runner, {**env, "DCO_TEST_METADATA": json.dumps(metadata)}, old_base, head,
event="merge_group")
assert result.returncode == 0, result.stdout + result.stderr
assert f"Verified GitHub-generated merge {head}" in result.stdout
assert calls.read_text().strip() == f"api repos/qualification/dco/commits/{head}"


def test_missing_event_coordinates_fail_closed(history, git_env):
runner, old_base, _ = history
head = commit(runner, git_env, "Signed contribution")
for base_ref, head_sha in (("", head), ("release/next", "")):
result = check_dco(runner, git_env, old_base, head_sha, base_ref=base_ref)
assert result.returncode != 0, (base_ref, head_sha)
17 changes: 16 additions & 1 deletion tests/test_python_ci_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,5 +394,20 @@ def test_typescript_core_shards_feed_one_complete_coverage_report() -> None:
assert "name: typescript-control-plane-coverage" in report
assert "path: coverage/control-plane/lcov.info" in report
forward = WORKFLOW.split(" node-forward-compatibility:\n", 1)[1].split(" test-shard:\n", 1)[0]
assert "github.event_name != 'pull_request'" in forward
assert "(github.event_name == 'push' || github.event_name == 'workflow_dispatch')" in forward
assert "continue-on-error: true" in forward


def test_merge_queue_candidates_run_full_qualification() -> None:
import yaml

workflow = yaml.safe_load(WORKFLOW)
assert "merge_group" in workflow[True]
classify = WORKFLOW.split("name: Classify the exact pull-request change", 1)[1].split(" - uses:", 1)[0]
# Only pull_request may classify an exemption; queue candidates take the
# non-PR branch, which the classifier always plans as full.
assert 'if [[ "$EVENT_NAME" == pull_request ]]; then' in classify
assert "--non-pr" in classify.split("else", 1)[1]
assert "github.event_name != 'merge_group'" in WORKFLOW.split(" sonar:\n", 1)[1].split(" uses:", 1)[0]
# The required check list is unchanged: merge-gate always reports.
assert "if: always()" in WORKFLOW.split(" merge-gate:\n", 1)[1]
9 changes: 8 additions & 1 deletion tests/test_sonarcloud_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

import pytest
import yaml

WORKFLOW = Path(__file__).resolve().parents[1] / ".github" / "workflows" / "sonarcloud.yml"

Expand Down Expand Up @@ -71,6 +72,12 @@ def test_sonar_reuses_same_run_coverage_without_a_privileged_trigger() -> None:
assert "name: python-coverage-xml" in workflow
assert "run-id:" not in workflow
assert "github-token:" not in workflow
assert "needs: pytest\n uses: ./.github/workflows/sonarcloud.yml" in caller
sonar = yaml.safe_load(caller)["jobs"]["sonar"]
# Coverage comes from this run's pytest job, handed to the local reusable
# workflow. The only condition may skip merge-queue refs; a status function
# such as always() would let analysis run without that coverage.
assert sonar["needs"] == "pytest"
assert sonar["uses"] == "./.github/workflows/sonarcloud.yml"
assert sonar.get("if") == "github.event_name != 'merge_group'"
assert '"apps/**"' in caller
assert '"sonar-project.properties"' in caller
Loading