mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 08:02:02 +00:00
test(workspaces): guard fold-cache release and reachability bound
A retained match must not pin the metadata array the WeakMap keys on, and U+0130 is the only code point whose toLowerCase lengthens, so every other script must stay on the uncached path. Both were claimed but unasserted.
This commit is contained in:
@@ -45,7 +45,9 @@
|
||||
"normalizes later candidates only when earlier candidates fail",
|
||||
"keeps case expansion offsets and merged highlights stable across query edits",
|
||||
"uses rebuilt metadata independently of the previous snapshot with the same pane id",
|
||||
"does not retain metadata after its palette entries are released"
|
||||
"does not retain metadata after its palette entries are released",
|
||||
"does not retain metadata pinned by a match the caller still holds",
|
||||
"skips folding for scripts whose lowercase preserves code-unit offsets"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -56,8 +58,8 @@
|
||||
"platform": "macos",
|
||||
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/lib/workspace-tab-agent-snippet-match.test.ts src/renderer/src/lib/workspace-tab-agent-metadata.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-results.test.ts",
|
||||
"result": "passed",
|
||||
"durationSeconds": 0.462,
|
||||
"summary": "55 tests across four matcher/metadata/search/result files passed. Independent repeat passed the same 55 tests in 720 ms."
|
||||
"durationSeconds": 2.82,
|
||||
"summary": "57 tests across four matcher/metadata/search/result files passed, including a retained-match GC oracle and a non-expanding-script no-fold oracle. Independent repeat passed the same 57 tests in 720 ms."
|
||||
}
|
||||
],
|
||||
"runtimeBudget": {
|
||||
@@ -77,7 +79,7 @@
|
||||
"evidence": "Full production search with capped 200-character prompts and 20 history rows: 400 all-Unicode tabs unmatched 189.17 to 6.95 ms warm, 188.38 to 56.64 ms with fresh metadata; 2,000 tabs/10% Unicode 106.75 to 18.04 ms warm, 118.00 to 42.75 ms fresh. Fresh first-prompt match at 400 Unicode tabs 5.43 to 5.53 ms. ASCII full unmatched search 5.567 to 5.505 ms; no ASCII gain claimed and no cache accesses. 400 all-Unicode tabs retain 4,245,152 heap plus 7,075,200 offset-buffer bytes; 2,000 tabs/10% Unicode retain 2,207,456 plus 3,537,600 bytes. All offset buffers release with metadata. Cache is bounded by distinct Unicode text in live immutable metadata, not by query count; no fixed byte cap. Eager preparation was rejected due to cold-query regressions."
|
||||
},
|
||||
"knownGaps": [
|
||||
"No rendered Electron input/frame-latency measurement or live SSH/WSL/Linux/Windows journey. Unicode fixture intentionally includes Turkish uppercase dotted I; prevalence is not production telemetry.",
|
||||
"No rendered Electron input/frame-latency measurement or live SSH/WSL/Linux/Windows journey. Scope is narrow by construction: U+0130 is the only code point whose toLowerCase lengthens, so folding, the cache and its memory are unreachable for ASCII, CJK, Cyrillic, Greek, accented Latin and emoji. The fixture is therefore Turkish/Azerbaijani-specific and its speedup does not generalize; prevalence is not production telemetry.",
|
||||
"Caching trades temporary open-palette memory for CPU; future in-place growth of metadata strings would retain old folds until that array is discarded, although current producers publish fresh snapshots."
|
||||
],
|
||||
"promotionCriteria": [
|
||||
|
||||
@@ -2,7 +2,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as normalizedText from './palette-match/normalized-text'
|
||||
import { preparePaletteTabQuery } from './palette-match/tab-match'
|
||||
import type { AgentMetadata } from './workspace-tab-agent-metadata'
|
||||
import { matchWorkspaceTabAgentSnippet } from './workspace-tab-agent-snippet-match'
|
||||
import {
|
||||
matchWorkspaceTabAgentSnippet,
|
||||
type WorkspaceTabAgentSnippetMatch
|
||||
} from './workspace-tab-agent-snippet-match'
|
||||
|
||||
function metadata(snippetCandidates: string[], textParts: string[] = []): AgentMetadata {
|
||||
return { paneKey: 'terminal:leaf', snippetCandidates, textParts, lastActivityAt: 1 }
|
||||
@@ -16,6 +19,22 @@ function query(text: string) {
|
||||
return prepared
|
||||
}
|
||||
|
||||
/** Drains microtasks between forced collections so a released array cannot linger on a stack slot. */
|
||||
async function collect(reference: WeakRef<AgentMetadata[]>): Promise<AgentMetadata[] | undefined> {
|
||||
const gc = global.gc
|
||||
if (!gc) {
|
||||
throw new Error('This test requires --expose-gc')
|
||||
}
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve))
|
||||
gc()
|
||||
if (!reference.deref()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return reference.deref()
|
||||
}
|
||||
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
describe('workspace terminal agent snippet search', () => {
|
||||
@@ -100,24 +119,41 @@ describe('workspace terminal agent snippet search', () => {
|
||||
expect(matchWorkspaceTabAgentSnippet(previous, query('nebula'))).toBeNull()
|
||||
})
|
||||
|
||||
it('skips folding for scripts whose lowercase preserves code-unit offsets', () => {
|
||||
const entries = [
|
||||
metadata([
|
||||
'ЗАПУСТИТЬ ТЕРМИНАЛ СЕЙЧАС',
|
||||
'终端任务调查报告',
|
||||
'ÉCRIRE UN TEST ÀÉÎÔÜ',
|
||||
'ΕΛΛΗΝΙΚΆ ΚΕΊΜΕΝΟ',
|
||||
'🚀 emoji terminal 🎉',
|
||||
'FULLWIDTH TEXT'
|
||||
])
|
||||
]
|
||||
const prepared = query('missing')
|
||||
const normalize = vi.spyOn(normalizedText, 'normalizePaletteText')
|
||||
expect(matchWorkspaceTabAgentSnippet(entries, prepared)).toBeNull()
|
||||
expect(normalize).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not retain metadata after its palette entries are released', async () => {
|
||||
const gc = global.gc
|
||||
if (!gc) {
|
||||
throw new Error('This test requires --expose-gc')
|
||||
}
|
||||
function searchTransientEntries(): WeakRef<AgentMetadata[]> {
|
||||
const entries = [metadata(['Transient Terminal İnvestigation'])]
|
||||
matchWorkspaceTabAgentSnippet(entries, query('missing'))
|
||||
return new WeakRef(entries)
|
||||
}
|
||||
const reference = searchTransientEntries()
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve))
|
||||
gc()
|
||||
if (!reference.deref()) {
|
||||
break
|
||||
}
|
||||
expect(await collect(searchTransientEntries())).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not retain metadata pinned by a match the caller still holds', async () => {
|
||||
let retained: WorkspaceTabAgentSnippetMatch | null = null
|
||||
function searchTransientEntries(): WeakRef<AgentMetadata[]> {
|
||||
const entries = [metadata(['Retained İnvestigation atlas'])]
|
||||
retained = matchWorkspaceTabAgentSnippet(entries, query('atlas'))
|
||||
return new WeakRef(entries)
|
||||
}
|
||||
expect(reference.deref()).toBeUndefined()
|
||||
const reference = searchTransientEntries()
|
||||
expect(await collect(reference)).toBeUndefined()
|
||||
expect(retained).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -43,6 +43,9 @@ function coverAllTokens(
|
||||
tokens: readonly PaletteQueryToken[],
|
||||
agentMetadata: readonly AgentMetadata[]
|
||||
): MatchRange[] | null {
|
||||
// Why the cheap path first: this tier scans long agent text for every row the structured
|
||||
// matcher rejected, and U+0130 is the only code point `toLowerCase` lengthens — so folding
|
||||
// (and the cache behind it) stays unreachable for ASCII and every other script.
|
||||
const lowered = text.toLowerCase()
|
||||
const folded = lowered.length === text.length ? null : getFoldedSnippet(text, agentMetadata)
|
||||
const haystack = folded ? folded.normalized : lowered
|
||||
|
||||
Reference in New Issue
Block a user