From 0f623732bddfa27542a86cfc1c5fb34ee9e9a195 Mon Sep 17 00:00:00 2001 From: Ysopwnable <161784452+N0zoM1z0@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:16:09 +0800 Subject: [PATCH] fix(ci): require portable inventory reports on selected runs (#3549) Run complete portable-inventory validation whenever code tests are selected and the job is not cancelled. Missing timing reports now fail rather than bypassing the existing validator. Preserve partial diagnostics and successful-history ownership. Verified the baseline regression, missing/present report and cancellation/selection controls, actual Vitest blob/report failure paths, static checks, actionlint and test discovery. Fixes #3542 Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .../boundary/filesystem/ciTestReports.test.ts | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bacfcf7fc7..25627d4b9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -433,7 +433,7 @@ jobs: --outputFile.junit=test-results.xml --outputFile.json=test-results.json - name: Summarize slowest test files - if: needs.changes.outputs.code == 'true' && !cancelled() && hashFiles('test-timings.json') != '' + if: needs.changes.outputs.code == 'true' && !cancelled() env: SHARD_RESULT: ${{ needs.test-shard.result }} SOURCE_REQUIRED: ${{ needs.changes.outputs.tier == 'fast' }} diff --git a/tests/boundary/filesystem/ciTestReports.test.ts b/tests/boundary/filesystem/ciTestReports.test.ts index 48ba18c114..233d7c8eb6 100644 --- a/tests/boundary/filesystem/ciTestReports.test.ts +++ b/tests/boundary/filesystem/ciTestReports.test.ts @@ -140,13 +140,18 @@ function conditionValue( condition: string | undefined, needs: Record, canceled: boolean, + timingReportExists = false, ) { if (!condition) throw new Error("Missing report collection condition"); return runInNewContext( condition .replaceAll("needs.test-shard", 'needs["test-shard"]') .replaceAll("needs.test-source", 'needs["test-source"]'), - { needs, cancelled: () => canceled }, + { + needs, + cancelled: () => canceled, + hashFiles: () => (timingReportExists ? "timing-report" : ""), + }, { timeout: 1_000 }, ); } @@ -207,6 +212,28 @@ it("collects either fast test group while keeping full reports tied to full shar ); }); +it("requires portable report validation even when the timing report is missing", async () => { + const summary = (await workflow()).test.steps.find((step) => + step.run?.includes("scripts/ci/test-report.mjs"), + ); + for (const tier of ["fast", "full"]) { + for (const timingReportExists of [false, true]) { + for (const code of ["true", "false"]) { + for (const canceled of [false, true]) { + expect( + conditionValue( + summary?.if, + { changes: { outputs: { code, tier } } }, + canceled, + timingReportExists, + ), + ).toBe(code === "true" && !canceled); + } + } + } + } +}); + it("keeps diagnosis available after merge failure and publishes only complete successful history", async () => { const steps = (await workflow()).test!.steps; const diagnosis = steps.find((step) => step.with?.name === "test-results"); @@ -270,6 +297,16 @@ it("rejects successful shard reports with missing or duplicate portable files", readFile(join(root, ".cache/ci-test-history/test-results.json")), ).rejects.toMatchObject({ code: "ENOENT" }); } + await run(["tests/first.test.ts", "tests/second.test.ts"]); + await rm(join(root, "test-timings.json")); + await expect( + executeTimingReport(root, { CI_VERIFY_TEST_INVENTORY: "true" }), + ).rejects.toMatchObject({ + stderr: expect.stringContaining("ENOENT"), + }); + await expect( + readFile(join(root, ".cache/ci-test-history/test-results.json")), + ).rejects.toMatchObject({ code: "ENOENT" }); }); it("runs the existing onboarding and Python producer guards from their selected CI owners", async () => {