mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-16 14:53:19 -07:00
coverage
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { RollingSummaryProcessor } from './rollingSummaryProcessor.js';
|
||||
import { createMockEnvironment, createDummyNode } from '../testing/contextTestUtils.js';
|
||||
|
||||
describe('RollingSummaryProcessor', () => {
|
||||
it('should initialize with correct default options', () => {
|
||||
const env = createMockEnvironment();
|
||||
const processor = RollingSummaryProcessor.create(env, { target: 'incremental' });
|
||||
expect(processor.id).toBe('RollingSummaryProcessor');
|
||||
});
|
||||
|
||||
it('should summarize older nodes when the deficit exceeds the threshold', async () => {
|
||||
// env.tokenCalculator uses charsPerToken=1 based on createMockEnvironment
|
||||
const env = createMockEnvironment();
|
||||
|
||||
// We want to free exactly 100 tokens.
|
||||
// We will supply nodes that cost 50 tokens each.
|
||||
const processor = RollingSummaryProcessor.create(env, {
|
||||
target: 'freeNTokens',
|
||||
freeTokensTarget: 100
|
||||
});
|
||||
|
||||
const text50 = 'A'.repeat(50);
|
||||
const targets = [
|
||||
createDummyNode('ep1', 'USER_PROMPT', 50, { semanticParts: [{ type: 'text', text: text50 }] }, 'id1'),
|
||||
createDummyNode('ep1', 'AGENT_THOUGHT', 50, { text: text50 }, 'id2'),
|
||||
createDummyNode('ep1', 'AGENT_YIELD', 50, { text: text50 }, 'id3'),
|
||||
];
|
||||
|
||||
const result = await processor.process({ targets, buffer: {} as any, inbox: {} as any });
|
||||
|
||||
// 3 nodes at 50 cost each.
|
||||
// The first node (id1) is the initial USER_PROMPT and is always skipped by RollingSummaryProcessor.
|
||||
// Node id2 adds 50 deficit. Node id3 adds 50 deficit. Total = 100 deficit, which hits the target break point.
|
||||
// Thus, id2 and id3 are summarized into a new ROLLING_SUMMARY node.
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0].type).toBe('USER_PROMPT');
|
||||
expect(result[1].type).toBe('ROLLING_SUMMARY');
|
||||
});
|
||||
|
||||
it('should preserve targets if deficit does not trigger summary', async () => {
|
||||
const env = createMockEnvironment();
|
||||
|
||||
// We want to free 100 tokens, but our nodes will only cost 10 tokens each.
|
||||
const processor = RollingSummaryProcessor.create(env, {
|
||||
target: 'freeNTokens',
|
||||
freeTokensTarget: 100
|
||||
});
|
||||
|
||||
const text10 = 'A'.repeat(10);
|
||||
const targets = [
|
||||
createDummyNode('ep1', 'USER_PROMPT', 10, { semanticParts: [{ type: 'text', text: text10 }] }, 'id1'),
|
||||
createDummyNode('ep1', 'AGENT_THOUGHT', 10, { text: text10 }, 'id2'),
|
||||
];
|
||||
|
||||
const result = await processor.process({ targets, buffer: {} as any, inbox: {} as any });
|
||||
|
||||
// Deficit accumulator reaches 10. This is < 100 limit, and total summarizable nodes < 2 anyway.
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0].type).toBe('USER_PROMPT');
|
||||
expect(result[1].type).toBe('AGENT_THOUGHT');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ContextEnvironmentImpl } from './environmentImpl.js';
|
||||
import { ContextTracer } from '../tracer.js';
|
||||
import { ContextEventBus } from '../eventBus.js';
|
||||
import { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
|
||||
import { DeterministicIdGenerator } from '../system/DeterministicIdGenerator.js';
|
||||
import type { BaseLlmClient } from '../../core/baseLlmClient.js';
|
||||
|
||||
describe('ContextEnvironmentImpl', () => {
|
||||
it('should initialize with defaults correctly', () => {
|
||||
const tracer = new ContextTracer({ targetDir: '/tmp', sessionId: 'mock' });
|
||||
const eventBus = new ContextEventBus();
|
||||
const mockLlmClient = {} as BaseLlmClient;
|
||||
|
||||
const env = new ContextEnvironmentImpl(
|
||||
mockLlmClient,
|
||||
'mock-session',
|
||||
'mock-prompt',
|
||||
'/tmp/trace',
|
||||
'/tmp/temp',
|
||||
tracer,
|
||||
4,
|
||||
eventBus,
|
||||
);
|
||||
|
||||
expect(env.llmClient).toBe(mockLlmClient);
|
||||
expect(env.sessionId).toBe('mock-session');
|
||||
expect(env.promptId).toBe('mock-prompt');
|
||||
expect(env.traceDir).toBe('/tmp/trace');
|
||||
expect(env.projectTempDir).toBe('/tmp/temp');
|
||||
expect(env.tracer).toBe(tracer);
|
||||
expect(env.charsPerToken).toBe(4);
|
||||
expect(env.eventBus).toBe(eventBus);
|
||||
|
||||
// Default internals
|
||||
expect(env.behaviorRegistry).toBeDefined();
|
||||
expect(env.tokenCalculator).toBeDefined();
|
||||
expect(env.fileSystem).toBeDefined();
|
||||
expect(env.idGenerator).toBeDefined();
|
||||
expect(env.inbox).toBeDefined();
|
||||
expect(env.irMapper).toBeDefined();
|
||||
});
|
||||
|
||||
it('should initialize with provided overrides', () => {
|
||||
const tracer = new ContextTracer({ targetDir: '/tmp', sessionId: 'mock' });
|
||||
const eventBus = new ContextEventBus();
|
||||
const mockLlmClient = {} as BaseLlmClient;
|
||||
const fileSystem = new InMemoryFileSystem();
|
||||
const idGenerator = new DeterministicIdGenerator('test-');
|
||||
|
||||
const env = new ContextEnvironmentImpl(
|
||||
mockLlmClient,
|
||||
'mock-session',
|
||||
'mock-prompt',
|
||||
'/tmp/trace',
|
||||
'/tmp/temp',
|
||||
tracer,
|
||||
4,
|
||||
eventBus,
|
||||
fileSystem,
|
||||
idGenerator,
|
||||
);
|
||||
|
||||
expect(env.fileSystem).toBe(fileSystem);
|
||||
expect(env.idGenerator).toBe(idGenerator);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { LiveInbox, InboxSnapshotImpl } from './inbox.js';
|
||||
import { DeterministicIdGenerator } from '../system/DeterministicIdGenerator.js';
|
||||
|
||||
describe('Inbox', () => {
|
||||
it('should publish messages and provide snapshots', () => {
|
||||
const inbox = new LiveInbox();
|
||||
const idGenerator = new DeterministicIdGenerator('mock-uuid-');
|
||||
|
||||
inbox.publish('test-topic', { data: 'hello' }, idGenerator);
|
||||
inbox.publish('other-topic', { data: 'world' }, idGenerator);
|
||||
|
||||
const messages = inbox.getMessages();
|
||||
expect(messages.length).toBe(2);
|
||||
expect(messages[0].topic).toBe('test-topic');
|
||||
expect(messages[0].payload).toEqual({ data: 'hello' });
|
||||
});
|
||||
|
||||
it('should drain consumed messages from the snapshot', () => {
|
||||
const inbox = new LiveInbox();
|
||||
const idGenerator = new DeterministicIdGenerator('mock-uuid-');
|
||||
|
||||
inbox.publish('test-topic', { data: 'hello' }, idGenerator);
|
||||
inbox.publish('other-topic', { data: 'world' }, idGenerator);
|
||||
|
||||
const messages = inbox.getMessages();
|
||||
const snapshot = new InboxSnapshotImpl(messages);
|
||||
|
||||
const filtered = snapshot.getMessages<{ data: string }>('test-topic');
|
||||
expect(filtered.length).toBe(1);
|
||||
expect(filtered[0].payload.data).toBe('hello');
|
||||
|
||||
// Consume the message
|
||||
snapshot.consume(filtered[0].id);
|
||||
|
||||
// Provide the consumed IDs to the real inbox to drain them
|
||||
inbox.drainConsumed(snapshot.getConsumedIds());
|
||||
|
||||
const finalMessages = inbox.getMessages();
|
||||
expect(finalMessages.length).toBe(1);
|
||||
expect(finalMessages[0].topic).toBe('other-topic');
|
||||
});
|
||||
});
|
||||
@@ -35,7 +35,7 @@ export class InboxSnapshotImpl implements InboxSnapshot {
|
||||
}
|
||||
|
||||
getMessages<T = unknown>(topic: string): ReadonlyArray<InboxMessage<T>> {
|
||||
return this.messages.filter((m) => m.topic === topic) as unknown as ReadonlyArray<InboxMessage<T>>;
|
||||
return this.messages.filter((m) => m.topic === topic) as ReadonlyArray<InboxMessage<T>>;
|
||||
}
|
||||
|
||||
consume(messageId: string): void {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { SidecarRegistry } from './registry.js';
|
||||
import type { ContextProcessorDef, ContextWorkerDef } from './registry.js';
|
||||
|
||||
describe('SidecarRegistry', () => {
|
||||
it('should register and retrieve processors correctly', () => {
|
||||
const registry = new SidecarRegistry();
|
||||
const processorDef: ContextProcessorDef = {
|
||||
id: 'TestProcessor',
|
||||
schema: { type: 'object' },
|
||||
create: () => ({} as any),
|
||||
};
|
||||
|
||||
registry.registerProcessor(processorDef);
|
||||
const retrieved = registry.getProcessor('TestProcessor');
|
||||
expect(retrieved).toBe(processorDef);
|
||||
});
|
||||
|
||||
it('should register and retrieve workers correctly', () => {
|
||||
const registry = new SidecarRegistry();
|
||||
const workerDef: ContextWorkerDef = {
|
||||
id: 'TestWorker',
|
||||
schema: { type: 'object' },
|
||||
create: () => ({} as any),
|
||||
};
|
||||
|
||||
registry.registerWorker(workerDef);
|
||||
const retrieved = registry.getWorker('TestWorker');
|
||||
expect(retrieved).toBe(workerDef);
|
||||
});
|
||||
|
||||
it('should throw an error when retrieving unregistered processors', () => {
|
||||
const registry = new SidecarRegistry();
|
||||
expect(() => registry.getProcessor('Unknown')).toThrow('Context Processor [Unknown] is not registered.');
|
||||
});
|
||||
|
||||
it('should throw an error when retrieving unregistered workers', () => {
|
||||
const registry = new SidecarRegistry();
|
||||
expect(() => registry.getWorker('Unknown')).toThrow('Context Worker [Unknown] is not registered.');
|
||||
});
|
||||
|
||||
it('should return combined schemas', () => {
|
||||
const registry = new SidecarRegistry();
|
||||
registry.registerProcessor({
|
||||
id: 'TestProcessor',
|
||||
schema: { title: 'processorSchema' },
|
||||
create: () => ({} as any),
|
||||
});
|
||||
registry.registerWorker({
|
||||
id: 'TestWorker',
|
||||
schema: { title: 'workerSchema' },
|
||||
create: () => ({} as any),
|
||||
});
|
||||
|
||||
const schemas = registry.getSchemas() as any[];
|
||||
expect(schemas.length).toBe(2);
|
||||
expect(schemas.find(s => s.title === 'processorSchema')).toBeDefined();
|
||||
expect(schemas.find(s => s.title === 'workerSchema')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should safely clear the registry', () => {
|
||||
const registry = new SidecarRegistry();
|
||||
registry.registerProcessor({ id: 'TestProcessor', schema: {}, create: () => ({} as any) });
|
||||
registry.registerWorker({ id: 'TestWorker', schema: {}, create: () => ({} as any) });
|
||||
|
||||
registry.clear();
|
||||
|
||||
expect(() => registry.getProcessor('TestProcessor')).toThrow();
|
||||
expect(() => registry.getWorker('TestWorker')).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -14,10 +14,6 @@ import { ContextTracer } from '../tracer.js';
|
||||
import { ContextEnvironmentImpl } from '../sidecar/environmentImpl.js';
|
||||
import { SidecarLoader } from '../sidecar/SidecarLoader.js';
|
||||
import { ContextEventBus } from '../eventBus.js';
|
||||
import { ContextTokenCalculator } from '../utils/contextTokenCalculator.js';
|
||||
import { IrNodeBehaviorRegistry } from '../ir/behaviorRegistry.js';
|
||||
import { registerBuiltInBehaviors } from '../ir/builtinBehaviors.js';
|
||||
import { IrMapper } from '../ir/mapper.js';
|
||||
import { SidecarRegistry } from '../sidecar/registry.js';
|
||||
import { registerBuiltInProcessors } from '../sidecar/builtins.js';
|
||||
import { PipelineOrchestrator } from '../sidecar/orchestrator.js';
|
||||
@@ -99,34 +95,27 @@ export function createDummyToolNode(
|
||||
export function createMockEnvironment(
|
||||
overrides?: Partial<ContextEnvironment>,
|
||||
): ContextEnvironment {
|
||||
const registry = new IrNodeBehaviorRegistry();
|
||||
registerBuiltInBehaviors(registry);
|
||||
const irMapper = new IrMapper(registry);
|
||||
const llmClient = vi.fn().mockReturnValue({
|
||||
generateContent: vi.fn().mockResolvedValue(createMockGenerateContentResponse('Mock LLM summary response')),
|
||||
})() as unknown as BaseLlmClient;
|
||||
|
||||
const tracer = new ContextTracer({ targetDir: '/tmp', sessionId: 'mock-session' });
|
||||
const eventBus = new ContextEventBus();
|
||||
|
||||
const env = new ContextEnvironmentImpl(
|
||||
llmClient,
|
||||
'mock-session',
|
||||
'mock-prompt-id',
|
||||
'/tmp/.gemini/trace',
|
||||
'/tmp/.gemini/tool-outputs',
|
||||
tracer,
|
||||
1,
|
||||
eventBus,
|
||||
new InMemoryFileSystem(),
|
||||
new DeterministicIdGenerator('mock-uuid-')
|
||||
);
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
llmClient: vi.fn().mockReturnValue({
|
||||
generateContent: vi.fn().mockResolvedValue(createMockGenerateContentResponse('Mock LLM summary response')),
|
||||
})() as unknown as BaseLlmClient,
|
||||
promptId: 'mock-prompt-id',
|
||||
sessionId: 'mock-session',
|
||||
traceDir: '/tmp/.gemini/trace',
|
||||
projectTempDir: '/tmp/.gemini/tool-outputs',
|
||||
eventBus: new ContextEventBus(),
|
||||
tracer: new ContextTracer({ targetDir: '/tmp', sessionId: 'mock-session' }),
|
||||
charsPerToken: 1,
|
||||
tokenCalculator: new ContextTokenCalculator(1, registry),
|
||||
fileSystem: new InMemoryFileSystem(),
|
||||
idGenerator: new DeterministicIdGenerator('mock-uuid-'),
|
||||
behaviorRegistry: registry,
|
||||
inbox: {
|
||||
publish: vi.fn(),
|
||||
getMessages: vi.fn().mockReturnValue([]),
|
||||
drainConsumed: vi.fn(),
|
||||
} as any,
|
||||
irMapper,
|
||||
...overrides,
|
||||
} as ContextEnvironment;
|
||||
return { ...env, ...overrides };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user