| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| import assert from 'node:assert/strict'; | |
| import { existsSync, readFileSync } from 'node:fs'; | |
| import test from 'node:test'; | |
| import { | |
| collectWorkspaceSourceClosure, | |
| windowsPackageSourceEntrypoints, | |
| windowsReleasePatternCoversSource, | |
| } from './windows-package-source-closure.mjs'; | |
| import { readPullRequestPathFilter } from './workflow-pull-request-paths.mjs'; | |
| /** | |
| * The `packages/` half of this lane's filter that the import closure does not | |
| * account for. Each entry schedules a 25-minute Windows runner on its own, so | |
| * it has to earn that here rather than sit indistinguishable among the twenty | |
| * derived entries — which is the only way a rotted one is ever noticed. | |
| */ | |
| const UNDERIVED_PACKAGE_PATTERNS = new Set([ | |
| // Regenerated and diffed by `check:release`, and consumed by the packaging | |
| // step rather than imported by the worker. | |
| 'packages/cli/RUNTIME_HOST_PEER_DEPENDENCIES.rust.tsv', | |
| 'packages/cli/RUNTIME_HOST_PEER_THIRD_PARTY_NOTICES.txt', | |
| // Candidate election runs in the packaged app, not in the worker closure, and | |
| // has broken this path before. | |
| 'packages/runtime-host/src/client/connect-or-spawn.ts', | |
| 'packages/runtime-host/src/client/launcher.ts', | |
| // Builds the worker the closure starts from, so it precedes rather than joins | |
| // it. | |
| 'packages/runtime/scripts/build-filesystem-worker.mjs', | |
| ]); | |
| test('the Windows package trigger is exactly its closure plus declared exceptions', async () => { | |
| const closure = await collectWorkspaceSourceClosure(windowsPackageSourceEntrypoints); | |
| const patterns = readPullRequestPathFilter('release-windows-check.yml'); | |
| assert.ok(patterns.length > 0, 'release-windows-check.yml declares no pull_request.paths'); | |
| const missing = closure.filter( | |
| (sourcePath) => | |
| !patterns.some((pattern) => windowsReleasePatternCoversSource(sourcePath, pattern)), | |
| ); | |
| for (const expected of [ | |
| 'packages/core/src/absolute-path.ts', | |
| 'packages/core/src/sandbox-boundary.ts', | |
| 'packages/core/src/serialized-byte-length.ts', | |
| 'packages/core/src/windows-path.ts', | |
| 'packages/runtime/src/child-fd-input.ts', | |
| 'packages/runtime/src/child-process-lifecycle.ts', | |
| 'packages/runtime/src/process-tree-terminator.ts', | |
| ]) { | |
| assert.ok(closure.includes(expected), `closure omitted ${expected}`); | |
| } | |
| assert.deepEqual(missing, []); | |
| // The other direction, which containment alone cannot see: a pattern backed | |
| // by nothing is dead weight that still books a Windows runner every time it | |
| // matches, and nothing reports it. Exceptions are declared above, so this | |
| // fails both when the closure stops reaching an entry and when a stale | |
| // exception outlives its reason. | |
| const underived = patterns | |
| .filter((pattern) => pattern.startsWith('packages/')) | |
| .filter((pattern) => !closure.some((path) => windowsReleasePatternCoversSource(path, pattern))) | |
| .sort(); | |
| assert.deepEqual(underived, [...UNDERIVED_PACKAGE_PATTERNS].sort()); | |
| }); | |
| test('the Windows package workflow path list has no duplicate entries', () => { | |
| const patterns = readPullRequestPathFilter('release-windows-check.yml'); | |
| assert.equal(new Set(patterns).size, patterns.length); | |
| }); | |
| /** | |
| * This belongs beside the other closure contract rather than with the planner | |
| * tests, because computing a closure needs esbuild and the planner tests run | |
| * before `npm ci` installs it. `.github/workflows/windows-recovery.yml` is in | |
| * `RELEASE_CONTRACT_FILES` so that editing the filter selects the gate that | |
| * checks it. | |
| */ | |
| test('the recovery filter is exactly the Windows-branching closure of its tests', async () => { | |
| const workflow = readFileSync( | |
| new URL('../.github/workflows/windows-recovery.yml', import.meta.url), | |
| 'utf8', | |
| ); | |
| const filtered = readPullRequestPathFilter('windows-recovery.yml') | |
| .filter((path) => path.startsWith('packages/')) | |
| .sort(); | |
| // Derived from the dist tests the steps actually execute, mapped back to | |
| // source. A suite added to a step joins this set without anyone remembering | |
| // to widen the filter. | |
| // The separator class matches the backslash form too, because these steps run | |
| // under pwsh where both are legal — a suite spelled with backslashes would | |
| // otherwise be dropped from the set that decides the filter, which is the | |
| // fail-open direction. | |
| const entrypoints = [ | |
| ...new Set( | |
| [ | |
| ...workflow.matchAll( | |
| /packages[/\\]([\w-]+)[/\\]dist[/\\]__tests__[/\\]([\w.-]+)\.test\.js/gu, | |
| ), | |
| ].map(([, workspace, name]) => `packages/${workspace}/src/__tests__/${name}.test.ts`), | |
| ), | |
| ].sort(); | |
| assert.ok(entrypoints.length > 0, 'no executed suite was recognised'); | |
| for (const entrypoint of entrypoints) { | |
| assert.ok(existsSync(new URL(`../${entrypoint}`, import.meta.url)), entrypoint); | |
| } | |
| // Set equality, not containment, and in both directions on purpose. A subset | |
| // check cannot see an omission, which is how `stable-storage.ts` — reached | |
| // through the two listed lock authorities — sat outside a filter that was | |
| // supposed to cover exactly it. A superset check cannot see a file that | |
| // stopped branching and now schedules a Windows runner for nothing. | |
| // | |
| // `win32` is the criterion because it is the one a machine can check, not | |
| // because it names every Windows-only path: `git-worktree-child-executor.ts` | |
| // carries a genuine Windows workaround with no such literal. A branch written | |
| // without it stays outside this filter, and the lane's schedule is what | |
| // observes it. | |
| const closure = await collectWorkspaceSourceClosure(entrypoints); | |
| const windowsBranching = closure | |
| .filter((path) => | |
| readFileSync(new URL(`../${path}`, import.meta.url), 'utf8').includes('win32'), | |
| ) | |
| .sort(); | |
| assert.deepEqual(filtered, windowsBranching); | |
| }); |