From f3922f617e1e4ac9277c7473b800f4e76d3f5297 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo <269034524+mldangelo-oai@users.noreply.github.com> Date: Sat, 29 Aug 2026 23:00:06 -0700 Subject: [PATCH] ci: reduce test shards and redundant jobs --- .github/workflows/node-ci.yml | 24 ++++++++--------- sdk/typescript/TESTING.md | 11 +++++--- .../tests-ts/release-automation.test.ts | 16 ++---------- sdk/typescript/tests-ts/skeleton.test.ts | 26 +++++++++++-------- 4 files changed, 35 insertions(+), 42 deletions(-) diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index da46b8319..8bb25ee78 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -152,10 +152,6 @@ jobs: - name: Audit production dependencies continue-on-error: true run: pnpm --dir sdk/typescript run audit:prod - - name: Typecheck - run: pnpm --dir sdk/typescript run types - - name: Check formatting - run: pnpm --dir sdk/typescript run format - name: Pack working-directory: sdk/typescript run: pnpm pack --pack-destination ../../dist @@ -181,7 +177,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] - shard: [1, 2, 3] + shard: [1, 2] steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -219,6 +215,12 @@ jobs: run: | sudo apt-get update sudo apt-get install --yes ripgrep + - name: Typecheck + if: matrix.os == 'ubuntu-latest' && matrix.shard == 1 + run: pnpm --dir sdk/typescript run types + - name: Check formatting + if: matrix.os == 'ubuntu-latest' && matrix.shard == 1 + run: pnpm --dir sdk/typescript run format - name: Test timeout-minutes: 10 working-directory: sdk/typescript @@ -227,7 +229,7 @@ jobs: TMP: ${{ runner.temp }} TMPDIR: ${{ runner.temp }} CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: "false" - run: node scripts/run-ci-tests.mjs ${{ matrix.shard }}/3 ${{ matrix.os == 'ubuntu-latest' && '--coverage --coverage-reporter=text --coverage-reporter=lcov' || '' }} + run: node scripts/run-ci-tests.mjs ${{ matrix.shard }}/2 ${{ matrix.os == 'ubuntu-latest' && '--coverage --coverage-reporter=text --coverage-reporter=lcov' || '' }} - name: Upload test reports if: always() continue-on-error: true @@ -394,7 +396,7 @@ jobs: strategy: fail-fast: false matrix: - shard: [1, 2, 3, 4, 5, 6, 7] + shard: [1, 2, 3, 4, 5, 6] steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -433,7 +435,7 @@ jobs: TMP: ${{ steps.windows-temp.outputs.path }} TMPDIR: ${{ steps.windows-temp.outputs.path }} CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: "false" - run: node sdk/typescript/scripts/run-ci-tests.mjs ${{ matrix.shard }}/7 + run: node sdk/typescript/scripts/run-ci-tests.mjs ${{ matrix.shard }}/6 - name: Upload Windows test reports if: always() continue-on-error: true @@ -487,14 +489,10 @@ jobs: run: node scripts/check-package.mjs ../../dist/*.tgz windows: - name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} + name: windows-latest / node-22 runs-on: ubuntu-latest if: always() needs: [validate-title, windows-test, windows-verify] - strategy: - fail-fast: false - matrix: - node: ["22.13.0", "24"] steps: - name: Require every Windows coverage job if: needs.validate-title.result != 'success' || (needs.validate-title.outputs.ci-mode == 'full' && (needs.windows-test.result != 'success' || needs.windows-verify.result != 'success')) || (needs.validate-title.outputs.ci-mode != 'full' && needs.validate-title.outputs.ci-mode != 'markdown') diff --git a/sdk/typescript/TESTING.md b/sdk/typescript/TESTING.md index 261ce99c6..6c1047589 100644 --- a/sdk/typescript/TESTING.md +++ b/sdk/typescript/TESTING.md @@ -89,19 +89,22 @@ runtime still installs and inspects the package, including a strict NodeNext TypeScript consumer, the actual CLI, credential locking, dashboard assets, and a nested Codex worker. Native plugin-build tests remain in the shared Bun suite. -The full Bun suite runs once per OS under Node 22: three file shards on Linux -and macOS, and seven on Windows. The other Node versions run the installed +The full Bun suite runs once per OS under Node 22: two file shards on Linux +and macOS, and six on Windows. The other Node versions run the installed package checks instead of repeating the same Bun suite. MCP and Python tests run in separate required jobs. Python uses four isolated pytest-xdist workers with work stealing; worker crashes fail the run without automatic restarts. +Typechecking and formatting run once in the first Linux shard before its +tests. Other shards can start as soon as the package is ready. + `scripts/run-ci-tests.mjs` assigns the longest measured files first. Its `ci-test-durations.json` records per-file seconds from CI reports. Every new test file is included automatically with a one-second estimate. Refresh those estimates from the uploaded reports when adding or splitting expensive files; estimates affect scheduling, never whether a test runs. -To reproduce one Windows shard locally after building the plugin, run -`node scripts/run-ci-tests.mjs 3/7 --seed=12345`. +To reproduce one shard on Windows after building the plugin, run +`node scripts/run-ci-tests.mjs 3/6 --seed=12345`. Every Bun lane uploads JUnit; Linux lanes also upload LCOV per shard. Python reports include case durations, and the MCP runner can upload its JUnit report. diff --git a/sdk/typescript/tests-ts/release-automation.test.ts b/sdk/typescript/tests-ts/release-automation.test.ts index c8f11d8ca..e8fd54cb9 100644 --- a/sdk/typescript/tests-ts/release-automation.test.ts +++ b/sdk/typescript/tests-ts/release-automation.test.ts @@ -4026,25 +4026,13 @@ describe("GitHub release workflow safeguards", () => { } expect(workflow.jobs["required-test"]?.steps[0]?.run).toBe("exit 1"); - const renderName = (template: string, values: Record) => { - let name = template; - for (const [key, value] of Object.entries(values)) { - name = name.replaceAll("${{ matrix." + key + " }}", value); - } - return name.replace( - "${{ matrix.node == '22.13.0' && '22' || matrix.node }}", - values["node"] === "22.13.0" ? "22" : values["node"] ?? "", - ); - }; const unixJob = workflow.jobs["required-test"]; const windowsJob = workflow.jobs["windows"]; const fullNames = [ ...(unixJob?.strategy?.matrix["os"] ?? []).map((os) => - renderName(unixJob?.name ?? "", { os }), - ), - ...(windowsJob?.strategy?.matrix["node"] ?? []).map((node) => - renderName(windowsJob?.name ?? "", { node }), + (unixJob?.name ?? "").replace("${{ matrix.os }}", os), ), + windowsJob?.name ?? "", ]; const requiredContexts = new Set([ "ubuntu-latest / node-22", diff --git a/sdk/typescript/tests-ts/skeleton.test.ts b/sdk/typescript/tests-ts/skeleton.test.ts index 27fda31d6..28f99d38c 100644 --- a/sdk/typescript/tests-ts/skeleton.test.ts +++ b/sdk/typescript/tests-ts/skeleton.test.ts @@ -105,7 +105,7 @@ describe("TypeScript package skeleton", () => { const { jobs } = await workflow("node-ci.yml"); expect(jobs["test"]?.strategy?.matrix).toEqual({ os: ["ubuntu-latest", "macos-latest"], - shard: [1, 2, 3], + shard: [1, 2], }); expect(jobs["compatibility"]?.strategy?.matrix).toEqual({ os: ["ubuntu-latest"], @@ -113,7 +113,7 @@ describe("TypeScript package skeleton", () => { include: [{ os: "macos-latest", node: "22.13.0" }], }); expect(jobs["windows-test"]?.strategy?.matrix).toEqual({ - shard: [1, 2, 3, 4, 5, 6, 7], + shard: [1, 2, 3, 4, 5, 6], }); expect(jobs["windows-verify"]?.strategy?.matrix["node"]).toEqual([ "22.13.0", @@ -160,7 +160,7 @@ describe("TypeScript package skeleton", () => { expect(packageJson.scripts["test:ci"]).toContain("pnpm run test "); expect(jobs["windows-test"]?.steps).toContainEqual( expect.objectContaining({ - run: "node sdk/typescript/scripts/run-ci-tests.mjs ${{ matrix.shard }}/7", + run: "node sdk/typescript/scripts/run-ci-tests.mjs ${{ matrix.shard }}/6", }), ); }); @@ -229,15 +229,19 @@ describe("TypeScript package skeleton", () => { test("runs shared static checks once and keeps every diagnostic upload non-blocking", async () => { const { jobs } = await workflow("node-ci.yml"); const steps = Object.values(jobs).flatMap((job) => job.steps); - for (const name of [ - "Check plugin source boundary", - "Typecheck", - "Check formatting", - ]) { + for (const [name, job] of [ + ["Check plugin source boundary", "package"], + ["Typecheck", "test"], + ["Check formatting", "test"], + ] as const) { expect(steps.filter((step) => step.name === name)).toHaveLength(1); - expect(jobs["package"]!.steps.some((step) => step.name === name)).toBe( - true, - ); + const step = jobs[job]!.steps.find((step) => step.name === name); + expect(step).toBeDefined(); + if (job === "test") { + expect(step?.if).toBe( + "matrix.os == 'ubuntu-latest' && matrix.shard == 1", + ); + } } for (const name of [ "Upload test reports",