mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 00:02:54 +00:00
* oom(01): A1-shared-readers — reintroduce #10179 subset Files: 18 applied, 0 deleted (from6eb70d8370) Co-authored-by: Orca <help@stably.ai> * oom(02): A2-shared-image-media — reintroduce #10179 subset Files: 7 applied, 0 deleted (from6eb70d8370) Co-authored-by: Orca <help@stably.ai> * oom(03): A3-shared-fs-listing — reintroduce #10179 subset Files: 21 applied, 0 deleted (from6eb70d8370) Co-authored-by: Orca <help@stably.ai> * oom(04): A4-shared-remote-relay — reintroduce #10179 subset Files: 8 applied, 0 deleted (from6eb70d8370) Co-authored-by: Orca <help@stably.ai> * oom(05): A5-shared-misc — reintroduce #10179 subset Files: 28 applied, 0 deleted (from6eb70d8370) Co-authored-by: Orca <help@stably.ai> * oom(06): B-shared-wiring — reintroduce #10179 subset Files: 81 applied, 0 deleted (from6eb70d8370) Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
177 lines
5.8 KiB
TypeScript
177 lines
5.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { scanWorkspaceSpaceEntryTree } from './workspace-space-entry-traversal'
|
|
import { WorkspaceSpaceScanCapacityError } from './workspace-space-scan-budget'
|
|
|
|
type Entry = { name: string }
|
|
|
|
function makeTraversal(
|
|
directories: ReadonlyMap<string, readonly Entry[]>,
|
|
classifyEntry: (path: string) => Promise<{
|
|
kind: 'directory' | 'file' | 'symlink'
|
|
sizeBytes: number
|
|
}>,
|
|
limits?: { maxEntries?: number; maxRetainedBytes?: number }
|
|
) {
|
|
return scanWorkspaceSpaceEntryTree({
|
|
rootPath: '/root',
|
|
rootName: 'root',
|
|
concurrency: 5,
|
|
entryName: (entry: Entry) => entry.name,
|
|
joinPath: (parent, child) => `${parent}/${child}`,
|
|
classifyEntry: (path) => classifyEntry(path),
|
|
readDirectory: async (path) => {
|
|
const entries = directories.get(path)
|
|
if (!entries) {
|
|
throw new Error(`unreadable ${path}`)
|
|
}
|
|
return entries
|
|
},
|
|
checkCancelled: () => undefined,
|
|
createCancellationError: () => new Error('cancelled'),
|
|
isCancellationError: (error) => error instanceof Error && error.message === 'cancelled',
|
|
limits
|
|
})
|
|
}
|
|
|
|
describe('scanWorkspaceSpaceEntryTree', () => {
|
|
it('uses a fixed worker pool and preserves source order', async () => {
|
|
const entries = Array.from({ length: 200 }, (_, index) => ({ name: `file-${index}` }))
|
|
let release!: () => void
|
|
const gate = new Promise<void>((resolve) => {
|
|
release = resolve
|
|
})
|
|
let active = 0
|
|
let peak = 0
|
|
let started = 0
|
|
let saturated!: () => void
|
|
const saturation = new Promise<void>((resolve) => {
|
|
saturated = resolve
|
|
})
|
|
|
|
const scan = makeTraversal(new Map([['/root', entries]]), async (path) => {
|
|
if (path === '/root') {
|
|
return { kind: 'directory', sizeBytes: 1 }
|
|
}
|
|
active += 1
|
|
started += 1
|
|
peak = Math.max(peak, active)
|
|
if (started === 5) {
|
|
saturated()
|
|
}
|
|
await gate
|
|
active -= 1
|
|
return { kind: 'file', sizeBytes: 1 }
|
|
})
|
|
|
|
await saturation
|
|
expect(started).toBe(5)
|
|
expect(peak).toBe(5)
|
|
release()
|
|
|
|
const result = await scan
|
|
expect(result.children?.map((child) => child.name)).toEqual(entries.map((entry) => entry.name))
|
|
expect(result.sizeBytes).toBe(201)
|
|
})
|
|
|
|
it('preserves aggregate sizes and partial-failure accounting', async () => {
|
|
const directories = new Map<string, readonly Entry[]>([
|
|
['/root', [{ name: 'directory' }, { name: 'missing' }, { name: 'link' }, { name: 'file' }]],
|
|
['/root/directory', [{ name: 'nested' }, { name: 'unreadable' }]],
|
|
['/root/directory/unreadable', []]
|
|
])
|
|
directories.delete('/root/directory/unreadable')
|
|
|
|
const result = await makeTraversal(directories, async (path) => {
|
|
if (path === '/root') {
|
|
return { kind: 'directory', sizeBytes: 10 }
|
|
}
|
|
if (path === '/root/directory') {
|
|
return { kind: 'directory', sizeBytes: 5 }
|
|
}
|
|
if (path === '/root/directory/nested') {
|
|
return { kind: 'file', sizeBytes: 100 }
|
|
}
|
|
if (path === '/root/directory/unreadable') {
|
|
return { kind: 'directory', sizeBytes: 7 }
|
|
}
|
|
if (path === '/root/missing') {
|
|
throw new Error('missing')
|
|
}
|
|
if (path === '/root/link') {
|
|
return { kind: 'symlink', sizeBytes: 2 }
|
|
}
|
|
return { kind: 'file', sizeBytes: 20 }
|
|
})
|
|
|
|
expect(result).toMatchObject({ sizeBytes: 144, skippedEntryCount: 2 })
|
|
expect(result.children?.map((child) => child.name)).toEqual(['directory', 'link', 'file'])
|
|
expect(result.children?.[0]).toMatchObject({
|
|
sizeBytes: 112,
|
|
skippedEntryCount: 1
|
|
})
|
|
})
|
|
|
|
it('accepts the exact entry cap without changing order or totals', async () => {
|
|
const entries = [{ name: 'first' }, { name: 'second' }]
|
|
const result = await makeTraversal(
|
|
new Map([['/root', entries]]),
|
|
async (path) => ({ kind: path === '/root' ? 'directory' : 'file', sizeBytes: 1 }),
|
|
{ maxEntries: entries.length }
|
|
)
|
|
|
|
expect(result.children?.map((child) => child.name)).toEqual(['first', 'second'])
|
|
expect(result.sizeBytes).toBe(3)
|
|
})
|
|
|
|
it('fails closed instead of retaining entries beyond the scan cap', async () => {
|
|
const entries = [{ name: 'first' }, { name: 'second' }, { name: 'overflow' }]
|
|
const scan = makeTraversal(
|
|
new Map([['/root', entries]]),
|
|
async (path) => ({ kind: path === '/root' ? 'directory' : 'file', sizeBytes: 1 }),
|
|
{ maxEntries: entries.length - 1 }
|
|
)
|
|
|
|
await expect(scan).rejects.toBeInstanceOf(WorkspaceSpaceScanCapacityError)
|
|
})
|
|
|
|
it('aggregates a deep chain exactly at the entry cap without recursive unwinding', async () => {
|
|
const depth = 256
|
|
const directories = new Map<string, readonly Entry[]>()
|
|
let path = '/root'
|
|
for (let index = 0; index < depth; index += 1) {
|
|
const name = `directory-${index}`
|
|
directories.set(path, [{ name }])
|
|
path = `${path}/${name}`
|
|
}
|
|
directories.set(path, [])
|
|
|
|
const result = await makeTraversal(
|
|
directories,
|
|
async () => ({ kind: 'directory', sizeBytes: 1 }),
|
|
{ maxEntries: depth }
|
|
)
|
|
|
|
expect(result.sizeBytes).toBe(depth + 1)
|
|
expect(result.children).toEqual([
|
|
expect.objectContaining({ name: 'directory-0', sizeBytes: depth })
|
|
])
|
|
})
|
|
|
|
it('fails closed when a deep chain crosses the cumulative entry cap', async () => {
|
|
const directories = new Map<string, readonly Entry[]>()
|
|
let path = '/root'
|
|
for (let index = 0; index < 5; index += 1) {
|
|
const name = `directory-${index}`
|
|
directories.set(path, [{ name }])
|
|
path = `${path}/${name}`
|
|
}
|
|
directories.set(path, [])
|
|
|
|
const scan = makeTraversal(directories, async () => ({ kind: 'directory', sizeBytes: 1 }), {
|
|
maxEntries: 4
|
|
})
|
|
|
|
await expect(scan).rejects.toBeInstanceOf(WorkspaceSpaceScanCapacityError)
|
|
})
|
|
})
|