mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-07 20:00:37 -07:00
fix: copy command delay in Linux handled (#6856)
Co-authored-by: Hriday Taneja <hridayt@google.com>
This commit is contained in:
@@ -23,12 +23,15 @@ const mockProcess = vi.hoisted(() => ({
|
|||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.stubGlobal('process', {
|
vi.stubGlobal(
|
||||||
...process,
|
'process',
|
||||||
get platform() {
|
Object.create(process, {
|
||||||
return mockProcess.platform;
|
platform: {
|
||||||
},
|
get: () => mockProcess.platform,
|
||||||
});
|
configurable: true, // Allows the property to be changed later if needed
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
interface MockChildProcess extends EventEmitter {
|
interface MockChildProcess extends EventEmitter {
|
||||||
stdin: EventEmitter & {
|
stdin: EventEmitter & {
|
||||||
@@ -53,8 +56,14 @@ describe('commandUtils', () => {
|
|||||||
stdin: Object.assign(new EventEmitter(), {
|
stdin: Object.assign(new EventEmitter(), {
|
||||||
write: vi.fn(),
|
write: vi.fn(),
|
||||||
end: vi.fn(),
|
end: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
}),
|
||||||
|
stdout: Object.assign(new EventEmitter(), {
|
||||||
|
destroy: vi.fn(),
|
||||||
|
}),
|
||||||
|
stderr: Object.assign(new EventEmitter(), {
|
||||||
|
destroy: vi.fn(),
|
||||||
}),
|
}),
|
||||||
stderr: new EventEmitter(),
|
|
||||||
}) as MockChildProcess;
|
}) as MockChildProcess;
|
||||||
|
|
||||||
mockSpawn.mockReturnValue(mockChild as unknown as ReturnType<typeof spawn>);
|
mockSpawn.mockReturnValue(mockChild as unknown as ReturnType<typeof spawn>);
|
||||||
@@ -220,6 +229,70 @@ describe('commandUtils', () => {
|
|||||||
expect(mockChild.stdin.end).toHaveBeenCalled();
|
expect(mockChild.stdin.end).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should successfully copy on Linux when receiving an "exit" event', async () => {
|
||||||
|
const testText = 'Hello, linux!';
|
||||||
|
const linuxOptions: SpawnOptions = {
|
||||||
|
stdio: ['pipe', 'inherit', 'pipe'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simulate successful execution via 'exit' event
|
||||||
|
setTimeout(() => {
|
||||||
|
mockChild.emit('exit', 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
await copyToClipboard(testText);
|
||||||
|
|
||||||
|
expect(mockSpawn).toHaveBeenCalledWith(
|
||||||
|
'xclip',
|
||||||
|
['-selection', 'clipboard'],
|
||||||
|
linuxOptions,
|
||||||
|
);
|
||||||
|
expect(mockChild.stdin.write).toHaveBeenCalledWith(testText);
|
||||||
|
expect(mockChild.stdin.end).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle command failure on Linux via "exit" event', async () => {
|
||||||
|
const testText = 'Hello, linux!';
|
||||||
|
let callCount = 0;
|
||||||
|
|
||||||
|
mockSpawn.mockImplementation(() => {
|
||||||
|
const child = Object.assign(new EventEmitter(), {
|
||||||
|
stdin: Object.assign(new EventEmitter(), {
|
||||||
|
write: vi.fn(),
|
||||||
|
end: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
}),
|
||||||
|
stdout: Object.assign(new EventEmitter(), {
|
||||||
|
destroy: vi.fn(),
|
||||||
|
}),
|
||||||
|
stderr: Object.assign(new EventEmitter(), {
|
||||||
|
destroy: vi.fn(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (callCount === 0) {
|
||||||
|
// First call (xclip) fails with 'exit'
|
||||||
|
child.stderr.emit('data', 'xclip failed');
|
||||||
|
child.emit('exit', 127);
|
||||||
|
} else {
|
||||||
|
// Second call (xsel) also fails with 'exit'
|
||||||
|
child.stderr.emit('data', 'xsel failed');
|
||||||
|
child.emit('exit', 127);
|
||||||
|
}
|
||||||
|
callCount++;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return child as unknown as ReturnType<typeof spawn>;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(copyToClipboard(testText)).rejects.toThrow(
|
||||||
|
'All copy commands failed. "\'xclip\' exited with code 127: xclip failed", "\'xsel\' exited with code 127: xsel failed".',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockSpawn).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
it('should fall back to xsel when xclip fails', async () => {
|
it('should fall back to xsel when xclip fails', async () => {
|
||||||
const testText = 'Hello, world!';
|
const testText = 'Hello, world!';
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ export const copyToClipboard = async (text: string): Promise<void> => {
|
|||||||
if (child.stderr) {
|
if (child.stderr) {
|
||||||
child.stderr.on('data', (chunk) => (stderr += chunk.toString()));
|
child.stderr.on('data', (chunk) => (stderr += chunk.toString()));
|
||||||
}
|
}
|
||||||
child.on('error', reject);
|
const copyResult = (code: number | null) => {
|
||||||
child.on('close', (code) => {
|
|
||||||
if (code === 0) return resolve();
|
if (code === 0) return resolve();
|
||||||
const errorMsg = stderr.trim();
|
const errorMsg = stderr.trim();
|
||||||
reject(
|
reject(
|
||||||
@@ -62,7 +61,28 @@ export const copyToClipboard = async (text: string): Promise<void> => {
|
|||||||
`'${cmd}' exited with code ${code}${errorMsg ? `: ${errorMsg}` : ''}`,
|
`'${cmd}' exited with code ${code}${errorMsg ? `: ${errorMsg}` : ''}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The 'exit' event workaround is only needed for the specific stdio
|
||||||
|
// configuration used on Linux.
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
child.stdin?.destroy();
|
||||||
|
child.stdout?.destroy();
|
||||||
|
child.stderr?.destroy();
|
||||||
|
copyResult(code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
child.on('error', reject);
|
||||||
|
|
||||||
|
// For win32/darwin, 'close' is the safest event, guaranteeing all I/O is flushed.
|
||||||
|
// For Linux, this acts as a fallback. This is safe because the promise
|
||||||
|
// can only be settled once.
|
||||||
|
child.on('close', (code) => {
|
||||||
|
copyResult(code);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (child.stdin) {
|
if (child.stdin) {
|
||||||
child.stdin.on('error', reject);
|
child.stdin.on('error', reject);
|
||||||
child.stdin.write(text);
|
child.stdin.write(text);
|
||||||
|
|||||||
Reference in New Issue
Block a user