mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-30 06:54:15 -07:00
feat: Implement background shell commands (#14849)
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import os from 'node:os';
|
||||
import { spawn as cpSpawn } from 'node:child_process';
|
||||
import { killProcessGroup, SIGKILL_TIMEOUT_MS } from './process-utils.js';
|
||||
|
||||
vi.mock('node:os');
|
||||
vi.mock('node:child_process');
|
||||
|
||||
describe('process-utils', () => {
|
||||
const mockProcessKill = vi
|
||||
.spyOn(process, 'kill')
|
||||
.mockImplementation(() => true);
|
||||
const mockSpawn = vi.mocked(cpSpawn);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe('killProcessGroup', () => {
|
||||
it('should use taskkill on Windows', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('win32');
|
||||
|
||||
await killProcessGroup({ pid: 1234 });
|
||||
|
||||
expect(mockSpawn).toHaveBeenCalledWith('taskkill', [
|
||||
'/pid',
|
||||
'1234',
|
||||
'/f',
|
||||
'/t',
|
||||
]);
|
||||
expect(mockProcessKill).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use pty.kill() on Windows if pty is provided', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('win32');
|
||||
const mockPty = { kill: vi.fn() };
|
||||
|
||||
await killProcessGroup({ pid: 1234, pty: mockPty });
|
||||
|
||||
expect(mockPty.kill).toHaveBeenCalled();
|
||||
expect(mockSpawn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should kill the process group on Unix with SIGKILL by default', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
|
||||
await killProcessGroup({ pid: 1234 });
|
||||
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGKILL');
|
||||
});
|
||||
|
||||
it('should use escalation on Unix if requested', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
const exited = false;
|
||||
const isExited = () => exited;
|
||||
|
||||
const killPromise = killProcessGroup({
|
||||
pid: 1234,
|
||||
escalate: true,
|
||||
isExited,
|
||||
});
|
||||
|
||||
// First call should be SIGTERM
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGTERM');
|
||||
|
||||
// Advance time
|
||||
await vi.advanceTimersByTimeAsync(SIGKILL_TIMEOUT_MS);
|
||||
|
||||
// Second call should be SIGKILL
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGKILL');
|
||||
|
||||
await killPromise;
|
||||
});
|
||||
|
||||
it('should skip SIGKILL if isExited returns true after SIGTERM', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
let exited = false;
|
||||
const isExited = vi.fn().mockImplementation(() => exited);
|
||||
|
||||
const killPromise = killProcessGroup({
|
||||
pid: 1234,
|
||||
escalate: true,
|
||||
isExited,
|
||||
});
|
||||
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGTERM');
|
||||
|
||||
// Simulate process exiting
|
||||
exited = true;
|
||||
|
||||
await vi.advanceTimersByTimeAsync(SIGKILL_TIMEOUT_MS);
|
||||
|
||||
expect(mockProcessKill).not.toHaveBeenCalledWith(-1234, 'SIGKILL');
|
||||
await killPromise;
|
||||
});
|
||||
|
||||
it('should fallback to specific process kill if group kill fails', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
mockProcessKill.mockImplementationOnce(() => {
|
||||
throw new Error('ESRCH');
|
||||
});
|
||||
|
||||
await killProcessGroup({ pid: 1234 });
|
||||
|
||||
// Failed group kill
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGKILL');
|
||||
// Fallback individual kill
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(1234, 'SIGKILL');
|
||||
});
|
||||
|
||||
it('should use pty fallback on Unix if group kill fails', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
mockProcessKill.mockImplementationOnce(() => {
|
||||
throw new Error('ESRCH');
|
||||
});
|
||||
const mockPty = { kill: vi.fn() };
|
||||
|
||||
await killProcessGroup({ pid: 1234, pty: mockPty });
|
||||
|
||||
expect(mockPty.kill).toHaveBeenCalledWith('SIGKILL');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user