mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-01 15:34:29 -07:00
feat: Implement background shell commands (#14849)
This commit is contained in:
@@ -76,7 +76,13 @@ vi.mock('../utils/getPty.js', () => ({
|
||||
getPty: mockGetPty,
|
||||
}));
|
||||
vi.mock('../utils/terminalSerializer.js', () => ({
|
||||
serializeTerminalToObject: mockSerializeTerminalToObject,
|
||||
// Avoid passing the heavy Terminal object to the spy to prevent OOM
|
||||
serializeTerminalToObject: (
|
||||
_terminal: unknown,
|
||||
...args: [number | undefined, number | undefined]
|
||||
) => mockSerializeTerminalToObject(...args),
|
||||
convertColorToHex: () => '#000000',
|
||||
ColorMode: { DEFAULT: 0, PALETTE: 1, RGB: 2 },
|
||||
}));
|
||||
vi.mock('../utils/systemEncoding.js', () => ({
|
||||
getCachedEncodingForBuffer: vi.fn().mockReturnValue('utf-8'),
|
||||
@@ -318,6 +324,7 @@ describe('ShellExecutionService', () => {
|
||||
}
|
||||
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
|
||||
},
|
||||
{ ...shellExecutionConfig, maxSerializedLines: 100 },
|
||||
);
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
@@ -675,7 +682,7 @@ describe('ShellExecutionService', () => {
|
||||
expect(result.rawOutput).toEqual(
|
||||
Buffer.concat([binaryChunk1, binaryChunk2]),
|
||||
);
|
||||
expect(onOutputEventMock).toHaveBeenCalledTimes(3);
|
||||
expect(onOutputEventMock).toHaveBeenCalledTimes(4);
|
||||
expect(onOutputEventMock.mock.calls[0][0]).toEqual({
|
||||
type: 'binary_detected',
|
||||
});
|
||||
@@ -687,6 +694,11 @@ describe('ShellExecutionService', () => {
|
||||
type: 'binary_progress',
|
||||
bytesReceived: 8,
|
||||
});
|
||||
expect(onOutputEventMock.mock.calls[3][0]).toEqual({
|
||||
type: 'exit',
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not emit data events after binary is detected', async () => {
|
||||
@@ -705,6 +717,7 @@ describe('ShellExecutionService', () => {
|
||||
'binary_detected',
|
||||
'binary_progress',
|
||||
'binary_progress',
|
||||
'exit',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -763,9 +776,7 @@ describe('ShellExecutionService', () => {
|
||||
coloredShellExecutionConfig,
|
||||
);
|
||||
|
||||
expect(mockSerializeTerminalToObject).toHaveBeenCalledWith(
|
||||
expect.anything(), // The terminal object
|
||||
);
|
||||
expect(mockSerializeTerminalToObject).toHaveBeenCalled();
|
||||
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -932,11 +943,20 @@ describe('ShellExecutionService child_process fallback', () => {
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.aborted).toBe(false);
|
||||
expect(result.output).toBe('file1.txt\na warning');
|
||||
expect(handle.pid).toBe(undefined);
|
||||
expect(handle.pid).toBe(12345);
|
||||
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith({
|
||||
type: 'data',
|
||||
chunk: 'file1.txt\na warning',
|
||||
chunk: 'file1.txt\n',
|
||||
});
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith({
|
||||
type: 'data',
|
||||
chunk: 'a warning',
|
||||
});
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith({
|
||||
type: 'exit',
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -948,12 +968,15 @@ describe('ShellExecutionService child_process fallback', () => {
|
||||
});
|
||||
|
||||
expect(result.output.trim()).toBe('aredword');
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'data',
|
||||
chunk: 'aredword',
|
||||
}),
|
||||
);
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith({
|
||||
type: 'data',
|
||||
chunk: 'a\u001b[31mred\u001b[0mword',
|
||||
});
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith({
|
||||
type: 'exit',
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly decode multi-byte characters split across chunks', async () => {
|
||||
@@ -974,10 +997,14 @@ describe('ShellExecutionService child_process fallback', () => {
|
||||
});
|
||||
|
||||
expect(result.output.trim()).toBe('');
|
||||
expect(onOutputEventMock).not.toHaveBeenCalled();
|
||||
expect(onOutputEventMock).toHaveBeenCalledWith({
|
||||
type: 'exit',
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should truncate stdout using a sliding window and show a warning', async () => {
|
||||
it('should truncate stdout using a sliding window and show a warning', async () => {
|
||||
const MAX_SIZE = 16 * 1024 * 1024;
|
||||
const chunk1 = 'a'.repeat(MAX_SIZE / 2 - 5);
|
||||
const chunk2 = 'b'.repeat(MAX_SIZE / 2 - 5);
|
||||
@@ -1173,26 +1200,44 @@ describe('ShellExecutionService child_process fallback', () => {
|
||||
expect(result.rawOutput).toEqual(
|
||||
Buffer.concat([binaryChunk1, binaryChunk2]),
|
||||
);
|
||||
expect(onOutputEventMock).toHaveBeenCalledTimes(1);
|
||||
expect(onOutputEventMock).toHaveBeenCalledTimes(4);
|
||||
expect(onOutputEventMock.mock.calls[0][0]).toEqual({
|
||||
type: 'binary_detected',
|
||||
});
|
||||
expect(onOutputEventMock.mock.calls[1][0]).toEqual({
|
||||
type: 'binary_progress',
|
||||
bytesReceived: 4,
|
||||
});
|
||||
expect(onOutputEventMock.mock.calls[2][0]).toEqual({
|
||||
type: 'binary_progress',
|
||||
bytesReceived: 8,
|
||||
});
|
||||
expect(onOutputEventMock.mock.calls[3][0]).toEqual({
|
||||
type: 'exit',
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not emit data events after binary is detected', async () => {
|
||||
mockIsBinary.mockImplementation((buffer) => buffer.includes(0x00));
|
||||
|
||||
await simulateExecution('cat mixed_file', (cp) => {
|
||||
cp.stdout?.emit('data', Buffer.from('some text'));
|
||||
cp.stdout?.emit('data', Buffer.from([0x00, 0x01, 0x02]));
|
||||
cp.stdout?.emit('data', Buffer.from('more text'));
|
||||
cp.emit('exit', 0, null);
|
||||
cp.emit('close', 0, null);
|
||||
});
|
||||
|
||||
const eventTypes = onOutputEventMock.mock.calls.map(
|
||||
(call: [ShellOutputEvent]) => call[0].type,
|
||||
);
|
||||
expect(eventTypes).toEqual(['binary_detected']);
|
||||
expect(eventTypes).toEqual([
|
||||
'binary_detected',
|
||||
'binary_progress',
|
||||
'binary_progress',
|
||||
'exit',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user