Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com> Co-authored-by: paisley <8197966+su8su@users.noreply.github.com> Co-authored-by: Felix <24791380+vcfgv@users.noreply.github.com>
138 lines
3.8 KiB
TypeScript
138 lines
3.8 KiB
TypeScript
import { EventEmitter } from 'node:events';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const originalPlatform = process.platform;
|
|
|
|
const {
|
|
mockExec,
|
|
mockCreateServer,
|
|
} = vi.hoisted(() => ({
|
|
mockExec: vi.fn(),
|
|
mockCreateServer: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('electron', () => ({
|
|
app: {
|
|
isPackaged: false,
|
|
getPath: () => '/tmp',
|
|
},
|
|
utilityProcess: {},
|
|
}));
|
|
|
|
vi.mock('child_process', () => ({
|
|
exec: mockExec,
|
|
execSync: vi.fn(),
|
|
spawn: vi.fn(),
|
|
default: {
|
|
exec: mockExec,
|
|
execSync: vi.fn(),
|
|
spawn: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('net', () => ({
|
|
createServer: mockCreateServer,
|
|
}));
|
|
|
|
class MockUtilityChild extends EventEmitter {
|
|
pid?: number;
|
|
kill = vi.fn();
|
|
|
|
constructor(pid?: number) {
|
|
super();
|
|
this.pid = pid;
|
|
}
|
|
}
|
|
|
|
function setPlatform(platform: string): void {
|
|
Object.defineProperty(process, 'platform', { value: platform, writable: true });
|
|
}
|
|
|
|
describe('gateway supervisor process cleanup', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
|
|
mockExec.mockImplementation((_cmd: string, _opts: object, cb: (err: Error | null, stdout: string) => void) => {
|
|
cb(null, '');
|
|
return {} as never;
|
|
});
|
|
|
|
mockCreateServer.mockImplementation(() => {
|
|
const handlers = new Map<string, (...args: unknown[]) => void>();
|
|
return {
|
|
once(event: string, callback: (...args: unknown[]) => void) {
|
|
handlers.set(event, callback);
|
|
return this;
|
|
},
|
|
listen() {
|
|
queueMicrotask(() => handlers.get('listening')?.());
|
|
return this;
|
|
},
|
|
close(callback?: () => void) {
|
|
callback?.();
|
|
},
|
|
};
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true });
|
|
});
|
|
|
|
it('uses taskkill tree strategy for owned process on Windows', async () => {
|
|
setPlatform('win32');
|
|
const child = new MockUtilityChild(4321);
|
|
const { terminateOwnedGatewayProcess } = await import('@electron/gateway/supervisor');
|
|
|
|
const stopPromise = terminateOwnedGatewayProcess(child as unknown as Electron.UtilityProcess);
|
|
child.emit('exit', 0);
|
|
await stopPromise;
|
|
|
|
await vi.waitFor(() => {
|
|
expect(mockExec).toHaveBeenCalledWith(
|
|
'taskkill /F /PID 4321 /T',
|
|
expect.objectContaining({ timeout: 5000, windowsHide: true }),
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
expect(child.kill).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('uses direct child.kill for owned process on non-Windows', async () => {
|
|
setPlatform('linux');
|
|
const child = new MockUtilityChild(9876);
|
|
const { terminateOwnedGatewayProcess } = await import('@electron/gateway/supervisor');
|
|
|
|
const stopPromise = terminateOwnedGatewayProcess(child as unknown as Electron.UtilityProcess);
|
|
child.emit('exit', 0);
|
|
await stopPromise;
|
|
|
|
expect(child.kill).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('waits for port release after orphan cleanup on Windows', async () => {
|
|
setPlatform('win32');
|
|
const { findExistingGatewayProcess } = await import('@electron/gateway/supervisor');
|
|
|
|
mockExec.mockImplementation((cmd: string, _opts: object, cb: (err: Error | null, stdout: string) => void) => {
|
|
if (cmd.includes('netstat -ano')) {
|
|
cb(null, ' TCP 127.0.0.1:18789 0.0.0.0:0 LISTENING 4321\n');
|
|
return {} as never;
|
|
}
|
|
cb(null, '');
|
|
return {} as never;
|
|
});
|
|
|
|
const result = await findExistingGatewayProcess({ port: 18789 });
|
|
expect(result).toBeNull();
|
|
|
|
expect(mockExec).toHaveBeenCalledWith(
|
|
expect.stringContaining('taskkill /F /PID 4321 /T'),
|
|
expect.objectContaining({ timeout: 5000, windowsHide: true }),
|
|
expect.any(Function),
|
|
);
|
|
expect(mockCreateServer).toHaveBeenCalled();
|
|
});
|
|
});
|