Files
goodbuddy/src/main/assistant/subagent-scheduler.test.ts
T
2026-08-13 01:36:00 +08:00

101 lines
3.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { vi } from 'vitest'
import { SubagentScheduler } from './subagent-scheduler'
describe('SubagentScheduler', () => {
it('enforces concurrency and starts queued work in FIFO order', async () => {
const scheduler = new SubagentScheduler({
concurrency: 2,
queueLimit: 3,
timeoutMs: 1_000
})
const started: number[] = []
let releaseInitial!: () => void
const initialGate = new Promise<void>((resolve) => {
releaseInitial = resolve
})
const jobs = [0, 1, 2, 3].map((value) =>
scheduler.schedule(async () => {
started.push(value)
if (value < 2) {
await initialGate
}
return value
})
)
await Promise.resolve()
expect(started).toEqual([0, 1])
releaseInitial()
await expect(Promise.all(jobs)).resolves.toEqual([0, 1, 2, 3])
expect(started).toEqual([0, 1, 2, 3])
scheduler.dispose()
})
it('rejects overflow, queued cancellation, and timed out work', async () => {
const scheduler = new SubagentScheduler({
concurrency: 1,
queueLimit: 1,
timeoutMs: 20
})
const blocker = scheduler.schedule(
(signal) => new Promise((_resolve, reject) => {
signal.addEventListener('abort', () => reject(signal.reason))
})
)
const controller = new AbortController()
const queued = scheduler.schedule(async () => 'queued', controller.signal)
await expect(
scheduler.schedule(async () => 'overflow')
).rejects.toThrow('队列已满')
controller.abort(new Error('cancelled'))
await expect(queued).rejects.toThrow('cancelled')
await expect(blocker).rejects.toThrow('120 秒')
scheduler.dispose()
})
it('holds its concurrency slot until aborted work finishes cleanup', async () => {
const scheduler = new SubagentScheduler({
concurrency: 1,
queueLimit: 1,
timeoutMs: 1_000
})
const controller = new AbortController()
let finishCleanup!: () => void
const cleanupGate = new Promise<void>((resolve) => {
finishCleanup = resolve
})
const started: string[] = []
const first = scheduler.schedule(async (signal) => {
started.push('first')
await new Promise<void>((resolve) => {
signal.addEventListener('abort', () => resolve(), { once: true })
})
await cleanupGate
return 'first'
}, controller.signal)
const second = scheduler.schedule(async () => {
started.push('second')
return 'second'
})
await vi.waitFor(() => expect(started).toEqual(['first']))
controller.abort(new Error('cancelled'))
await expect(first).rejects.toThrow('cancelled')
await Promise.resolve()
expect(started).toEqual(['first'])
let idle = false
const idlePromise = scheduler.waitForIdle().then(() => {
idle = true
})
await Promise.resolve()
expect(idle).toBe(false)
finishCleanup()
await expect(second).resolves.toBe('second')
await idlePromise
expect(started).toEqual(['first', 'second'])
scheduler.dispose()
})
})