fix(ci): report swap and a df -h fallback in the lint lane failure dump (#15095)

* fix(ci): report swap and a df -h fallback in the lint lane's failure dump

The lint lane's state-at-failure dump asked /proc/meminfo for MemTotal and
MemAvailable only, and ran df -hT with no retry, while the three sibling dumps
in the same workflow grep SwapTotal|SwapFree and fall back to df -h. A
swap-exhausted death on the contended pool therefore uploaded an artifact that
could not show it, and a runner whose df has no -T uploaded no filesystem table
at all.

Witness it by executing the step body rather than pinning its text: the new case
stubs a df that rejects any option cluster carrying T, so the marker can only
come from the -h retry, and asserts both swap fields reach the artifact. Red
before (the artifact held MemTotal/MemAvailable only and no filesystem table),
green after.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin the retained dump fields and the three sibling dumps

R1-1: the swap/df-fallback case asserted only what the PR adds, so the
halves of the two edited lines it kept were unguarded. Collapsing the
retry to `df -h || true`, or reordering it to `df -h || df -hT || true`,
stayed green while the artifact permanently lost the filesystem Type
column; narrowing the grep to swap alone stayed green while the lane this
PR fixes stopped reporting RAM at failure. Give the stub's `-hT` arm its
own marker so "retried after -hT failed" is distinguishable from "never
tried -hT", and assert both RAM fields beside the swap ones.

R1-2: the same dump is replicated at four sites and nothing pinned them
equal, so dropping the swap fields or the `|| df -h` retry from test,
test_macos or test_windows shipped green. Pin the three siblings
textually, extending the sampler pin loop idiom already in this file --
textual rather than executed because test_windows greps /proc/meminfo,
which Git Bash lacks.

Test-only: .github/workflows/ci.yml is byte-unchanged by this commit
(md5 31b18cbbaeb407d9348e1f3f51f1c0b4). Verified with a nine-mutant
battery run one mutant at a time against ci.yml, restoring and md5-
checking after each: all nine are now killed, each by a named assertion.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvik9qbdof

---------

Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
易良andQwen-Coder authored and GitHub committed 2026-10-22 14:44:05 +08:00
1 parent 3efcf2adf7
commit d6abebb40c
2 files changed
+105 -2

No files matched your search

+103
View File
@@ -240,6 +240,109 @@ describe('ci.yml disk-pressure evidence', () => {
}
});
it('reports swap and falls back to df -h in the failure dump', () => {
// The three sibling state-at-failure dumps grep SwapTotal|SwapFree and
// retry `df -h` when `df -hT` is unavailable; this one did neither, so a
// swap-exhausted death on the contended pool uploaded an artifact that
// could not show it, and a runner whose df has no -T (busybox, minimal
// images) uploaded no filesystem table at all. Execute the step body
// instead of pinning its text: the grep pattern is only worth anything if
// the fields actually reach the artifact. The stub rejects any option
// cluster carrying T — `df -hT` arrives as ONE argument, so matching on
// `-T` alone would let it through and fake the fallback. That arm must stay
// ahead of `-h`, and must mark its own output: otherwise "retried after
// -hT failed" and "never tried -hT" are indistinguishable.
//
// The three sibling dumps carry the same two fields and no equality pin
// reaches them ('Dump disk state on failure' is this lane's alone). Pin
// them textually like the sampler loop above — not executed, because
// test_windows greps /proc/meminfo, which Git Bash lacks.
for (const jobName of ['test', 'test_macos', 'test_windows']) {
const leg = ciJobs[jobName].steps.find(
(candidate) => candidate.name === 'Run tests and generate reports',
);
assert.ok(leg, `missing run-tests step in ${jobName}`);
assert.match(
leg.run,
/df -hT .* \|\| df -h /,
`${jobName} lost the df -h retry`,
);
assert.match(leg.run, /SwapTotal\|SwapFree/, `${jobName} lost swap`);
}
const root = mkdtempSync(join(tmpdir(), 'ci-disk-pressure-'));
const df = join(root, 'df');
writeFileSync(
df,
'#!/usr/bin/env bash\ncase "$*" in\n *T*) echo STUB-DF-HT; exit 1 ;;\n *-h*) echo STUB-DF-H ;;\nesac\n',
);
chmodSync(df, 0o755);
try {
const result = spawnSync(
'bash',
[
'-e',
'-o',
'pipefail',
'-c',
lintStep('Dump disk state on failure').run,
],
{
encoding: 'utf8',
timeout: 30_000,
env: {
...process.env,
PATH: `${root}:${process.env.PATH}`,
RUNNER_TEMP: root,
},
},
);
assert.equal(result.error, undefined);
assert.equal(
result.status,
0,
`signal: ${result.signal}\nstdout: ${result.stdout}\nstderr: ${result.stderr}`,
);
const samples = readFileSync(
join(root, 'disk-pressure-samples.log'),
'utf8',
);
assert.match(
samples,
/^SwapTotal:/m,
`swap exhaustion is invisible in the artifact\nstdout: ${result.stdout}`,
);
assert.match(samples, /^SwapFree:/m);
// All four fields share the one edited grep line, so a pattern narrowed
// to swap alone must go red here. `MemAvailable` has to keep the exact
// spelling the sampler's `awk '/MemAvailable/'` uses, or the two stop
// being comparable.
assert.match(
samples,
/^MemTotal:/m,
`RAM total is invisible in the artifact\nstdout: ${result.stdout}`,
);
assert.match(samples, /^MemAvailable:/m, 'RAM available is invisible');
// -hT must be ATTEMPTED, not merely tolerated: a `df -h || true`
// collapse or a reorder still yields STUB-DF-H, silently losing the
// filesystem Type column.
assert.match(
samples,
/^STUB-DF-HT$/m,
`df -hT was never attempted first\nstdout: ${result.stdout}`,
);
assert.match(
samples,
/^STUB-DF-H$/m,
`df -hT failed and nothing retried with -h\nstdout: ${result.stdout}`,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it('keeps install failure status while writing the pre-install sample', () => {
const root = mkdtempSync(join(tmpdir(), 'ci-disk-pressure-'));
// The install step runs `corepack pnpm install`; stub corepack so the
+2 -2
View File
@@ -1380,9 +1380,9 @@ jobs:
DISK_SAMPLES="${RUNNER_TEMP}/disk-pressure-samples.log"
{
echo "DISKCONTEXT failure-dump $(date -u +%Y-%m-%dT%H:%M:%SZ)"
df -hT || true
df -hT || df -h || true
df -i || true
grep -E 'MemTotal|MemAvailable' /proc/meminfo || true
grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo || true
} 2>/dev/null | tee -a "$DISK_SAMPLES" || true
# Collector for the #10035 telemetry, kept as the LAST step: failure()
# is evaluated when the step is reached and never revisited, so at its