fix(ci): preserve independent validation for main push SHAs (#15508)

This commit is contained in:
易良 authored and GitHub committed 2026-10-25 16:51:04 +08:00
1 parent b423ed9042
commit cc49daa282
4 files changed
+77 -59

No files matched your search

+6 -13
View File
@@ -60,19 +60,12 @@ concurrency:
# repo's namespace and PR runs DO cancel in progress, so a fork whose
# default branch is `main` cancels a victim PR's run on every push.
# Supersede within a PR is kept: one branch backs one open PR per fork.
# `main` never cancels in progress, and that is intended rather than
# accidental: a shared group holds at most one running plus one pending
# run, and a newer pending run REPLACES the older one even with
# cancel-in-progress: false, so back-to-back merges still coalesce onto the
# newest tree — but the in-flight run always finishes and reports. Merges
# land every ~18min (median) against this ~30min lane, and with no merge
# queue this run is the only fast witness of a merged tree; cancelling it
# starved the suite once already — e2e.yml measured 67 of 100 push runs
# cancelled and only 25 ever reported (#5859). The coverage upload is
# `always()`-gated, so a cancelled run would also publish partial coverage
# under the complete run's artifact name. Release branches are treated the
# same way.
group: '${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref }}'
# A ref-only group replaces older pending main pushes even when cancellation
# is disabled. Give each push-tip SHA its own group so every merged tree can
# report independently. Repeated pushes/reruns of one SHA still share a group.
# Keep running main and release validations uncancellable.
group: |-
${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.sha || github.head_ref || github.ref }}
cancel-in-progress: |-
${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }}
+5 -13
View File
@@ -17,20 +17,12 @@ on:
workflow_dispatch:
concurrency:
# Scope the group by event so pushes to `main` coalesce with each other without
# ever touching the nightly schedule or a manual dispatch — they share the ref
# but not the event. feat/e2e/** pushes coalesce within their own branch.
# Each main push-tip SHA needs its own group: GitHub replaces older pending
# runs in a shared group even with cancellation disabled. Same-SHA reruns
# still share a group; other events and development branches stay ref-scoped.
group: |-
${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref_name }}
# Do NOT cancel in-progress runs on `main`: a full run takes ~40min while
# merges land every ~18min (median), so cancelling on every merge starved the
# suite — over 100 push runs, 67 were cancelled and only 25 ever reported.
# With cancellation off, GitHub keeps at most ONE pending run per group and
# cancels the previously pending one, so the queue collapses to the newest
# tree on its own: the in-flight run always finishes, and each result covers
# the batch of commits merged since the last one (bisect that range when it
# goes red). Dev branches keep cancelling superseded runs — nobody bisects
# those, and the latest push is the only tree of interest.
${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.sha || github.head_ref || github.ref_name }}
# Running main validations must finish; development pushes still supersede.
cancel-in-progress: |-
${{ github.event_name == 'push' && github.ref_name != 'main' }}
+62 -26
View File
@@ -927,37 +927,73 @@ describe('post-merge push lane', () => {
});
it('scopes the concurrency group and keeps main uncancellable', () => {
// All three components, the way e2e-workflow.test.js pins its own.
//
// Event: unscoped, the group collapses onto `Qwen Code CI-refs/heads/main`
// and the post-merge run shares it with the nightly (60-minute lanes, no
// cancellation on main) and with any PR from a fork branch named literally
// `refs/heads/main` — a legal ref name.
//
// Repo + ref: `head_ref` alone is not unique. Two PRs with the same head
// branch name — including from different forks, which evaluate groups in
// the base repo's namespace — share a group, and PR runs DO cancel in
// progress, so they cancel each other. A fork's default branch is `main`,
// the most common head_ref there is. Dropping the ref half entirely would
// collapse every PR into one group and leave this suite green.
//
// cancel-in-progress: `main` stays out of cancellation on purpose, not by
// accident. A shared group holds at most one running plus one pending run,
// and a newer pending run REPLACES the older one even while this reads
// `false`, so back-to-back merges still coalesce onto the newest tree —
// but the in-flight run always finishes and reports, which matters
// because with no merge queue this lane is the only fast witness of a
// merged tree and its coverage upload is `always()`-gated. The history of
// flipping it the other way is recorded once already: 67 of 100 push runs
// cancelled and only 25 ever reported (#5859). Pinning the literal keeps
// a later "make main cancellable" change from landing unnoticed.
expect(String(ci.concurrency.group)).toBe(
'${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref }}',
expect(String(ci.concurrency.group).trim()).toBe(
"${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.sha || github.head_ref || github.ref }}",
);
expect(String(ci.concurrency['cancel-in-progress']).trim()).toBe(
"${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }}",
);
});
it.each([
['push', 'refs/heads/main', '', true],
['push', 'refs/heads/feat/e2e/example', '', false],
['push', 'refs/heads/release/example', '', false],
['schedule', 'refs/heads/main', '', false],
['workflow_dispatch', 'refs/heads/main', '', false],
['workflow_dispatch', 'refs/heads/release/example', '', false],
['pull_request', 'refs/pull/1/merge', 'feature', false],
['merge_group', 'refs/heads/gh-readonly-queue/main/pr-1', '', false],
])(
'groups %s on %s without changing other events',
(event, ref, headRef, perSha) => {
const workflows = [ci];
if (['push', 'schedule', 'workflow_dispatch'].includes(event)) {
workflows.push(
parse(readFileSync('.github/workflows/e2e.yml', 'utf8')),
);
}
for (const workflow of workflows) {
for (const sourceRepo of event === 'pull_request'
? ['one/fork', 'two/fork']
: ['']) {
for (const sha of ['a'.repeat(40), 'b'.repeat(40)]) {
for (const attempt of [1, 2]) {
const github = {
workflow: workflow.name,
repository: 'QwenLM/qwen-code',
event_name: event,
event: {
pull_request: { head: { repo: { full_name: sourceRepo } } },
},
ref,
ref_name: ref.replace('refs/heads/', ''),
head_ref: headRef,
sha,
run_id: attempt,
run_attempt: attempt,
};
// Evaluate the actual YAML's string/boolean expressions. Exact
// expression pins above and in e2e-workflow constrain this subset.
const group = workflow.concurrency.group
.trim()
.replace(/\$\{\{(.*?)\}\}/g, (_, expression) =>
new Function('github', `return (${expression});`)(github),
);
const isCi = workflow === ci;
const repoPart = isCi
? `${sourceRepo || github.repository}-`
: '';
const refPart = headRef || (isCi ? ref : github.ref_name);
expect(group).toBe(
`${workflow.name}-${event}-${repoPart}${perSha ? sha : refPart}`,
);
}
}
}
}
},
);
});
describe('platform lanes — the retired sensitivity classifier', () => {
+4 -7
View File
@@ -264,13 +264,10 @@ describe('e2e workflow', () => {
);
});
it('scopes the concurrency group by event and ref', () => {
// Scoping by event keeps main pushes coalescing with each other without
// touching the nightly schedule or a manual dispatch on the same ref.
const group = yml.concurrency.group;
expect(group).toContain('github.workflow');
expect(group).toContain('github.event_name');
expect(group).toContain('github.head_ref || github.ref_name');
it('scopes main pushes by SHA and other events by ref', () => {
expect(yml.concurrency.group.trim()).toBe(
"${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.sha || github.head_ref || github.ref_name }}",
);
});
it('isolates child-process-heavy suites from the three parallel forks', () => {