From cd4c917fcf9b992ab0d579a6a7652961c428efd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Sat, 31 Oct 2026 15:35:03 +0800 Subject: [PATCH] fix(ci): route helper-test tmpdir off the host /tmp tmpfs (#15979) * fix(ci): route helper-test tmpdir off the host /tmp tmpfs * test(ci): pin the helper-battery tmpdir routing against comments Two gaps in the #15979 change: - The pin used toContain/indexOf on the raw run block, so prefixing the export with `# ` disabled the routing while the guard stayed green. Filter comment lines before asserting, like the lint-history pin already does, and pin the private dir + EXIT trap the step now creates. - The step routed TMPDIR straight into the shared RUNNER_TEMP root. Copy the unit lanes' second half too: mktemp -d under RUNNER_TEMP and reap it on EXIT, so a cancelled battery does not leave its fixture tree behind (its own after() hook does not run on a signal). Co-authored-by: Qwen-Coder Patrol-Run: qwen-pr-closeout/jmvvyiud548 --------- Co-authored-by: Qwen-Coder --- .github/workflows/ci.yml | 14 +++++++++++++ scripts/tests/ci-platform-lanes.test.js | 26 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d4a31bb416..4cd1ecc2b90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1476,6 +1476,20 @@ jobs: - name: 'Run .github/scripts helper tests' if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" run: |- + # Route os.tmpdir() off the host /tmp tmpfs: its inode quota is + # small and shared across the pool, and e2e-build.test.mjs creates + # thousands of chunk files there (#15965 ENOSPC with 202G free). + # Same routing as the unit lanes (#10035), private per-run dir + # included: RUNNER_TEMP is shared, and a cancelled battery never + # reaches its own after() cleanup, so the fixture tree would stick + # around in _work/_temp. A hard kill stays uncovered either way. + export TMPDIR="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" + HELPER_TMPDIR="$(mktemp -d "${TMPDIR}/helper-tests-XXXXXX" 2>/dev/null)" || HELPER_TMPDIR="" + if [ -n "$HELPER_TMPDIR" ]; then + TMPDIR="$HELPER_TMPDIR" + export TMPDIR + trap 'rm -rf "$HELPER_TMPDIR"' EXIT + fi node --test --test-concurrency=1 ${{ env.HELPER_TESTS }} 2>&1 | tee "${RUNNER_TEMP}/helper-attempt1.log" || { node --test --test-concurrency=1 ${{ env.HELPER_TESTS }} && echo "::warning::helper tests failed once and the retry absorbed it; attempt-1 failures: $(grep -E '^not ok' "${RUNNER_TEMP}/helper-attempt1.log" | head -5 | paste -sd ';' -) — count this toward the #12772 helper-battery recurrence threshold" diff --git a/scripts/tests/ci-platform-lanes.test.js b/scripts/tests/ci-platform-lanes.test.js index ec253fe0ff1..76222796d97 100644 --- a/scripts/tests/ci-platform-lanes.test.js +++ b/scripts/tests/ci-platform-lanes.test.js @@ -1258,6 +1258,32 @@ describe('GitHub helper tests', () => { } }); + it('routes the battery tmpdir off the host /tmp tmpfs', () => { + // #15965: e2e-build.test.mjs mkdtemps thousands of chunk files via + // os.tmpdir(); on the ECS pool that defaulted to the shared /tmp tmpfs, + // whose ~1M inode quota filled up and failed the lane with ENOSPC while + // 202G of capacity stayed free. RUNNER_TEMP sits on the roomy data disk, + // same routing the unit lanes already use (#10035). + const step = (ci.jobs.lint_and_static.steps ?? []).find( + (candidate) => candidate.name === 'Run .github/scripts helper tests', + ); + expect(step, 'helper-tests step missing').toBeDefined(); + // Comments stripped: the rationale above the export names it too, so a pin + // satisfied by prose would stay green with the routing commented out. + const run = String(step.run) + .split('\n') + .filter((line) => !line.trim().startsWith('#')) + .join('\n'); + expect(run).toContain('export TMPDIR="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"'); + expect(run.indexOf('export TMPDIR')).toBeLessThan( + run.indexOf('node --test'), + ); + // The routed dir is private to the run and reaped on exit: RUNNER_TEMP + // itself is shared with every other job on the host. + expect(run).toContain('mktemp -d "${TMPDIR}/helper-tests-XXXXXX"'); + expect(run).toContain('trap \'rm -rf "$HELPER_TMPDIR"\' EXIT'); + }); + it('retries the full-profile battery once against pool contention', () => { // #12772: the battery spawns real subprocess loops on the shared ECS // pool, and a push to main has no flaky-rerun patrol — one host hiccup