ci: route pane close and retirement changes to the close specs (#21572)

Every terminal-pane route named what BINDS a pane — the pty transports, the ssh
reconnect ledgers, the park watchers. None named what unbinds one. #21005
changed the pane close and retirement lifecycle across three transports, and
replaying its fifteen paths through the selector returns [] with
--reusable-workflow false: it merged with E2E skipped outright. #21001, the same
seam a week earlier, ran E2E only because it happened to also touch an
ssh-named file, so the close specs were never selected even then.

Unbinding is the half that strands a PTY or leaves a retired leaf mounted as a
blank pane, so the three specs that judge it now gate it: the parked-tab close
retirement, the split-pane close layout consistency, and the paired client's
view of a leaf the host retired.

Scope held deliberately narrow. The route is not an SSH source route, so it does
not start a Docker relay; close reaches SSH only through the shared provider the
non-Docker specs already cover. runtime-rpc-client.ts is left out although
#21005 touched it: it carries no close decision and churns about three times as
often as these files, so routing on it would run this lane on unrelated runtime
work. Replaying twenty merged PRs shows exactly one selection change, #21001.
This commit is contained in:
Neil
2026-09-18 23:26:01 -07:00
committed by GitHub
parent 2a53293b11
commit d35e1dcba8
2 changed files with 99 additions and 0 deletions
@@ -0,0 +1,74 @@
import { describe, expect, it } from 'vitest'
import { existsSync } from 'node:fs'
import { join, resolve } from 'node:path'
import {
hasSshSourceChange,
PR_E2E_SOURCE_ROUTES,
selectPrE2eSpecs,
shouldRunReusablePrE2e
} from './pr-e2e-source-routing.mjs'
const projectDir = resolve(import.meta.dirname, '../..')
/**
* The product paths of #21005, "retire captured remote handles when pending panes close". It
* changed the pane close and retirement lifecycle across three transports and matched no route
* at all, so it merged with E2E skipped outright. The routes named every module that BINDS a
* pane and none that unbinds one.
*/
const PENDING_PANE_CLOSE_CHANGE = [
'src/renderer/src/components/terminal-pane/retire-unbound-ipc-terminal-pane.ts',
'src/renderer/src/components/terminal-pane/retire-unbound-runtime-terminal-pane.ts',
'src/renderer/src/components/terminal-pane/terminal-pane-close-admission.ts',
'src/renderer/src/components/terminal-pane/terminal-pane-retirement-ownership.ts',
'src/renderer/src/components/terminal-pane/use-terminal-pane-close-actions.ts',
'src/renderer/src/store/terminals/terminal-tab-close-providers.ts'
]
const CLOSE_ROUTE = PR_E2E_SOURCE_ROUTES.find(
(route) => route.id === 'terminal-pane.close-and-retirement'
)
describe('pane close and retirement PR E2E routing', () => {
it('selects the close specs for a pane close lifecycle change', () => {
expect(selectPrE2eSpecs(PENDING_PANE_CLOSE_CHANGE)).toEqual([
'tests/e2e/paired-remote-split-pane-host-retired-ghost.spec.ts',
'tests/e2e/terminal-pane-close-layout-consistency.spec.ts',
'tests/e2e/terminal-parked-close-retirement.spec.ts'
])
expect(shouldRunReusablePrE2e(PENDING_PANE_CLOSE_CHANGE)).toBe(true)
})
it('routes each unbind authority on its own, not just as a group', () => {
expect(CLOSE_ROUTE, 'the close route was renamed or removed').toBeDefined()
// A single-file change is the shape that slipped through; assert per path rather than only
// on the union, where one matching path would hide five that do not.
for (const file of PENDING_PANE_CLOSE_CHANGE) {
expect(selectPrE2eSpecs([file]), file).toEqual(CLOSE_ROUTE.specs.toSorted())
}
})
it('keeps every routed spec and authority a real file', () => {
for (const spec of CLOSE_ROUTE.specs) {
expect(existsSync(join(projectDir, spec)), spec).toBe(true)
}
for (const file of PENDING_PANE_CLOSE_CHANGE) {
expect(existsSync(join(projectDir, file)), file).toBe(true)
}
})
it('leaves the Docker SSH lane and unrelated changes alone', () => {
// The lane costs a Docker relay per run. Close changes reach SSH only through the shared
// provider, which the non-Docker specs above already cover.
expect(hasSshSourceChange(PENDING_PANE_CLOSE_CHANGE)).toBe(false)
// Neighbours in the same directories that this route must not claim.
for (const file of [
'src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx',
'src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts',
'src/renderer/src/runtime/runtime-rpc-client.ts',
'src/renderer/src/store/terminals/terminal-tab-close-providers.test.ts'
]) {
expect(CLOSE_ROUTE.matches(file), file).toBe(false)
}
})
})
+25
View File
@@ -150,6 +150,31 @@ export const PR_E2E_SOURCE_ROUTES = [
file
)
},
{
// Why a route of its own: every other terminal-pane route names what BINDS a pane — the pty
// transports, the ssh reconnect ledgers, the park watchers. Nothing named what unbinds one,
// so the close/retire lifecycle reached main with e2e skipped outright. Unbinding is the half
// that can strand a PTY or leave a retired leaf mounted as a blank pane.
//
// Deliberately absent: src/renderer/src/runtime/runtime-rpc-client.ts, the transport these
// retirements call out through. It carries no close decision and churns ~3x these files, so
// routing on it would run this lane on unrelated runtime work.
id: 'terminal-pane.close-and-retirement',
specs: [
// Closing a tab whose pane is parked (never mounted) must retire that exact PTY.
'tests/e2e/terminal-parked-close-retirement.spec.ts',
// Closing one leaf of a split must leave root leaves, leaf→pty bindings, and live panes
// agreeing — the ghost-blank-pane shape a bad unbind produces.
'tests/e2e/terminal-pane-close-layout-consistency.spec.ts',
// The runtime half: a leaf the host retires must stop being mounted on a paired client.
'tests/e2e/paired-remote-split-pane-host-retired-ghost.spec.ts'
],
matches: (file) =>
isProductSource(file) &&
/^(?:src\/renderer\/src\/components\/terminal-pane\/(?:retire-unbound-(?:ipc|runtime)-terminal-pane|terminal-pane-(?:close-admission|close-identity|lifecycle-close|pane-closed|retirement-ownership)|use-terminal-pane-close-actions)|src\/renderer\/src\/store\/(?:terminals\/terminal-tab-close(?:-providers)?|slices\/(?:terminal-tab-retirement|terminal-retirement-teardown-reservation|retired-terminal-tab-state-sweep)))\.ts$/.test(
file
)
},
{
id: 'terminal-session.parked-cli-split',
specs: ['tests/e2e/terminal-parked-cli-split.spec.ts'],