Files
56fec7f3f5 ci: shard the Linux unit tests and web-shell smoke, and reuse cached builds (#16133)
* ci: shard the Linux unit-test lane eight ways behind one build

The Linux Test job ran 45-59 minutes: a ten-minute prepare build, then the CLI and core suites back to back. A new test_build job runs the prepare build once and uploads its outputs; eight shards install without the build, unpack them and run --shard=N/8 of every Vitest suite. Test (ubuntu-latest, Node 22.x) becomes an aggregate that passes only when the build and every shard pass.

* ci: record ci.yml's growth from the test_build prelude

test_build carries the same pre-checkout cleanup, checkout and install prelude as the test shards; ci-platform-lanes.test.js pins the two copies identical.

* ci: record the Linux lane's two new jobs in the PR-stage budget

test_build and the test_ubuntu aggregate add two jobs; the declared timeouts drop from 305 to 295 minutes.

* ci: shard the web-shell E2E smoke four ways

The smoke lane ran 44-46 minutes on every recent PR, about 33 of them in the browser tests at two workers, and is the longest lane once the Linux unit tests are sharded. Each shard runs --shard=N/4 of the smoke; the transcript gate runs on the first. web-shell E2E Smoke (ubuntu-latest, Node 22.x) becomes an aggregate over the shards.

* ci: bound the unit shards' tree download and retry it once

A pool host stalled 17 minutes in download-artifact while the other shards took under a minute.

* ci: rebuild only the workspaces a change reaches

scripts/ci-build-cache.mjs hashes each workspace's tracked build inputs (tests excluded) with its workspace dependencies' hashes and the shared root inputs. test_build and the web-shell smoke shards restore the newest cached build and rebuild only workspaces whose hash changed. A push to main always builds from scratch and saves, so a pull request starts from a clean build of main.

* docs(ci): describe the incremental build cache

* ci: keep the build cache path the same on every pool host

actions/cache versions an entry by its path; under runner.temp the path differs per host, so a run could never restore another host's save.

* ci: save the ARM64 build cache from the smoke lane, and run the aggregates on the shards' runners

The smoke shards run on the ARM64 pool, which test_build may not, so the first shard saves the clean build it made when its restore found nothing. The two aggregate checks queued 9 and 13 minutes for a hosted runner after their shards finished; they now take the shards' runners.

* ci: bound the sharded test step and skip shards when the build fails

Address the first automated review round on the Linux sharding lane:

- Gate every build-consuming shard step on `test_build` success. `!cancelled()`
  stays true when a needed job fails, so a failed build still started all eight
  shards, each paying a full install before dying at the missing artifact and
  reporting artifact plumbing as its failing step.
- Drop the test step cap to 45 minutes. The job cap is 60, so the old 110 could
  never fire; a step timeout keeps the diagnostic steps below it alive while a
  job timeout does not.
- Keep the built-tree artifact for a week so a late "re-run failed jobs" still
  finds it, and correct the trigger comment that still named three post-merge
  jobs when five now accept push.

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

* ci: keep disk telemetry for a failed build and correct the design doc

`test_build` installs with the disk sampler and then builds for up to its
whole timeout, but the sampler dies with the install step's trap and no
collector ran, so an ENOSPC build (or the OOM that took a shard host) kept
no evidence. Give it the same failure-gated dump and last-step upload as
`lint_and_static`, under its own artifact name.

The incremental-lint design still said the test job keeps normal prepare
behaviour; this lane now installs with `QWEN_SKIP_PREPARE=1` and unpacks
the tree built once by `test_build`. Correct both language versions.

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

* ci: share the built-tree definition and the build preflight, harden the guards

The archive step re-implemented `builtPaths()` from `scripts/ci-build-cache.mjs`,
so the tree the shards unpack had a second definition that could drift from the
one `pack` stores. A `paths` mode now prints that list for `tar -T -`, and a new
test pins the four artifact-name literals that tie the producer to the eight
consumers.

The cached branch of `build` also hand-reproduced a subset of `scripts/build.js`
and so skipped its ink-patch preflight: the same missing
`frame-controller.d.ts` self-healed on a cache miss and surfaced as ~50 tsc
errors inside ink's declarations on a hit. The preflight is now a function
reachable as `scripts/build.js --preflight` and runs before both branches.

The remaining review suggestions were all meta-test gaps: the standalone-runtime
expression and the `test_build`/`test` prelude were guarded by nothing that
compares them in order, `ci-build-cache.mjs`'s three CI modes were never
executed by a test, and the infra-classification filter's step name was not
joined to `ci.yml`. Each now has an assertion that goes red when the guard is
removed.

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

* ci: bound the build steps so the failure collectors can run

Round-2 review found four issues in the previous round's fixes and one gap
in the new build tests.

A job timeout cancels the job and skips the failure-gated collectors, so the
`test_build` steps that can block now carry caps below the job's 45 minutes:
the install, the build, the pack and the archive. The install caps are
identical across `test_build`, `test` and `lint_and_static` so the prelude
parity guard still compares the three copies field for field. A test pins
every blocking cap under the job ceiling.

The `--preflight` early exit was only asserted as a string, so it could be
renamed with nothing going red while the cache-hit path silently ran the
full build. A spawned case now copies `scripts/build.js` into the fixture
and asserts the flag exits without generate, a workspace build or the
schema step.

The CLI mode tests write extension-less `#!/bin/sh` stubs and join PATH with
':', which cmd.exe cannot resolve; the Windows lane collects that file and
downgrades its failure to a warning, so it would have carried a permanently
red block. The suite is excluded on win32 alongside the other bash-fixture
suites.

The `test_build`/`test` intersection grew to 16 with the disk collector, but
the floor stayed at 15, leaving exactly one shared step uncompared; the floor
now matches the membership. The cache-hit-with-changes branch — the code
that decides what is rebuilt and what is deleted — gains a case: a changed
workspace is purged and rebuilt by directory, and an untouched workspace
keeps what the cache restored.

ci.yml's recorded size baseline is updated to the current file so the next
PR gets the full growth allowance back.

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

* test(ci): keep cache fixture call logs outside the archived tree

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

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 14:52:12 +08:00

75 lines
3.7 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { configDefaults, defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['scripts/tests/**/*.test.{js,ts}'],
// Script tests that drive Linux-only CI (ubuntu-latest workflow jobs, or
// bash/shell fixtures Windows cannot express) fail on a Windows runner.
// Linux CI remains their authoritative coverage.
exclude:
process.platform === 'win32'
? [
...configDefaults.exclude,
'scripts/tests/e2e-shard-retry.test.js',
'scripts/tests/security-checks-audit-retry.test.js',
'scripts/tests/pr-self-report-label.test.js',
// Bash-driven workflow suites cannot run on Windows; pure
// YAML-parse workflow suites still do.
'scripts/tests/qwen-*-workflow.test.js',
'scripts/tests/serve-ab-workflow.test.js',
// Drives a bash script that manages process groups.
'scripts/tests/rocketmq-lite-broker-script.test.js',
// Drives a fixture repository through extension-less `#!/bin/sh`
// stubs joined onto PATH with ':'; cmd.exe resolves through
// PATHEXT and would run the real corepack/node instead.
'scripts/tests/ci-build-cache.test.js',
]
: [...configDefaults.exclude],
setupFiles: ['scripts/tests/test-setup.ts'],
// Several suites spawn the real `corepack pnpm`; on a cold per-run cache
// every worker would download the pinned pnpm concurrently, and a losing
// install poisons the shared cache for the rest of the run (#12436).
// Warm it once here, before any worker forks.
globalSetup: ['scripts/tests/corepack-warmup.js'],
// Several tests in install-script.test.js shell out to `node` to run
// create-standalone-package.js, which on Windows runs a full
// tar+gzip pass under antivirus inspection. Real runtimes observed on
// Windows CI: 4780ms / 1666ms / 1079ms — the 4.8s one is right at
// vitest's 5s default and flakes. Bump the suite timeout so a single
// slow subprocess startup doesn't fail an otherwise-healthy test run.
//
// 30s then proved to be the quiet-host figure. On the shared pool the
// same work runs about 5x slower, and release run 33725742855 lost its
// Quality Checks (Scripts) job to two files at once —
// qwen-autofix-workflow.test.js, whose heaviest case measures ~14s idle,
// and acp-serve-boundary-guard.test.js — neither of them slow, both past
// 30s under contention. Per-test `vi.setConfig` does not help: these
// cases register their timeout at collection, before it runs.
// `||`, not `??`: `??` only catches `undefined`, and the value this repo
// actually plants is `''` — that is what `${{ cond && 'x' || '' }}` renders
// when the condition is false. `Number('')` is 0, and vitest reads 0 as
// "no timeout at all", so the empty spelling would silently disarm every
// ceiling in this suite. `NaN` from a typo falls back the same way.
testTimeout: Number(process.env['QWEN_SCRIPTS_TEST_TIMEOUT_MS']) || 90_000,
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
},
// No poolOptions override: the fixed 8-16 worker floor it used to carry
// oversubscribes the 3-core macOS runners. Vitest's default scales with
// the host cores, which is what every other suite in this repository
// uses.
//
// RPC-timeout exemption; see scripts/tests/unit-vitest-configs.test.ts.
dangerouslyIgnoreUnhandledErrors: process.platform !== 'linux',
},
});