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
40 changes: 39 additions & 1 deletion .github/scripts/ci-disk-pressure.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,27 @@ const workflowPath = join(
'workflows',
'ci.yml',
);
const testSteps = parse(readFileSync(workflowPath, 'utf8')).jobs.test.steps;
const ciJobs = parse(readFileSync(workflowPath, 'utf8')).jobs;
const testSteps = ciJobs.test.steps;

function step(name) {
const value = testSteps.find((candidate) => candidate.name === name);
assert.ok(value, `missing ${name} step`);
return value;
}

// lint_and_static duplicates the sampling install step; it must also carry
// its own failure()-gated collector, or the lane produces the #10035
// telemetry and destroys it with the runner temp dir on the exact ENOSPC
// death the sampler exists to explain.
function lintStep(name) {
const value = ciJobs.lint_and_static.steps.find(
(candidate) => candidate.name === name,
);
assert.ok(value, `missing ${name} step in lint_and_static`);
return value;
}

describe('ci.yml disk-pressure evidence', () => {
it('starts sampling before npm ci and preserves those samples for upload', () => {
const install = step('Install dependencies').run;
Expand Down Expand Up @@ -77,6 +90,31 @@ describe('ci.yml disk-pressure evidence', () => {
);
});

it('gives lint_and_static the same sampler and its own collector', () => {
Comment thread
wenshao marked this conversation as resolved.
// The install step is pinned byte-identical to test's by
// ci-platform-lanes.test.js's shared-prelude equality; what that pin
// cannot see is the collector, which deliberately diverges by artifact
// name (upload-artifact v4+ rejects duplicate names when both jobs fail
// in one run). Pin the collector's contract here.
const install = lintStep('Install dependencies').run;
assert.match(
install,
/DISK_SAMPLES="\$\{RUNNER_TEMP\}\/disk-pressure-samples\.log"/,
);
const upload = lintStep('Upload disk-pressure samples');
assert.equal(upload.if, '${{ failure() }}');
assert.equal(upload.with['if-no-files-found'], 'ignore');
assert.equal(
upload.with.path,
'${{ runner.temp }}/disk-pressure-samples.log',
);
assert.notEqual(
upload.with.name,
step('Upload disk-pressure samples').with.name,
'artifact names must differ or the second failing job cannot upload',
);
});

it('keeps install failure status while writing the pre-install sample', () => {
const root = mkdtempSync(join(tmpdir(), 'ci-disk-pressure-'));
const npm = join(root, 'npm');
Expand Down
77 changes: 64 additions & 13 deletions .github/scripts/qwen-triage-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ const ciIntegrationJob = ciDoc.jobs.integration_cli;
const ciIntegrationOwnershipStep = ciIntegrationJob.steps.find(
(s) => s.name === 'Restore workspace ownership',
);
const ciLintJob = ciDoc.jobs.lint_and_static;
const ciLintOwnershipStep = ciLintJob.steps.find(
(s) => s.name === 'Restore workspace ownership',
);

// A probe that only checks .qwen/.git reports "healthy" on workspace-wide
// poisoning (root-owned node_modules/dist, no .qwen/.git) and skips the
Expand Down Expand Up @@ -371,7 +375,10 @@ describe('qwen-code-pr-review.yml resolve-pr: agent settings', () => {
// included. The step must therefore truncate all four after the agent,
// on every exit path.
const statusIdx = run.indexOf('status=$?');
assert.ok(statusIdx > -1, 'the run block must capture the qwen exit status');
assert.ok(
statusIdx > -1,
'the run block must capture the qwen exit status',
);
for (const file of [
'GITHUB_ENV',
'GITHUB_PATH',
Expand All @@ -380,7 +387,10 @@ describe('qwen-code-pr-review.yml resolve-pr: agent settings', () => {
]) {
const trunc = `: > "\${${file}:?}" || true`;
const idx = run.indexOf(trunc);
assert.ok(idx > -1, `${file} must be truncated after the qwen invocation`);
assert.ok(
idx > -1,
`${file} must be truncated after the qwen invocation`,
);
assert.ok(
idx > statusIdx,
`${file} truncation must run on the exit path, after status=$?`,
Expand All @@ -400,7 +410,7 @@ describe('qwen-code-pr-review.yml resolve-pr: agent settings', () => {
'the workspace settings removal must precede the qwen invocation',
);
assert.ok(run.includes('echo "::stop-commands::${stop_token}"'));
assert.ok(run.includes("printf '\\n::%s::\\n' \"$stop_token\""));
assert.ok(run.includes('printf \'\\n::%s::\\n\' "$stop_token"'));
assert.ok(run.includes('--approval-mode yolo'));
for (const key of Object.keys(resolveConflictsStep.env)) {
assert.ok(
Expand Down Expand Up @@ -1024,6 +1034,18 @@ describe('ci.yml: self-hosted checkout jobs restore ownership unconditionally',
);
});

it('lint_and_static restores ownership unconditionally', () => {
// The split copied the recovery prelude into the new lane; a
// poisoning-recovery edit landing only in this copy would leave every
// other pin green while the future required check fails checkout with
// EACCES on the next contaminated runner.
assertUnconditional(
ciLintJob.steps,
ciLintOwnershipStep,
'ci.yml lint_and_static',
);
});

it('cleanup step removes .qwen but no longer any .stale.* dirs', () => {
assert.ok(
ciCleanStep,
Expand Down Expand Up @@ -1428,21 +1450,35 @@ describe('qwen-triage: flakiness gate (#9125)', () => {
/^\s*cp "\$\{RUNNER_TEMP:\?\}\/flake-record-files-all" "\$GATE_HOME\/files-all"$/m,
'the scrubbed child must copy the parent-recorded diff, never re-run git under env -i',
);
const recordDiffAt = recordStep.run.search(/^\s*\/usr\/bin\/git -c core\.quotePath=false diff -z/m);
const recordDiffAt = recordStep.run.search(
/^\s*\/usr\/bin\/git -c core\.quotePath=false diff -z/m,
);
const recordReExecAt = recordStep.run.search(/exec \/usr\/bin\/env -i/);
const recordCpAt = recordStep.run.search(/^\s*cp "\$\{RUNNER_TEMP:\?\}\/flake-record-files-all" "\$GATE_HOME\/files-all"$/m);
const recordInstallAt = recordStep.run.search(/^\s*install -d -m 0700 -o root -g root "\$GATE_HOME"$/m);
const recordCpAt = recordStep.run.search(
/^\s*cp "\$\{RUNNER_TEMP:\?\}\/flake-record-files-all" "\$GATE_HOME\/files-all"$/m,
);
const recordInstallAt = recordStep.run.search(
/^\s*install -d -m 0700 -o root -g root "\$GATE_HOME"$/m,
);
assert.ok(
recordDiffAt !== -1 && recordReExecAt !== -1 && recordCpAt !== -1 && recordInstallAt !== -1 &&
recordDiffAt < recordReExecAt && recordReExecAt < recordInstallAt && recordInstallAt < recordCpAt,
recordDiffAt !== -1 &&
recordReExecAt !== -1 &&
recordCpAt !== -1 &&
recordInstallAt !== -1 &&
recordDiffAt < recordReExecAt &&
recordReExecAt < recordInstallAt &&
recordInstallAt < recordCpAt,
'the diff must be recorded in the parent arm before the env -i re-exec, and copied into the recreated root-only home',
);
// The scrubbed child must never re-run git under env -i: the ordering
// pin uses first-match semantics, so it cannot by itself forbid a
// second git in the child. Strip comments first (the child's own docs
// name `git diff` when describing what NOT to do) before asserting.
assert.doesNotMatch(
recordStep.run.slice(recordReExecAt).replace(/^\s*#.*$/gm, '').replace(/\\\n/g, ' '),
recordStep.run
.slice(recordReExecAt)
.replace(/^\s*#.*$/gm, '')
.replace(/\\\n/g, ' '),
/\bgit\b[^\n]*\b(diff|log|show|whatchanged)\b/,
'the scrubbed child must never re-run git under env -i — that is the failure shape of run 32227155960',
);
Expand Down Expand Up @@ -3846,12 +3882,19 @@ describe('qwen-triage: flakiness gate — behavioral, under the production wrapp
timeout: 30_000,
},
);
assert.equal(res.status, 0, `the gate refusal is fail-open: ${res.stderr}`);
assert.equal(
res.status,
0,
`the gate refusal is fail-open: ${res.stderr}`,
);
const outputs = Object.fromEntries(
readFileSync(out, 'utf8')
.split('\n')
.filter((l) => l.includes('='))
.map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)]),
.map((l) => [
l.slice(0, l.indexOf('=')),
l.slice(l.indexOf('=') + 1),
]),
);
assert.equal(outputs.flake_verdict, 'error');
assert.match(
Expand Down Expand Up @@ -4218,7 +4261,11 @@ describe('qwen-triage: flakiness gate — behavioral, under the production wrapp
undefined,
'no verdict may be written on a poisoned startup',
);
assert.equal(counts('a.test.js'), 0, 'no round may run on a poisoned startup');
assert.equal(
counts('a.test.js'),
0,
'no round may run on a poisoned startup',
);
});

it('a BASH_FUNC_exec%% import cannot skip the env -i re-exec — bash refuses it at startup', () => {
Expand Down Expand Up @@ -4246,7 +4293,11 @@ describe('qwen-triage: flakiness gate — behavioral, under the production wrapp
undefined,
'no verdict may be written on a poisoned startup',
);
assert.equal(counts('a.test.js'), 0, 'the body must never run on a poisoned startup');
assert.equal(
counts('a.test.js'),
0,
'the body must never run on a poisoned startup',
);
});

it('a same-stem sibling (X.test.tsx next to changed X.test.ts) runs in ONE merged group, never attributed separately', () => {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/.size-baseline
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
9256 build-and-publish-image.yml
49610 cd-cua-driver.yml
2076 cd-mobile-mcp.yml
111021 ci.yml
128681 ci.yml
1482 codeql.yml
9389 comment-attachment-guard.yml
31677 desktop-release.yml
Expand Down
Loading
Loading