mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 00:02:54 +00:00
427 lines
13 KiB
TypeScript
427 lines
13 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
PtyPendingDataDrainQueue,
|
|
type PendingPtyData,
|
|
type PtyPendingDataDrainSelection
|
|
} from './pty-pending-data-drain-queue'
|
|
|
|
function pending(data: string): PendingPtyData {
|
|
return { data }
|
|
}
|
|
|
|
function createQueueState() {
|
|
const active = new Set<string>()
|
|
const blocked = new Set<string>()
|
|
const queue = new PtyPendingDataDrainQueue((id) =>
|
|
blocked.has(id) ? 'blocked' : active.has(id) ? 'active' : 'background'
|
|
)
|
|
return { active, blocked, queue }
|
|
}
|
|
|
|
function takeId(
|
|
queue: PtyPendingDataDrainQueue,
|
|
round: ReturnType<PtyPendingDataDrainQueue['beginRound']>
|
|
): string | null {
|
|
return queue.takeNext(round)?.id ?? null
|
|
}
|
|
|
|
describe('PtyPendingDataDrainQueue', () => {
|
|
it('drains active IDs first while preserving Map order within each lane', () => {
|
|
const { active, queue } = createQueueState()
|
|
queue.set('background-1', pending('b1'))
|
|
queue.set('active-1', pending('a1'))
|
|
queue.set('background-2', pending('b2'))
|
|
queue.set('active-2', pending('a2'))
|
|
active.add('active-1')
|
|
active.add('active-2')
|
|
queue.invalidateAll()
|
|
|
|
const round = queue.beginRound()
|
|
const order: string[] = []
|
|
for (;;) {
|
|
const selection = queue.takeNext(round)
|
|
if (!selection) {
|
|
break
|
|
}
|
|
order.push(selection.id)
|
|
queue.remove(selection)
|
|
}
|
|
queue.endRound(round)
|
|
|
|
expect(order).toEqual(['active-1', 'active-2', 'background-1', 'background-2'])
|
|
})
|
|
|
|
it('defers partial remainders beyond the current round frontier without replacing the node', () => {
|
|
const { queue } = createQueueState()
|
|
queue.set('pty-1', { data: 'firsttail', startSeq: 10 })
|
|
const initialDebug = queue.getDebugSnapshot()
|
|
|
|
const firstRound = queue.beginRound()
|
|
const selection = queue.takeNext(firstRound)
|
|
expect(selection).toMatchObject({ id: 'pty-1', pending: { data: 'firsttail', startSeq: 10 } })
|
|
queue.replaceWithRemainder(selection!, { data: 'tail', startSeq: 15 })
|
|
expect(queue.takeNext(firstRound)).toBeNull()
|
|
queue.endRound(firstRound)
|
|
|
|
const secondRound = queue.beginRound()
|
|
const remainder = queue.takeNext(secondRound)!
|
|
expect(remainder).toBe(selection)
|
|
expect(remainder).toMatchObject({
|
|
id: 'pty-1',
|
|
pending: { data: 'tail', startSeq: 15 }
|
|
})
|
|
queue.remove(remainder)
|
|
queue.endRound(secondRound)
|
|
|
|
expect(queue.getDebugSnapshot()).toMatchObject({
|
|
pendingSize: 0,
|
|
createdNodeCount: initialDebug.createdNodeCount,
|
|
peakNodeCount: 1,
|
|
allocationIdsByPty: {}
|
|
})
|
|
})
|
|
|
|
it('defers a reentrant append to an unvisited ID and preserves its Map position', () => {
|
|
const { queue } = createQueueState()
|
|
queue.set('a', pending('a'))
|
|
queue.set('b', pending('b'))
|
|
queue.set('c', pending('c'))
|
|
|
|
const firstRound = queue.beginRound()
|
|
const first = queue.takeNext(firstRound)!
|
|
expect(first.id).toBe('a')
|
|
queue.remove(first)
|
|
queue.set('b', pending('b+reentrant'))
|
|
expect(queue.getDebugSnapshot().allocationIdsByPty.b).toBe(2)
|
|
|
|
const currentRoundOrder: string[] = []
|
|
for (;;) {
|
|
const selection = queue.takeNext(firstRound)
|
|
if (!selection) {
|
|
break
|
|
}
|
|
currentRoundOrder.push(selection.id)
|
|
queue.remove(selection)
|
|
}
|
|
queue.endRound(firstRound)
|
|
|
|
expect(currentRoundOrder).toEqual(['c'])
|
|
expect([...queue.keys()]).toEqual(['b'])
|
|
const secondRound = queue.beginRound()
|
|
expect(queue.takeNext(secondRound)).toMatchObject({
|
|
id: 'b',
|
|
pending: { data: 'b+reentrant' }
|
|
})
|
|
queue.endRound(secondRound)
|
|
expect(queue.getDebugSnapshot().createdNodeCount).toBe(3)
|
|
})
|
|
|
|
it('rebuilds an earlier reentrant update before an untouched later ID in Map order', () => {
|
|
const { queue } = createQueueState()
|
|
queue.set('trigger', pending('trigger'))
|
|
queue.set('earlier', pending('old'))
|
|
queue.set('later', pending('later'))
|
|
|
|
const firstRound = queue.beginRound()
|
|
queue.remove(queue.takeNext(firstRound)!)
|
|
queue.set('earlier', pending('new'))
|
|
queue.endRound(firstRound)
|
|
|
|
const secondRound = queue.beginRound()
|
|
const order: string[] = []
|
|
for (;;) {
|
|
const selection = queue.takeNext(secondRound)
|
|
if (!selection) {
|
|
break
|
|
}
|
|
order.push(selection.id)
|
|
queue.remove(selection)
|
|
}
|
|
queue.endRound(secondRound)
|
|
|
|
expect(order).toEqual(['earlier', 'later'])
|
|
})
|
|
|
|
it('keeps aggregate pending chars exact across every owner transition', () => {
|
|
const { blocked, queue } = createQueueState()
|
|
queue.set('a', pending('abc'))
|
|
queue.set('b', pending('12345'))
|
|
expect(queue.totalPendingChars).toBe(8)
|
|
|
|
queue.set('a', pending('abcdef'))
|
|
expect(queue.totalPendingChars).toBe(11)
|
|
blocked.add('a')
|
|
queue.invalidate('a')
|
|
const blockedRound = queue.beginRound()
|
|
const b = queue.takeNext(blockedRound)!
|
|
queue.replaceWithRemainder(b, pending('12'))
|
|
queue.endRound(blockedRound)
|
|
expect(queue.totalPendingChars).toBe(8)
|
|
|
|
blocked.delete('a')
|
|
queue.reactivateBlocked()
|
|
const removalRound = queue.beginRound()
|
|
const first = queue.takeNext(removalRound)!
|
|
queue.remove(first)
|
|
queue.endRound(removalRound)
|
|
expect(queue.totalPendingChars).toBe(2)
|
|
|
|
expect(queue.delete('b')).toEqual(pending('12'))
|
|
expect(queue.totalPendingChars).toBe(0)
|
|
queue.set('c', pending('tail'))
|
|
queue.clear()
|
|
expect(queue.getDebugSnapshot()).toMatchObject({
|
|
pendingSize: 0,
|
|
totalPendingChars: 0
|
|
})
|
|
})
|
|
|
|
it('relinks only when the exact derived settings token changes', () => {
|
|
const settings = {
|
|
terminalMainSideEffectAuthority: true,
|
|
terminalHiddenDeliveryGate: true,
|
|
unrelated: 0
|
|
}
|
|
const queue = new PtyPendingDataDrainQueue(
|
|
() => 'background',
|
|
() => settings.terminalMainSideEffectAuthority && settings.terminalHiddenDeliveryGate
|
|
)
|
|
queue.set('a', pending('a'))
|
|
const firstRound = queue.beginRound()
|
|
queue.endRound(firstRound)
|
|
const baseline = queue.getDebugSnapshot().laneRebuildCount
|
|
|
|
settings.unrelated += 1
|
|
const unrelatedRound = queue.beginRound()
|
|
queue.endRound(unrelatedRound)
|
|
expect(queue.getDebugSnapshot().laneRebuildCount).toBe(baseline)
|
|
|
|
settings.terminalHiddenDeliveryGate = false
|
|
const disabledRound = queue.beginRound()
|
|
queue.endRound(disabledRound)
|
|
expect(queue.getDebugSnapshot().laneRebuildCount).toBe(baseline + 1)
|
|
|
|
settings.terminalMainSideEffectAuthority = false
|
|
settings.terminalHiddenDeliveryGate = true
|
|
const equivalentRound = queue.beginRound()
|
|
queue.endRound(equivalentRound)
|
|
expect(queue.getDebugSnapshot().laneRebuildCount).toBe(baseline + 1)
|
|
|
|
settings.terminalMainSideEffectAuthority = true
|
|
const enabledRound = queue.beginRound()
|
|
queue.endRound(enabledRound)
|
|
expect(queue.getDebugSnapshot().laneRebuildCount).toBe(baseline + 2)
|
|
})
|
|
|
|
it('does not follow a detached successor after reentrant delete or clear', () => {
|
|
const deleted = createQueueState().queue
|
|
deleted.set('a', pending('a'))
|
|
deleted.set('b', pending('b'))
|
|
deleted.set('c', pending('c'))
|
|
const deleteRound = deleted.beginRound()
|
|
const first = deleted.takeNext(deleteRound)!
|
|
deleted.remove(first)
|
|
deleted.delete('b')
|
|
expect(takeId(deleted, deleteRound)).toBe('c')
|
|
deleted.endRound(deleteRound)
|
|
|
|
const cleared = createQueueState().queue
|
|
cleared.set('a', pending('a'))
|
|
cleared.set('b', pending('b'))
|
|
const clearRound = cleared.beginRound()
|
|
cleared.remove(cleared.takeNext(clearRound)!)
|
|
cleared.clear()
|
|
expect(cleared.takeNext(clearRound)).toBeNull()
|
|
cleared.endRound(clearRound)
|
|
expect(cleared.getDebugSnapshot()).toMatchObject({
|
|
pendingSize: 0,
|
|
activeRunnableSize: 0,
|
|
backgroundRunnableSize: 0,
|
|
blockedSize: 0,
|
|
activeHeadId: null,
|
|
activeTailId: null,
|
|
backgroundHeadId: null,
|
|
backgroundTailId: null,
|
|
blockedHeadId: null,
|
|
blockedTailId: null,
|
|
openRound: null,
|
|
activeFrontier: 0,
|
|
backgroundFrontier: 0,
|
|
allocationIdsByPty: {}
|
|
})
|
|
})
|
|
|
|
it('reuses nodes through partial, update, and relink churn', () => {
|
|
const { queue } = createQueueState()
|
|
queue.set('a', pending('a'))
|
|
queue.set('b', pending('b'))
|
|
queue.set('c', pending('c'))
|
|
const initialAllocations = queue.getDebugSnapshot().allocationIdsByPty
|
|
|
|
for (let index = 0; index < 60; index++) {
|
|
const round = queue.beginRound()
|
|
const selection = queue.takeNext(round)!
|
|
queue.replaceWithRemainder(selection, pending(`${selection.id}-${index}`))
|
|
queue.endRound(round)
|
|
queue.set(selection.id, pending(`${selection.id}-${index}-updated`))
|
|
queue.invalidateAll()
|
|
}
|
|
|
|
expect(queue.getDebugSnapshot()).toMatchObject({
|
|
pendingSize: 3,
|
|
createdNodeCount: 3,
|
|
peakNodeCount: 3,
|
|
allocationIdsByPty: initialAllocations
|
|
})
|
|
|
|
const finalRound = queue.beginRound()
|
|
queue.clear()
|
|
expect(queue.takeNext(finalRound)).toBeNull()
|
|
expect(queue.getDebugSnapshot()).toMatchObject({
|
|
pendingSize: 0,
|
|
activeRunnableSize: 0,
|
|
backgroundRunnableSize: 0,
|
|
blockedSize: 0,
|
|
activeHeadId: null,
|
|
activeTailId: null,
|
|
backgroundHeadId: null,
|
|
backgroundTailId: null,
|
|
blockedHeadId: null,
|
|
blockedTailId: null,
|
|
openRound: null,
|
|
activeFrontier: 0,
|
|
backgroundFrontier: 0,
|
|
allocationIdsByPty: {}
|
|
})
|
|
})
|
|
|
|
it('freezes active classification until the next round', () => {
|
|
const { active, queue } = createQueueState()
|
|
queue.set('a', pending('a'))
|
|
queue.set('b', pending('b'))
|
|
|
|
const firstRound = queue.beginRound()
|
|
const first = queue.takeNext(firstRound)!
|
|
expect(first.id).toBe('a')
|
|
queue.remove(first)
|
|
active.add('b')
|
|
queue.invalidateAll()
|
|
expect(takeId(queue, firstRound)).toBe('b')
|
|
queue.endRound(firstRound)
|
|
|
|
queue.set('c', pending('c'))
|
|
active.add('c')
|
|
queue.invalidateAll()
|
|
const secondRound = queue.beginRound()
|
|
expect(takeId(queue, secondRound)).toBe('b')
|
|
queue.endRound(secondRound)
|
|
})
|
|
|
|
it('reactivates blocked candidates in authoritative Map order', () => {
|
|
const { blocked, queue } = createQueueState()
|
|
blocked.add('a')
|
|
blocked.add('b')
|
|
queue.set('a', pending('a'))
|
|
queue.set('b', pending('b'))
|
|
|
|
const blockedRound = queue.beginRound()
|
|
expect(queue.takeNext(blockedRound)).toBeNull()
|
|
queue.endRound(blockedRound)
|
|
expect(queue.getDebugSnapshot().blockedSize).toBe(2)
|
|
|
|
blocked.clear()
|
|
expect(queue.reactivateBlocked()).toBe(true)
|
|
const creditedRound = queue.beginRound()
|
|
const first = queue.takeNext(creditedRound)!
|
|
queue.remove(first)
|
|
const second = queue.takeNext(creditedRound)!
|
|
queue.remove(second)
|
|
queue.endRound(creditedRound)
|
|
expect([first.id, second.id]).toEqual(['a', 'b'])
|
|
})
|
|
|
|
it('visits 100 one-chunk candidates exactly once across 50 bounded rounds', () => {
|
|
const { queue } = createQueueState()
|
|
const legacy = new Map<string, PendingPtyData>()
|
|
for (let index = 0; index < 100; index++) {
|
|
const id = `pty-${index}`
|
|
const value = pending(String(index))
|
|
queue.set(id, value)
|
|
legacy.set(id, value)
|
|
}
|
|
|
|
let rounds = 0
|
|
let timerDecisions = 1
|
|
while (queue.size > 0) {
|
|
rounds += 1
|
|
const round = queue.beginRound()
|
|
for (let writes = 0; writes < 2; writes++) {
|
|
const selection = queue.takeNext(round)
|
|
if (!selection) {
|
|
break
|
|
}
|
|
queue.remove(selection)
|
|
}
|
|
queue.endRound(round)
|
|
if (queue.size > 0) {
|
|
timerDecisions += 1
|
|
}
|
|
}
|
|
|
|
let legacySelectionVisits = 0
|
|
let legacyTimerDecisions = 1
|
|
while (legacy.size > 0) {
|
|
const snapshot = [...legacy.keys()]
|
|
legacySelectionVisits += snapshot.length
|
|
for (const id of snapshot.slice(0, 2)) {
|
|
legacy.delete(id)
|
|
}
|
|
if (legacy.size > 0) {
|
|
legacyTimerDecisions += 1
|
|
}
|
|
}
|
|
|
|
expect(rounds).toBe(50)
|
|
expect(timerDecisions).toBe(50)
|
|
expect(legacySelectionVisits).toBe(2_550)
|
|
expect(legacyTimerDecisions).toBe(50)
|
|
expect(queue.getDebugSnapshot()).toMatchObject({
|
|
selectionVisitCount: 100,
|
|
createdNodeCount: 100,
|
|
peakNodeCount: 100,
|
|
pendingSize: 0,
|
|
activeRunnableSize: 0,
|
|
backgroundRunnableSize: 0,
|
|
blockedSize: 0,
|
|
activeHeadId: null,
|
|
activeTailId: null,
|
|
backgroundHeadId: null,
|
|
backgroundTailId: null,
|
|
blockedHeadId: null,
|
|
blockedTailId: null,
|
|
openRound: null,
|
|
activeFrontier: 0,
|
|
backgroundFrontier: 0,
|
|
allocationIdsByPty: {}
|
|
})
|
|
})
|
|
|
|
it('requires each selection to commit before advancing', () => {
|
|
const { queue } = createQueueState()
|
|
queue.set('a', pending('a'))
|
|
queue.set('b', pending('b'))
|
|
const round = queue.beginRound()
|
|
const selection = queue.takeNext(round)
|
|
|
|
expect(() => queue.takeNext(round)).toThrow(
|
|
'PTY pending-data selection must be committed before advancing'
|
|
)
|
|
expect(() => queue.set('a', pending('replacement'))).toThrow(
|
|
'Selected PTY pending data must commit before update'
|
|
)
|
|
queue.block(selection as PtyPendingDataDrainSelection)
|
|
expect(takeId(queue, round)).toBe('b')
|
|
queue.endRound(round)
|
|
})
|
|
})
|