Files
orca/src/shared/git-status-line-stats-cache.test.ts
T
Jinjing 04a6100ac0 Fix aborted git status scans being mistaken for completed empty results
- An aborted scan/numstat pass now always rejects instead of silently
  resolving, so a cancelled request can't look like a valid empty status.
- Stop clearing the line-stats cache key on abort, since an aborted pass
  never wrote to it — clearing was evicting a concurrent scan's healthy
  snapshot and forcing a redundant numstat recompute.
2026-07-15 17:33:48 -07:00

181 lines
5.7 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest'
import {
applyCachedGitStatusLineStats,
beginGitStatusLineStatsCacheWrite,
clearGitStatusLineStatsCache,
clearGitStatusLineStatsCacheKey,
GIT_STATUS_LINE_STATS_CACHE_MAX_AGE_MS,
reuseOrRecomputeGitStatusLineStats,
storeGitStatusLineStats
} from './git-status-line-stats-cache'
const cachedEntries = [
{
path: 'src/a.ts',
status: 'modified',
area: 'unstaged',
added: 3,
removed: 2
}
]
describe('git status line stats cache', () => {
beforeEach(() => {
clearGitStatusLineStatsCache()
})
it('reuses counts only for an exact head and status-entry identity', () => {
storeGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries: cachedEntries })
const matchingEntries = [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }]
expect(
applyCachedGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: matchingEntries
})
).toBe(true)
expect(matchingEntries).toEqual(cachedEntries)
expect(
applyCachedGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-2',
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }]
})
).toBe(false)
})
it('invalidates a matching status identity at the bounded age', () => {
storeGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: cachedEntries,
now: 1_000
})
expect(
applyCachedGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }],
now: 1_000 + GIT_STATUS_LINE_STATS_CACHE_MAX_AGE_MS
})
).toBe(false)
})
it('isolates execution-host keys and supports mutation invalidation', () => {
storeGitStatusLineStats({
cacheKey: 'wsl:ubuntu\0/repo',
head: 'head-1',
entries: cachedEntries
})
expect(
applyCachedGitStatusLineStats({
cacheKey: 'wsl:debian\0/repo',
head: 'head-1',
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }]
})
).toBe(false)
clearGitStatusLineStatsCacheKey('wsl:ubuntu\0/repo')
expect(
applyCachedGitStatusLineStats({
cacheKey: 'wsl:ubuntu\0/repo',
head: 'head-1',
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }]
})
).toBe(false)
})
it('lets an earlier recompute store after a later reuse-only read began', () => {
const recomputeWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
// A hinted safety read begins later, reuses, and never stores; it must not
// starve the older recompute's store.
beginGitStatusLineStatsCacheWrite('native\0/repo')
storeGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: cachedEntries,
writeToken: recomputeWrite
})
const entries: { path: string; status: string; area: string; added?: number }[] = [
{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }
]
expect(
applyCachedGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries })
).toBe(true)
expect(entries[0]?.added).toBe(3)
})
it('keeps a prior good snapshot reusable when an overlapping recompute is aborted', async () => {
// A completed scan stored healthy counts.
storeGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries: cachedEntries })
// A later scan begins and recomputes but is aborted before it can store.
const abortedWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
await reuseOrRecomputeGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }],
writeToken: abortedWrite,
reuse: false,
isAborted: () => true,
recompute: async () => true
})
// The aborted pass persisted nothing, so it must not evict the healthy
// snapshot — a following safety read still reuses the stored counts.
const entries: { path: string; status: string; area: string; added?: number }[] = [
{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }
]
expect(
applyCachedGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries })
).toBe(true)
expect(entries[0]?.added).toBe(3)
})
it('rejects stale cache writes and writes started before invalidation', () => {
const olderWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
const latestWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
storeGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: [{ ...cachedEntries[0], added: 8 }],
writeToken: latestWrite
})
storeGitStatusLineStats({
cacheKey: 'native\0/repo',
head: 'head-1',
entries: cachedEntries,
writeToken: olderWrite
})
const entries: { path: string; status: string; area: string; added?: number }[] = [
{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }
]
expect(
applyCachedGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries })
).toBe(true)
expect(entries[0]?.added).toBe(8)
const invalidatedWrite = beginGitStatusLineStatsCacheWrite('native\0/other')
clearGitStatusLineStatsCache()
storeGitStatusLineStats({
cacheKey: 'native\0/other',
head: 'head-1',
entries: cachedEntries,
writeToken: invalidatedWrite
})
expect(
applyCachedGitStatusLineStats({
cacheKey: 'native\0/other',
head: 'head-1',
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }]
})
).toBe(false)
})
})