GPU_GUARD_MONOREPO/packages/backend/test/process_spawn_options.test.ts

118 lines
3.5 KiB
TypeScript
Raw Permalink Normal View History

2026-05-20 21:39:12 +08:00
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { spawn, resolveShellConfig } from '../src/comm/child_process.js';
describe('process spawn options', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
const originalEnv = process.env;
const tempDirs: string[] = [];
afterEach(() => {
jest.clearAllMocks();
process.env = originalEnv;
if (originalPlatform) {
Object.defineProperty(process, 'platform', originalPlatform);
}
tempDirs.splice(0).forEach(dir => fs.rmSync(dir, { recursive: true, force: true }));
});
it('spawn automatically injects windowsHide: true', () => {
const spawnMock = jest.fn().mockReturnValue(createFakeChild());
jest.resetModules();
jest.doMock('child_process', () => ({
...jest.requireActual('child_process'),
spawn: spawnMock,
}));
let wrappedSpawn: typeof spawn;
jest.isolateModules(() => {
({ spawn: wrappedSpawn } = require('../src/comm/child_process.js'));
});
wrappedSpawn!('echo', ['hello'], { stdio: 'ignore' });
expect(spawnMock).toHaveBeenCalledWith(
'echo',
['hello'],
expect.objectContaining({ stdio: 'ignore', windowsHide: true })
);
});
it('uses Git Bash before cmd.exe on Windows when available', () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-git-bash-'));
tempDirs.push(root);
const gitBash = path.join(root, 'Git', 'bin', 'bash.exe');
fs.mkdirSync(path.dirname(gitBash), { recursive: true });
fs.writeFileSync(gitBash, '', 'utf8');
process.env = {
...originalEnv,
ProgramFiles: root,
'ProgramFiles(x86)': '',
ComSpec: 'C:\\Windows\\System32\\cmd.exe',
};
const config = resolveShellConfig();
expect(config).toEqual({
shell: gitBash,
args: ['-lc'],
});
});
it('hides subagent worker process windows', async () => {
const fakeChild = createFakeChild();
const spawnMock = jest.fn().mockReturnValue(fakeChild);
jest.resetModules();
jest.doMock('child_process', () => ({
spawn: spawnMock,
}));
let runner: any;
let protocolVersion: number;
await jest.isolateModulesAsync(async () => {
({ SubagentProcessRunner: runner } = await import('../src/modules/netaclaw/subagent/process_runner.js'));
({ SUBAGENT_WORKER_PROTOCOL_VERSION: protocolVersion } = await import('../src/modules/netaclaw/subagent/process_protocol.js'));
});
const instance = new runner({ workerPath: 'worker.js' });
void instance.run({
protocolVersion,
runId: 'run-hidden',
sessionId: 'session-1',
parentMessageId: 1,
parentToolCallId: 'tool-1',
parentAgentId: 1,
task: { goal: 'test' },
agentConfig: { name: 'agent', systemPrompt: 'test', model: 'openai:gpt-5.4', apiKey: 'test-key' },
toolNames: [],
}).catch(() => undefined);
expect(spawnMock).toHaveBeenCalledWith(
expect.any(String),
expect.any(Array),
expect.objectContaining({ windowsHide: true })
);
});
});
function createFakeChild() {
const { PassThrough } = require('stream');
const { EventEmitter } = require('events');
const child = new EventEmitter();
child.stdin = new PassThrough();
child.stdout = new PassThrough();
child.stderr = new PassThrough();
child.killed = false;
child.kill = jest.fn(() => {
child.killed = true;
child.emit('close', 0);
return true;
});
return child;
}