mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
test: cover keyboard input in five simultaneously flooding SSH panes
This commit is contained in:
@@ -267,6 +267,7 @@ jobs:
|
||||
if: >-
|
||||
inputs.test_files == '' ||
|
||||
inputs.ssh_source_changed == 'true' ||
|
||||
contains(inputs.test_files, 'tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts') ||
|
||||
contains(inputs.test_files, 'tests/e2e/ssh-startup-exec-readiness.spec.ts') ||
|
||||
contains(inputs.test_files, 'tests/e2e/paired-startup-exec-readiness.spec.ts')
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -18046,10 +18046,12 @@
|
||||
"invariant": "Transport loss and frozen-host silence must preserve the remote session; host relay loss may rebind a pane without accumulating reattachable leases. Reconnects must preserve usable terminal content, bounded PTYs/fds/processes, complete large listings, and independently recoverable watcher processes. Electron test shutdown must release inherited pipes after confirmed root exit without closing live-process pipes.",
|
||||
"oracle": "Poll a changed connected SSH authority after injected faults, then require terminal output and appropriate PTY identity. Read remote process/fd state, listFiles replies, and rendered explorer rows. Resolve Playwright cleanup only after the root process exits and its inherited pipes close; live-process pipes remain untouched.",
|
||||
"commands": [
|
||||
"ORCA_E2E_SSH_DOCKER=1 SKIP_BUILD=1 pnpm exec playwright test tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts --config tests/playwright.config.ts --project=electron-headless --workers=1",
|
||||
"ORCA_E2E_SSH_DOCKER=1 SKIP_BUILD=1 pnpm exec playwright test tests/e2e/ssh-docker-transport-drop-recovery.spec.ts tests/e2e/ssh-docker-half-open-link.spec.ts tests/e2e/ssh-docker-quick-open-large-listing.spec.ts tests/e2e/ssh-docker-reconnect-pane-restore.spec.ts tests/e2e/ssh-docker-resource-accumulation.spec.ts tests/e2e/ssh-docker-watcher-isolation.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts tests/e2e/helpers/electron-process-shutdown.unit.test.ts"
|
||||
],
|
||||
"testFiles": [
|
||||
"tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts",
|
||||
"tests/e2e/ssh-docker-transport-drop-recovery.spec.ts",
|
||||
"tests/e2e/ssh-docker-half-open-link.spec.ts",
|
||||
"tests/e2e/ssh-docker-quick-open-large-listing.spec.ts",
|
||||
@@ -18059,6 +18061,12 @@
|
||||
"tests/e2e/helpers/electron-process-shutdown.unit.test.ts"
|
||||
],
|
||||
"assertionRefs": [
|
||||
{
|
||||
"file": "tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts",
|
||||
"assertions": [
|
||||
"five distinct SSH PTYs acknowledge actual keyboard input after two rendered hide/reopen cycles while all five producers flood"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "tests/e2e/ssh-docker-transport-drop-recovery.spec.ts",
|
||||
"assertions": [
|
||||
@@ -18145,6 +18153,7 @@
|
||||
"Collect CI runtime and flake history plus product red/green evidence before blocking."
|
||||
],
|
||||
"knownGaps": [
|
||||
"Five-pane simultaneous flood input is newly registered and awaiting CI evidence; it does not resolve the separate #16764 wall-clock performance oracle.",
|
||||
"The disconnected 48MB flood still loses its relay channel: original post-flood input marker failed in 60s, and waiting for the finite producer completion marker failed in 120s. It remains an explicit #18018 fixme reproduction; frozen-host input is re-enabled after four successful runs.",
|
||||
"Linux and Windows desktop clients, WSL, folder workspaces, paired runtimes and live agent CLIs are not exercised by these Docker specs.",
|
||||
"Some legacy assertions inspect terminal serialization or backing state rather than rendered DOM; no blanket visual coverage claim.",
|
||||
|
||||
@@ -54,10 +54,9 @@ if (runtime.status !== 0) {
|
||||
// the product. Same rule as ssh-docker-relay-perf above. Gating needs a distribution
|
||||
// first, then a host-relative oracle; a bigger constant, or a ratio picked from three
|
||||
// samples, is the same arbitrary number in different clothes.
|
||||
// COVERAGE GAP, recorded as such: 5 simultaneously flooding SSH panes exercise writer
|
||||
// saturation, ACK/credit accounting and per-pane polling together, and nothing else covers
|
||||
// that combination. Flip `test.fixme` back to `test` to run it. Tracked in
|
||||
// stablyai/orca#16764.
|
||||
// The five-pane-input-under-flood spec below checks functional keyboard delivery during
|
||||
// simultaneous output and hide/reopen cycles; its initial CI validation is pending.
|
||||
// It does not replace this timing oracle. Performance coverage remains a gap in #16764.
|
||||
//
|
||||
// Why both projects: ssh-port-forward-lifecycle is @headful, which the headless project
|
||||
// grep-inverts away.
|
||||
@@ -87,6 +86,7 @@ const result = spawnSync(
|
||||
'tests/e2e/ssh-ai-vault-session-history.spec.ts',
|
||||
'tests/e2e/ssh-cold-activation-restore.spec.ts',
|
||||
'tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts',
|
||||
'tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts',
|
||||
'tests/e2e/ssh-docker-half-open-link.spec.ts',
|
||||
'tests/e2e/ssh-docker-quick-open-large-listing.spec.ts',
|
||||
'tests/e2e/ssh-docker-reconnect-pane-restore.spec.ts',
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { expect, test } from './helpers/orca-app'
|
||||
import {
|
||||
cleanupDockerSshRelayTarget,
|
||||
DOCKER_SSH_RELAY_REMOTE_REPO_PATH,
|
||||
startDockerSshRelayTarget
|
||||
} from './helpers/docker-ssh-relay-target'
|
||||
import { connectDockerSshRelayTarget } from './helpers/docker-ssh-relay-connection'
|
||||
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
||||
import {
|
||||
execInTerminal,
|
||||
focusActiveTerminalInput,
|
||||
focusLastTerminalPane,
|
||||
splitActiveTerminalPane,
|
||||
waitForActivePanePtyId,
|
||||
waitForActiveTerminalManager,
|
||||
waitForTerminalOutput
|
||||
} from './helpers/terminal'
|
||||
import { readPaneIdentitySnapshot } from './helpers/terminal-pane-identity'
|
||||
import { quotePosixShell } from '../../src/shared/wsl-login-shell-command'
|
||||
|
||||
function floodWithInputAcknowledgements(marker: string): string {
|
||||
const script = [
|
||||
`const marker=${JSON.stringify(marker)}`,
|
||||
"const padding='S'.repeat(2048)",
|
||||
"let input='', ack='', sequence=0, blocked=false",
|
||||
"process.stdin.setEncoding('utf8')",
|
||||
"process.stdin.on('data', chunk => { input+=chunk; let end; while((end=input.indexOf('\\n'))>=0) { ack=input.slice(0,end).trim(); input=input.slice(end+1); } })",
|
||||
"process.stdout.on('drain', () => { blocked=false })",
|
||||
"setInterval(() => { if(!blocked) blocked=!process.stdout.write(marker+':'+(++sequence)+':ACK='+ack+':'+padding+'\\n'); },8)"
|
||||
].join(';')
|
||||
return `node -e ${quotePosixShell(script)}`
|
||||
}
|
||||
|
||||
test.describe('five SSH panes under simultaneous output', () => {
|
||||
test.skip(process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Requires the Docker SSH target')
|
||||
|
||||
test('each pane acknowledges keyboard input after hiding and reopening the flooding workspace', async ({
|
||||
orcaPage,
|
||||
registerPostElectronShutdownCleanup
|
||||
}, testInfo) => {
|
||||
test.setTimeout(420_000)
|
||||
const target = startDockerSshRelayTarget(testInfo)
|
||||
registerPostElectronShutdownCleanup(async () => cleanupDockerSshRelayTarget(target))
|
||||
await waitForSessionReady(orcaPage)
|
||||
await waitForActiveWorktree(orcaPage)
|
||||
await connectDockerSshRelayTarget(orcaPage, target, {
|
||||
remotePath: DOCKER_SSH_RELAY_REMOTE_REPO_PATH
|
||||
})
|
||||
await ensureTerminalVisible(orcaPage, 45_000)
|
||||
await waitForActiveTerminalManager(orcaPage, 60_000)
|
||||
const runId = randomUUID()
|
||||
const owners: { leafId: string; ptyId: string; marker: string }[] = []
|
||||
for (let index = 0; index < 5; index++) {
|
||||
if (index > 0) {
|
||||
await splitActiveTerminalPane(orcaPage, 'vertical')
|
||||
await focusLastTerminalPane(orcaPage)
|
||||
}
|
||||
const ptyId = await waitForActivePanePtyId(orcaPage, 30_000)
|
||||
const identity = await readPaneIdentitySnapshot(orcaPage)
|
||||
expect(identity?.activeLeafId).toBeTruthy()
|
||||
const marker = `FLOOD_${runId}_${index}`
|
||||
owners.push({ leafId: identity!.activeLeafId!, ptyId, marker })
|
||||
await execInTerminal(orcaPage, ptyId, floodWithInputAcknowledgements(marker))
|
||||
await waitForTerminalOutput(orcaPage, `${marker}:`, 60_000, 80_000)
|
||||
}
|
||||
expect(new Set(owners.map((owner) => owner.ptyId)).size).toBe(5)
|
||||
const identity = await readPaneIdentitySnapshot(orcaPage)
|
||||
expect(identity?.panes).toHaveLength(5)
|
||||
const tabId = identity!.tabId
|
||||
const visibleTerminals = orcaPage.locator('.xterm:visible')
|
||||
await expect(visibleTerminals).toHaveCount(5)
|
||||
|
||||
for (let round = 0; round < 2; round++) {
|
||||
await orcaPage.evaluate(() => window.__store!.getState().setActiveView('tasks'))
|
||||
await expect
|
||||
.poll(() => orcaPage.evaluate(() => window.__store!.getState().activeView))
|
||||
.toBe('tasks')
|
||||
await expect(visibleTerminals).toHaveCount(0)
|
||||
await orcaPage.evaluate(() => window.__store!.getState().setActiveView('terminal'))
|
||||
await expect(visibleTerminals).toHaveCount(5)
|
||||
await waitForActiveTerminalManager(orcaPage, 60_000)
|
||||
for (const [index, owner] of owners.entries()) {
|
||||
await orcaPage.evaluate(
|
||||
({ tabId, leafId }) => {
|
||||
const manager = window.__paneManagers!.get(tabId)!
|
||||
const paneId = manager.getNumericIdForLeaf(leafId)
|
||||
if (paneId == null) {
|
||||
throw new Error(`Flood pane ${leafId} did not remount`)
|
||||
}
|
||||
manager.setActivePane(paneId, { focus: true })
|
||||
},
|
||||
{ tabId, leafId: owner.leafId }
|
||||
)
|
||||
expect(await waitForActivePanePtyId(orcaPage)).toBe(owner.ptyId)
|
||||
await focusActiveTerminalInput(orcaPage)
|
||||
const input = `input_${runId}_${round}_${index}`
|
||||
await orcaPage.keyboard.type(input)
|
||||
await orcaPage.keyboard.press('Enter')
|
||||
// The remote process repeats its latest ACK, so flood eviction cannot hide it.
|
||||
await waitForTerminalOutput(orcaPage, `:ACK=${input}:`, 30_000, 80_000)
|
||||
await waitForTerminalOutput(orcaPage, `${owner.marker}:`, 30_000, 80_000)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user