Files
orca/src/main/usage/usage-worktree-resolver.test.ts
T
Jinwoo Hong 2c2d068b26 perf(usage): resolve each cwd's worktree once per scan (#21130)
* perf(usage): resolve each cwd's worktree once per scan

Codex and OpenCode attribution ran the worktree containment search for every
parsed event, so a cold scan cost events x worktrees. On 745 MB of real
rollouts (~20k events) that is 1.2s with 0 worktrees, 5.0s with 100, 12.8s
with 300 and 39.8s with 1000; a full corpus with hundreds of remembered
worktrees is where the STA-7724 reparse burned minutes of main-thread CPU.

A scan holds only a few hundred distinct cwds, so both scanners now build one
memoized resolver per scan and thread it through parsing instead of passing the
worktree list to every event.

* refactor(usage): make the worktree resolver own canonicalization

`createUsageWorktreeResolver` now takes raw worktree refs and canonicalizes
them itself, so each scanner has one entry point and neither keeps a private
`buildWorktreesWithCanonicalPaths` or `canonicalizePath`. The resolver unit
test counts comparisons through the same `areWorktreePathsEqual` mock the
scanner-level test uses instead of a property getter.
2026-09-16 22:30:55 -04:00

88 lines
3.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as WorktreeLogic from '../ipc/worktree-logic'
import type { UsageScanWorktreeRef } from './usage-provider-contract'
const { worktreePathComparisons } = vi.hoisted(() => ({
worktreePathComparisons: { count: 0 }
}))
vi.mock('../ipc/worktree-logic', async (importOriginal) => {
const actual = await importOriginal<typeof WorktreeLogic>()
return {
...actual,
areWorktreePathsEqual: (left: string, right: string) => {
worktreePathComparisons.count += 1
return actual.areWorktreePathsEqual(left, right)
}
}
})
import { createUsageWorktreeResolver } from './usage-worktree-resolver'
function worktree(path: string, index: number): UsageScanWorktreeRef {
return {
repoId: `repo-${index}`,
worktreeId: `repo-${index}::${path}`,
path,
displayName: `Repo ${index}`
}
}
describe('createUsageWorktreeResolver', () => {
beforeEach(() => {
worktreePathComparisons.count = 0
})
it('walks the worktree list once per distinct cwd, including misses', async () => {
const resolveWorktree = await createUsageWorktreeResolver(
Array.from({ length: 50 }, (_, index) =>
worktree(`/repo-${String(index).padStart(3, '0')}`, index)
)
)
const attribute = (event: number): string | null => {
const cwd = event % 2 === 0 ? '/repo-049/nested/pkg' : '/outside/project'
return resolveWorktree(cwd)?.worktreeId ?? null
}
expect(attribute(0)).toBe('repo-49::/repo-049')
expect(attribute(1)).toBeNull()
const afterFirstOfEachCwd = worktreePathComparisons.count
expect(afterFirstOfEachCwd).toBeGreaterThan(0)
expect(afterFirstOfEachCwd).toBeLessThanOrEqual(100)
for (let event = 2; event < 1_000; event++) {
expect(attribute(event)).toBe(event % 2 === 0 ? 'repo-49::/repo-049' : null)
}
// Two distinct cwds walked the list once each; 998 more events cost nothing.
expect(worktreePathComparisons.count).toBe(afterFirstOfEachCwd)
})
it('keeps containment semantics unchanged', async () => {
const resolveWorktree = await createUsageWorktreeResolver([worktree('/workspace/repo', 1)])
expect(resolveWorktree('/workspace/repo')?.worktreeId).toBe('repo-1::/workspace/repo')
expect(resolveWorktree('/workspace/repo/packages/app')?.worktreeId).toBe(
'repo-1::/workspace/repo'
)
// `..name` is a child directory; `..` escapes.
expect(resolveWorktree('/workspace/repo/..fixtures/session')?.worktreeId).toBe(
'repo-1::/workspace/repo'
)
expect(resolveWorktree('/workspace/repo/../other/session')).toBeNull()
expect(resolveWorktree('/workspace/repo-sibling')).toBeNull()
})
it('does not treat a different Windows drive as contained', async () => {
const resolveWorktree = await createUsageWorktreeResolver([worktree('C:\\repo', 1)])
expect(resolveWorktree('C:\\repo\\packages\\app')?.worktreeId).toBe('repo-1::C:\\repo')
expect(resolveWorktree('D:\\other\\repo')).toBeNull()
})
it('resolves nothing when no worktree is known', async () => {
const resolveWorktree = await createUsageWorktreeResolver([])
expect(resolveWorktree('/workspace/repo')).toBeNull()
})
})