From 262a61703a588b75b9f979cd80aeadfc855cac93 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:06:24 -0700 Subject: [PATCH] ci(e2e): start the managed-SSH lane for every helper only its specs import (#30233) * ci(e2e): route every helper only managed-SSH specs import to their lane, by import graph * ci(e2e): normalize import-closure paths to / on Windows --- config/scripts/e2e-test-import-closure.mjs | 31 +++++++++++++++ .../pr-e2e-managed-ssh-harness-route.test.mjs | 39 +++++++++++++++++++ .../scripts/pr-e2e-route-contracts.test.mjs | 26 ++----------- config/scripts/pr-e2e-source-routing.mjs | 7 +++- 4 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 config/scripts/e2e-test-import-closure.mjs create mode 100644 config/scripts/pr-e2e-managed-ssh-harness-route.test.mjs diff --git a/config/scripts/e2e-test-import-closure.mjs b/config/scripts/e2e-test-import-closure.mjs new file mode 100644 index 00000000000..061460a1cc4 --- /dev/null +++ b/config/scripts/e2e-test-import-closure.mjs @@ -0,0 +1,31 @@ +import { existsSync, readFileSync } from 'node:fs' +import { dirname, join, relative, sep } from 'node:path' + +const RELATIVE_IMPORT = /(?:from|import)\s*\(?\s*'(\.[^']+)'/g + +/** The entry files plus every `tests/` file they import, transitively through relative imports. */ +export function testImportClosure(projectDir, entries) { + const seen = new Set() + const pending = [...entries] + while (pending.length > 0) { + const file = pending.pop() + if (seen.has(file)) { + continue + } + seen.add(file) + const source = readFileSync(join(projectDir, file), 'utf8') + for (const [, specifier] of source.matchAll(RELATIVE_IMPORT)) { + // Repo-relative with `/` on every platform, so `tests/` prefixes and route regexes apply. + const base = relative(projectDir, join(projectDir, dirname(file), specifier)) + .split(sep) + .join('/') + const resolved = [base, `${base}.ts`, `${base}.tsx`].find( + (candidate) => /\.tsx?$/.test(candidate) && existsSync(join(projectDir, candidate)) + ) + if (resolved?.startsWith('tests/')) { + pending.push(resolved) + } + } + } + return [...seen] +} diff --git a/config/scripts/pr-e2e-managed-ssh-harness-route.test.mjs b/config/scripts/pr-e2e-managed-ssh-harness-route.test.mjs new file mode 100644 index 00000000000..ca34a257598 --- /dev/null +++ b/config/scripts/pr-e2e-managed-ssh-harness-route.test.mjs @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest' +import { readdirSync } from 'node:fs' +import { join, resolve, sep } from 'node:path' +import { testImportClosure } from './e2e-test-import-closure.mjs' +import { selectPrE2eSpecs } from './pr-e2e-source-routing.mjs' + +const projectDir = resolve(import.meta.dirname, '../..') +const MANAGED_SSH_SPEC = /^tests\/e2e\/ssh-orcad-.*\.spec\.ts$/ + +const allSpecs = readdirSync(join(projectDir, 'tests/e2e'), { encoding: 'utf8', recursive: true }) + .map((name) => `tests/e2e/${name.split(sep).join('/')}`) + .filter((file) => file.endsWith('.spec.ts')) + +/** Test files only managed-SSH specs import: nothing else runs when they change. */ +function managedSshOnlyHarness() { + const importers = new Map() + for (const spec of allSpecs) { + for (const file of testImportClosure(projectDir, [spec])) { + if (file !== spec) { + importers.set(file, [...(importers.get(file) ?? []), spec]) + } + } + } + return [...importers] + .filter(([, specs]) => specs.every((spec) => MANAGED_SSH_SPEC.test(spec))) + .map(([file]) => file) + .sort() +} + +describe('managed-SSH harness route contract', () => { + it('starts the managed-SSH lane for every harness file only its specs import', () => { + const harness = managedSshOnlyHarness() + expect(harness.length).toBeGreaterThan(5) + const unrouted = harness.filter( + (file) => !selectPrE2eSpecs([file]).some((spec) => MANAGED_SSH_SPEC.test(spec)) + ) + expect(unrouted).toEqual([]) + }) +}) diff --git a/config/scripts/pr-e2e-route-contracts.test.mjs b/config/scripts/pr-e2e-route-contracts.test.mjs index 5510788f620..0d60073f58b 100644 --- a/config/scripts/pr-e2e-route-contracts.test.mjs +++ b/config/scripts/pr-e2e-route-contracts.test.mjs @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { existsSync, readFileSync } from 'node:fs' -import { dirname, join, relative, resolve } from 'node:path' +import { join, resolve } from 'node:path' import { parse } from 'yaml' import { classifyE2eJobs, @@ -12,6 +12,7 @@ import { selectGeneralE2eSpecs } from './ci-e2e-job-selection.mjs' import { classifyPrJobs } from './pr-code-change-scope.mjs' +import { testImportClosure } from './e2e-test-import-closure.mjs' import { isLayoutOracleSource, LAYOUT_ORACLE_E2E_SPECS } from './pr-layout-oracle-route.mjs' import { hasNativeImeSourceChange, @@ -180,28 +181,7 @@ describe('pr-e2e-layout-oracle-routing', () => { ] /** Every test file the oracle specs import, transitively: a change there can break the gate. */ - function oracleHarnessFiles() { - const seen = new Set() - const pending = [...LAYOUT_ORACLE_E2E_SPECS] - while (pending.length > 0) { - const file = pending.pop() - if (seen.has(file)) { - continue - } - seen.add(file) - const source = readFileSync(join(projectDir, file), 'utf8') - for (const [, specifier] of source.matchAll(/(?:from|import)\s*\(?\s*'(\.[^']+)'/g)) { - const base = relative(projectDir, join(projectDir, dirname(file), specifier)) - const resolved = [base, `${base}.ts`, `${base}.tsx`].find( - (candidate) => /\.tsx?$/.test(candidate) && existsSync(join(projectDir, candidate)) - ) - if (resolved?.startsWith('tests/')) { - pending.push(resolved) - } - } - } - return [...seen] - } + const oracleHarnessFiles = () => testImportClosure(projectDir, LAYOUT_ORACLE_E2E_SPECS) describe('workspace layout oracle merge gate', () => { it('routes each layout authority and stack writer on its own', () => { diff --git a/config/scripts/pr-e2e-source-routing.mjs b/config/scripts/pr-e2e-source-routing.mjs index 43698d561ea..aa066124dcc 100644 --- a/config/scripts/pr-e2e-source-routing.mjs +++ b/config/scripts/pr-e2e-source-routing.mjs @@ -20,9 +20,12 @@ const NATIVE_IME_PRODUCT_SOURCE = const NATIVE_IME_HARNESS = /^(?:config\/scripts\/focus-nested-wayland-terminal\.sh$|config\/scripts\/(?:run-terminal-ibus-hangul-e2e|terminal-ime-engagement-receipt)\.mjs$|tests\/e2e\/terminal-ime-(?:boundary-probe|byte-reader|engagement-receipt)\.ts$|tests\/e2e\/terminal-(?:ibus-hangul|hangul-terminating-digit|macos-2set-korean)-native\.spec\.ts$)/ -/** Managed-SSH harness helpers; every managed-SSH spec runs in one job, so any route may claim them. */ +/** + * Managed-SSH harness helpers; every managed-SSH spec runs in one job, so any route may claim them. + * `pr-e2e-managed-ssh-harness-route.test.mjs` fails when a helper only these specs import is missing. + */ const MANAGED_SSH_HARNESS = - /^tests\/e2e\/helpers\/(?:orcad-managed-workspace|orcad-convert-(?:flow|host)|orcad-template-variant|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/ + /^tests\/e2e\/helpers\/(?:orcad-managed-workspace|orcad-convert-(?:flow|host|teardown)|orcad-template-variant|orcad-upgrade-(?:profile|retained-state)|docker-ssh-host-target|reserve-local-port|ssh-remote-only-browser-fixture|windows-host-orcad-processes)\.ts$/ export const PR_E2E_SOURCE_ROUTES = [ {