mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
fix(ci): route web shell smoke to ARM64 independently (#15364)
* fix(ci): route web shell smoke to ARM64 independently * test(ci): pin the smoke lane's ARM64 routing in the meta-tests The runs-on change left two scripts/tests assertions pinning the old routing, so the Linux Test check and the nightly macOS/Windows lanes would go red once dependency installation succeeded (AGENTS.md guideline #3). - ci-platform-lanes: evaluate the real runs-on expression and pin self-hosted -> ARM64 plus the hosted and missing-output fallbacks, instead of the old equality with the shared test lane. - no-ak-integration-ci: replace the raw-YAML literal pin with a check that the lane still derives its runner from the classifier; the architecture override is pinned in ci-platform-lanes. - ci.yml: restore the #11250 pool history the routing change deleted, marked as x64-specific rather than a verdict on ARM64. * test: tolerate slow durable I/O in CI synchronization checks
This commit is contained in:
6 files changed
+140
-20
No files matched your search
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// classify_pr carries the routing logic TWICE — the `runs-on` expression
|
||||
// (which selects the classify job's own runner) and the `pick_runner` shell
|
||||
// step (which publishes `ubuntu_runner` for every downstream Linux job). If
|
||||
// step (which publishes `ubuntu_runner` for downstream Linux jobs). If
|
||||
// they drift, classify and the Test job land on different pools. These tests
|
||||
// evaluate BOTH against the same event matrix — including the negative
|
||||
// associations that must stay hosted — and assert they agree.
|
||||
@@ -118,7 +118,14 @@ function evalRunsOn(expression, { ecsDisabled, eventName, sameRepo, assoc }) {
|
||||
|
||||
// Executes the real pick_runner shell with the same inputs and returns the
|
||||
// selected runner exactly as CI would publish it.
|
||||
function runPickRunner({ ecsDisabled, sameRepo, assoc, eventName, dispatch }) {
|
||||
function runPickRunner({
|
||||
ecsDisabled,
|
||||
sameRepo,
|
||||
assoc,
|
||||
eventName,
|
||||
dispatch,
|
||||
fleet,
|
||||
}) {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'pick-runner-'));
|
||||
const outputFile = join(tmp, 'github_output');
|
||||
const result = spawnSync('bash', ['-c', pickRunner.run], {
|
||||
@@ -127,6 +134,7 @@ function runPickRunner({ ecsDisabled, sameRepo, assoc, eventName, dispatch }) {
|
||||
AUTHOR_ASSOCIATION: assoc,
|
||||
ECS_DISABLED: ecsDisabled ? 'true' : '',
|
||||
EVENT_NAME: eventName,
|
||||
LINUX_RUNNER_FLEET: fleet ?? '',
|
||||
DISPATCH_LINUX_RUNNER: dispatch ?? '',
|
||||
GITHUB_OUTPUT: outputFile,
|
||||
},
|
||||
@@ -266,6 +274,73 @@ describe('ci.yml classify_pr runner routing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ci.yml web_shell_e2e_smoke runner routing', () => {
|
||||
const expression = String(ciDoc.jobs.web_shell_e2e_smoke['runs-on'])
|
||||
.replace(/^\$\{\{\s*/, '')
|
||||
.replace(/\s*\}\}$/, '');
|
||||
const selectSmokeRunner = new Function(
|
||||
'needs',
|
||||
'fromJSON',
|
||||
'contains',
|
||||
`return (${expression});`,
|
||||
);
|
||||
|
||||
it('uses ARM64 for self-hosted runs while preserving every hosted fallback', () => {
|
||||
for (const ecsDisabled of [false, true]) {
|
||||
for (const fleet of ['', 'x64', 'arm64', 'hosted']) {
|
||||
for (const sameRepo of [false, true]) {
|
||||
for (const assoc of ASSOCIATIONS) {
|
||||
for (const dispatch of [
|
||||
undefined,
|
||||
'self-hosted',
|
||||
'self-hosted-arm64',
|
||||
'hosted',
|
||||
]) {
|
||||
const runner = runPickRunner({
|
||||
ecsDisabled,
|
||||
fleet,
|
||||
sameRepo,
|
||||
assoc,
|
||||
dispatch,
|
||||
eventName: dispatch ? 'workflow_dispatch' : 'pull_request',
|
||||
});
|
||||
assert.deepEqual(
|
||||
selectSmokeRunner(
|
||||
{ classify_pr: { outputs: { ubuntu_runner: runner } } },
|
||||
JSON.parse,
|
||||
(labels, label) => labels.includes(label),
|
||||
),
|
||||
JSON.parse(runner).includes('self-hosted')
|
||||
? ['self-hosted', 'linux', 'arm64']
|
||||
: JSON.parse(HOSTED),
|
||||
JSON.stringify({
|
||||
ecsDisabled,
|
||||
fleet,
|
||||
sameRepo,
|
||||
assoc,
|
||||
dispatch,
|
||||
runner,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('stays hosted when the classifier output is missing', () => {
|
||||
assert.deepEqual(
|
||||
selectSmokeRunner(
|
||||
{ classify_pr: { outputs: {} } },
|
||||
JSON.parse,
|
||||
(labels, label) => labels.includes(label),
|
||||
),
|
||||
JSON.parse(HOSTED),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ci.yml test_windows runner routing', () => {
|
||||
it('keeps every pull request hosted, whoever opens it', () => {
|
||||
// A pull_request run executes the workflow YAML from the PR's own merge
|
||||
|
||||
@@ -1442,14 +1442,16 @@ jobs:
|
||||
needs.classify_pr.outputs.skip_ci != 'true' &&
|
||||
needs.test.outputs.ci_profile == 'full'
|
||||
}}
|
||||
# Routed like test's: trusted runs use the ECS pool, untrusted fork PRs
|
||||
# stay hosted. #11250 moved it hosted after four pool attempts failed
|
||||
# (run 34081409590). Three ran the smoke unpinned at 8 workers, half the
|
||||
# host's 16 cores, against hosted's 2, and timed out broadly on
|
||||
# Keep the classifier's trust and hosted overrides, but use ARM64 for
|
||||
# this lane independently of the ordinary Linux CI fleet. The history
|
||||
# below is x64-specific, not a verdict on ARM64, but it is why this lane
|
||||
# is fragile: #11250 moved it back to hosted after four pool attempts
|
||||
# failed (run 34081409590). Three ran the smoke unpinned at 8 workers,
|
||||
# half the host's 16 cores, against hosted's 2, and timed out broadly on
|
||||
# visibility and clock waits. The fourth's transcript gate took 73 s
|
||||
# against its 60 s budget while npm install alone took 28 min on that
|
||||
# host. The smoke step pins the hosted worker count.
|
||||
runs-on: '${{ fromJSON(needs.classify_pr.outputs.ubuntu_runner || ''["ubuntu-latest"]'') }}'
|
||||
runs-on: '${{ contains(fromJSON(needs.classify_pr.outputs.ubuntu_runner || ''["ubuntu-latest"]''), ''self-hosted'') && fromJSON(''["self-hosted", "linux", "arm64"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
# Slow hosted mirrors kept downloading past 23 min; reserve time for
|
||||
# the 208 smoke tests and artifact cleanup after installation.
|
||||
timeout-minutes: 60
|
||||
|
||||
@@ -859,6 +859,7 @@ describe('hosted-session-command-session funnel (H4e-b3)', () => {
|
||||
expect(record.run.delivery?.state).toBe('rejected');
|
||||
});
|
||||
|
||||
// The 256 real durable commands require over 1,000 filesystem syncs.
|
||||
it('enforces the per-run command budget with budget_exhausted', ({
|
||||
signal,
|
||||
}) => {
|
||||
@@ -926,7 +927,7 @@ describe('hosted-session-command-session funnel (H4e-b3)', () => {
|
||||
})();
|
||||
pendingBudgetTests.push(work);
|
||||
return work;
|
||||
}, 30_000);
|
||||
}, 90_000);
|
||||
|
||||
it("commits the leader's receipt against an attached run and refetches its reply", async () => {
|
||||
const lead = await leadSideSetup();
|
||||
|
||||
@@ -1690,20 +1690,28 @@ describe('extension tests', () => {
|
||||
it('signals the durable commit before deferred settings finish', async () => {
|
||||
const { manager, prepared } = await readyPrepared('settings-deferred');
|
||||
const settings = deferred();
|
||||
const settingsStarted = deferred();
|
||||
const commitSettings = stubCommitSettings(
|
||||
prepared,
|
||||
vi.fn(async () => await settings.promise),
|
||||
vi.fn(async () => {
|
||||
settingsStarted.resolve();
|
||||
await settings.promise;
|
||||
}),
|
||||
);
|
||||
const onCommitted = vi.fn();
|
||||
|
||||
const committing = manager.commitPreparedExtension(prepared, onCommitted);
|
||||
await vi.waitFor(() => expect(commitSettings).toHaveBeenCalledOnce());
|
||||
|
||||
expect(onCommitted).toHaveBeenCalledOnce();
|
||||
expect(onCommitted.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
commitSettings.mock.invocationCallOrder[0]!,
|
||||
);
|
||||
settings.resolve();
|
||||
try {
|
||||
await Promise.race([settingsStarted.promise, committing]);
|
||||
expect(commitSettings).toHaveBeenCalledOnce();
|
||||
expect(onCommitted).toHaveBeenCalledOnce();
|
||||
expect(onCommitted.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
commitSettings.mock.invocationCallOrder[0]!,
|
||||
);
|
||||
} finally {
|
||||
settings.resolve();
|
||||
await committing;
|
||||
}
|
||||
await expect(committing).resolves.toMatchObject({
|
||||
identity: { name: 'settings-deferred' },
|
||||
});
|
||||
|
||||
@@ -71,6 +71,26 @@ const timeoutMinutesOn = (job, ubuntuRunner) => {
|
||||
return Number(new Function(`return (${expr});`)());
|
||||
};
|
||||
|
||||
// The smoke lane keeps the classifier's trust decision and hosted overrides
|
||||
// but picks its own architecture, so its `runs-on` cannot be compared with
|
||||
// another lane's. Evaluate the real expression the way
|
||||
// `.github/scripts/ci-runner-routing.test.mjs` does.
|
||||
const runnerOn = (job, ubuntuRunner) => {
|
||||
const expression = String(ci.jobs[job]['runs-on'])
|
||||
.replace(/^\$\{\{\s*/, '')
|
||||
.replace(/\s*\}\}$/, '');
|
||||
return new Function(
|
||||
'needs',
|
||||
'fromJSON',
|
||||
'contains',
|
||||
`return (${expression});`,
|
||||
)(
|
||||
{ classify_pr: { outputs: { ubuntu_runner: ubuntuRunner } } },
|
||||
JSON.parse,
|
||||
(labels, label) => labels.includes(label),
|
||||
);
|
||||
};
|
||||
|
||||
it('gives the shared Linux test lane one ceiling on both pools', () => {
|
||||
// The full suite outgrew 60 minutes on hosted runners too: on 2026-10-17,
|
||||
// 17 of the 30 hosted runs that ended on their own were cancelled at 60.
|
||||
@@ -101,8 +121,19 @@ it('keeps lint_and_static sized for cold-cache pool runs', () => {
|
||||
expect(timeoutMinutesOn('lint_and_static', '')).toBe(45);
|
||||
});
|
||||
|
||||
it('routes browser gates like the shared Linux test lane, at the hosted worker count', () => {
|
||||
expect(ci.jobs.web_shell_e2e_smoke['runs-on']).toBe(ci.jobs.test['runs-on']);
|
||||
it('routes the browser smoke to ARM64 independently of the shared Linux test lane', () => {
|
||||
// The lane keeps the classifier's trust decision and hosted overrides, but
|
||||
// it no longer inherits the ordinary Linux fleet's architecture.
|
||||
expect(runnerOn('web_shell_e2e_smoke', ECS_RUNNER)).toEqual([
|
||||
'self-hosted',
|
||||
'linux',
|
||||
'arm64',
|
||||
]);
|
||||
expect(runnerOn('web_shell_e2e_smoke', HOSTED_RUNNER)).toEqual([
|
||||
'ubuntu-latest',
|
||||
]);
|
||||
// A missing classifier output (classify_pr skipped) is the hosted routing.
|
||||
expect(runnerOn('web_shell_e2e_smoke', '')).toEqual(['ubuntu-latest']);
|
||||
const smoke = ci.jobs.web_shell_e2e_smoke.steps.find(
|
||||
(step) => step.name === 'Run web-shell browser smoke',
|
||||
);
|
||||
|
||||
@@ -1042,8 +1042,11 @@ describe('no-AK integration CI wiring', () => {
|
||||
);
|
||||
const webShellJob = getWorkflowJob(workflow, 'web_shell_e2e_smoke');
|
||||
|
||||
expect(webShellJob).toContain(
|
||||
"runs-on: '${{ fromJSON(needs.classify_pr.outputs.ubuntu_runner || ''[\"ubuntu-latest\"]'') }}'",
|
||||
// The lane still takes its runner from the classifier, so an untrusted
|
||||
// fork PR cannot reach a self-hosted host; the architecture override it
|
||||
// applies on top is pinned in scripts/tests/ci-platform-lanes.test.js.
|
||||
expect(webShellJob).toMatch(
|
||||
/runs-on: .*needs\.classify_pr\.outputs\.ubuntu_runner/,
|
||||
);
|
||||
const hostedInstall = getWorkflowStep(
|
||||
webShellJob,
|
||||
|
||||
Reference in new issue
Block a user