diff --git a/.github/workflows/windows-scoped-tests.yml b/.github/workflows/windows-scoped-tests.yml index 1d5816b00..4320c025a 100644 --- a/.github/workflows/windows-scoped-tests.yml +++ b/.github/workflows/windows-scoped-tests.yml @@ -28,7 +28,10 @@ name: Windows Scoped Tests # # The windows-scoped-tests job is the check to read: it passes when nothing # is in scope (no Windows job ran) and otherwise requires the Windows job, so -# a skipped run still reports success if this ever becomes a required check. +# a skipped run reports success. It is not one of the required checks, but +# gstack-ship-queue ready and land refuse a head with any failed check, so a +# red Windows job blocks the pull request it runs on: a change to a curated +# test file has to pass on Windows. # gstack-pregate reads a job keyed on a dispatch input as manual, never a PR # obligation: this check runs on the pull request itself. A workflow_dispatch # run with the files input reruns chosen Windows-safe files on a branch. diff --git a/changelog.d/gsta258-l3-windows-by-path.md b/changelog.d/gsta258-l3-windows-by-path.md index ff2b2e838..f82be1b87 100644 --- a/changelog.d/gsta258-l3-windows-by-path.md +++ b/changelog.d/gsta258-l3-windows-by-path.md @@ -1,8 +1,10 @@ -**Windows checks now run by path.** Windows Setup E2E, the 10-to-25-minute install test, no longer starts its Windows jobs on a pull request whose only install change is `package.json`'s version line, which the merge-train stamp puts on every pull request before it merges. Of the 61 pull requests in its last 100 runs, 37 changed no install file besides `package.json`. Its path list also gains `bun.lock` and the `bin/` scripts that the test or `./setup` runs. A new `Windows Scoped Tests` workflow runs the Windows-safe test files a pull request changed, on one Windows job, when the pull request touches path, file-name or process code, such as a hook script or a new `spawnSync` call. On the last 120 commits to main it would have run on 50, with a median of one test file. It catches both Windows breaks the plan names: /freeze's path check (#3758) and a test that started Bun with `--config=/dev/null` (#3829). `windows-smoke` still runs on every pull request, and the full Windows suite still runs nightly. +**Windows checks now run by path.** Windows Setup E2E, the 10-to-25-minute install test, no longer starts its Windows jobs on a pull request whose only install change is `package.json`'s version line, which the merge-train stamp puts on every pull request before it merges. Of the 61 pull requests in its last 100 runs, 37 changed no install file besides `package.json`. Its path list also gains `bun.lock` and the `bin/` scripts that the test or `./setup` runs. A new `Windows Scoped Tests` workflow runs the Windows-safe test files a pull request changed, on one Windows job, when the pull request touches path, file-name or process code, such as a hook script or a new `spawnSync` call. On the last 120 commits to main it would have run on 50, with a median of one test file. It catches both Windows breaks the plan names: /freeze's path check (#3758) and a test that started Bun with `--config=/dev/null` (#3829). A red run blocks its pull request like any failed check, so this release also fixes #3829's test, which has failed every nightly Windows run since it landed. `windows-smoke` still runs on every pull request, and the full Windows suite still runs nightly. ### CI - `.github/workflows/windows-setup-e2e.yml`: a Linux `windows-scope` job runs first and skips both Windows jobs when the only install change is `package.json`'s `"version"` line. Paths add `bun.lock`, `bin/gstack-config`, `bin/gstack-patch-names` and the scripts `./setup` sources or runs. - `.github/workflows/windows-scoped-tests.yml` (new): a Linux scope job reads the diff's file names and changed code lines, picks the changed files in the curated Windows-safe subset, and a `windows-latest` job runs them. The `windows-scoped-tests` check passes when nothing is in scope, so a skipped run still reports success. A `workflow_dispatch` with a `files` input reruns chosen Windows-safe files on a branch. ### Tests +- `test/review-risk-linear.test.ts`: the rules-hash case starts Bun with `bunIsolationArgs()` (`--config=NUL` on native Windows) instead of `--config=/dev/null`, which native Windows Bun cannot open (nightly Windows Free Tests run 41420490350, EINVAL). +- `scripts/test-pr-profile.ts`: the new workflow is listed as free-lane only beside the other Windows workflows (test/free-fixtures.test.ts requires every workflow to be classified). - `test/windows-path-scope-workflow.test.ts`: runs both scope steps in a two-commit git repository. The filter catches #3758's and #3829's changed files and passes over a docs-and-stamp change and a pure-logic change. The Setup E2E scope skips a version-only `package.json` bump and runs on a dependency change. `windows-smoke` stays unfiltered, and no job that can skip is a required check. diff --git a/docs/support-tiers.md b/docs/support-tiers.md index 33c1fb0aa..4450a8af2 100644 --- a/docs/support-tiers.md +++ b/docs/support-tiers.md @@ -26,7 +26,7 @@ That is the whole promise. Other skills, optional parts such as `/cso`'s native - `windows-smoke` is the required Windows check on pull requests. - The six-shard Windows free suite (the `Windows Free Tests` workflow, `windows-free-tests`) runs nightly on main, or when someone starts it by hand, and never blocks a pull request. - A red nightly run gets a fix only when it shows an install, startup or core-workflow break. -- Windows checks run by path (ship-90-90 plan D, approved 2026-11-03): `Windows Scoped Tests` runs a pull request's changed Windows-safe test files on one Windows job only when it touches path, file-name or process code, and `Windows Setup E2E` runs only when install files change (a version-only `package.json` change doesn't count). +- Windows checks run by path (ship-90-90 plan D, approved 2026-11-03): `Windows Scoped Tests` runs a pull request's changed Windows-safe test files on one Windows job only when it touches path, file-name or process code (a red run blocks that pull request, like any failed check), and `Windows Setup E2E` runs only when install files change (a version-only `package.json` change doesn't count). ## What doesn't change diff --git a/scripts/test-pr-profile.ts b/scripts/test-pr-profile.ts index dd0ca92bc..b552b8825 100644 --- a/scripts/test-pr-profile.ts +++ b/scripts/test-pr-profile.ts @@ -295,6 +295,7 @@ export const FREE_ONLY_PR_FILES = [ '.github/workflows/version-gate.yml', '.github/workflows/windows-free-tests.yml', '.github/workflows/windows-pregate-e2e.yml', + '.github/workflows/windows-scoped-tests.yml', '.github/workflows/windows-setup-e2e.yml', '.github/workflows/windows-smoke.yml', ] as const; diff --git a/test/review-risk-linear.test.ts b/test/review-risk-linear.test.ts index 7caf71633..ef19c7f19 100644 --- a/test/review-risk-linear.test.ts +++ b/test/review-risk-linear.test.ts @@ -7,6 +7,7 @@ import { spawnSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; +import { bunIsolationArgs } from '../lib/bun-isolation'; import { DATA_LINE, MONEY_LINE, SECRET_LINE, SECURITY_LINE, matchRisk } from '../lib/review-risk'; const ROOT = path.join(import.meta.dir, '..'); @@ -51,7 +52,7 @@ describe('the rules hashes cover every new pattern', () => { fs.symlinkSync(path.join(ROOT, 'lib/ship-policy.ts'), path.join(dir, 'ship-policy.ts')); fs.writeFileSync(path.join(dir, 'print.ts'), "import { DEPTH_RULES } from './review-depth';\nconsole.log(DEPTH_RULES);\n"); const hash = () => { - const r = spawnSync(process.execPath, ['--no-env-file', '--config=/dev/null', path.join(dir, 'print.ts')], { encoding: 'utf8', timeout: 30_000 }); + const r = spawnSync(process.execPath, [...bunIsolationArgs(), path.join(dir, 'print.ts')], { encoding: 'utf8', timeout: 30_000 }); expect(r.status, r.stderr).toBe(0); return r.stdout.trim(); }; diff --git a/test/windows-path-scope-workflow.test.ts b/test/windows-path-scope-workflow.test.ts index a990bc530..ff90e94cb 100644 --- a/test/windows-path-scope-workflow.test.ts +++ b/test/windows-path-scope-workflow.test.ts @@ -17,7 +17,7 @@ * The filter and scope steps run here as the runner would run them: bash in a * two-commit git repository, with HEAD^1 as the pull request's base. */ -import { describe, expect, test } from 'bun:test'; +import { afterAll, describe, expect, test } from 'bun:test'; import { spawnSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as os from 'node:os'; @@ -84,8 +84,12 @@ function parseOutputs(text: string): Record { interface StepRun { status: number | null; stderr: string; outputs: Record; summary: string; temp: string } +const TEMPS: string[] = []; +afterAll(() => { for (const dir of TEMPS) fs.rmSync(dir, { recursive: true, force: true }); }); +const newTemp = () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'windows-scope-temp-')); TEMPS.push(dir); return dir; }; + /** Run one workflow step's script under bash with a fresh GITHUB_OUTPUT and summary; temp is RUNNER_TEMP, kept for a later step. */ -function runStep(script: string, cwd: string, env: Record = {}, temp = fs.mkdtempSync(path.join(os.tmpdir(), 'windows-scope-temp-'))): StepRun { +function runStep(script: string, cwd: string, env: Record = {}, temp = newTemp()): StepRun { const output = path.join(temp, 'github-output'); const summary = path.join(temp, 'github-summary'); fs.writeFileSync(output, ''); @@ -243,6 +247,14 @@ describe('the path-scoped Windows shard on pull requests', () => { } const CURATED = ['test/hook-scripts.test.ts', 'test/review-risk-linear.test.ts', 'test/review-risk-routing.test.ts']; + test.skipIf(!CAN_SELECT)('the select step reads the real curated plan: a changed Windows-safe test is picked, a POSIX-only one is not', () => { + const temp = newTemp(); + fs.writeFileSync(path.join(temp, 'changed-files'), 'freeze/bin/check-freeze.sh\ntest/hook-scripts.test.ts\ntest/windows-path-scope-workflow.test.ts\n'); + const select = runStep(selectScript, ROOT, { PATH: `${toShell(path.dirname(process.execPath))}:${process.env.PATH}` }, temp); + expect(select.status, select.stderr).toBe(0); + expect(select.outputs.files).toBe('test/hook-scripts.test.ts'); + }, 60_000); + test.skipIf(!CAN_SELECT)('#3758: the hook paths put it in scope and the Windows job runs test/hook-scripts.test.ts', () => { const dir = prRepo(PR_3758); try {