import type { ChildProcess } from "node:child_process" ;
import { EventEmitter } from "node:events" ;
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest" ;
const spawnMock = vi.hoisted(() => vi.fn());
vi.mock("node:child_process" , async () => {
const actual = await vi.importActual<typeof import ("node:child_process" )>("node:child_process" );
return {
...actual,
spawn: spawnMock,
};
});
type ExecModule = typeof import ("./exec.js" );
let runCommandWithTimeout: ExecModule["runCommandWithTimeout" ];
function createFakeSpawnedChild() {
const child = new EventEmitter() as EventEmitter & ChildProcess;
const stdout = new EventEmitter();
const stderr = new EventEmitter();
let killed = false ;
const kill = vi.fn<(signal?: NodeJS.Signals) => boolean >(() => {
killed = true ;
return true ;
});
Object.defineProperty(child, "killed" , {
get: () => killed,
configurable: true ,
});
Object.defineProperty(child, "pid" , {
value: 12345 ,
configurable: true ,
});
child.stdout = stdout as ChildProcess["stdout" ];
child.stderr = stderr as ChildProcess["stderr" ];
child.stdin = null ;
child.kill = kill as ChildProcess["kill" ];
return { child, stdout, stderr, kill };
}
function emitProcessExit(
fake: ReturnType<typeof createFakeSpawnedChild>,
params?: {
code?: number | null ;
signal?: NodeJS.Signals | null ;
},
) {
const code = params?.code ?? null ;
const signal = params?.signal ?? null ;
fake.child.emit("exit" , code, signal);
fake.child.emit("close" , code, signal);
}
describe("runCommandWithTimeout no-output timer" , () => {
beforeAll(async () => {
vi.resetModules();
({ runCommandWithTimeout } = await import ("./exec.js" ));
});
beforeEach(() => {
spawnMock.mockClear();
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
it("resets no-output timeout when spawned child keeps emitting stdout" , async () => {
vi.useFakeTimers();
const fake = createFakeSpawnedChild();
spawnMock.mockReturnValue(fake.child);
const runPromise = runCommandWithTimeout(["node" , "-e" , "ignored" ], {
timeoutMs: 1 _000 ,
noOutputTimeoutMs: 80 ,
});
fake.stdout.emit("data" , Buffer.from("." ));
await vi.advanceTimersByTimeAsync(40 );
fake.stdout.emit("data" , Buffer.from("." ));
await vi.advanceTimersByTimeAsync(40 );
fake.stdout.emit("data" , Buffer.from("." ));
await vi.advanceTimersByTimeAsync(20 );
fake.child.emit("close" , 0 , null );
const result = await runPromise;
expect(result.code ?? 0 ).toBe(0 );
expect(result.termination).toBe("exit" );
expect(result.noOutputTimedOut).toBe(false );
expect(result.stdout).toBe("..." );
expect(fake.kill).not.toHaveBeenCalled();
});
it("marks no-output timeout when the spawned child goes silent" , async () => {
vi.useFakeTimers();
const fake = createFakeSpawnedChild();
spawnMock.mockReturnValue(fake.child);
const runPromise = runCommandWithTimeout(["node" , "-e" , "ignored" ], {
timeoutMs: 1 _000 ,
noOutputTimeoutMs: 80 ,
});
await vi.advanceTimersByTimeAsync(81 );
expect(fake.kill).toHaveBeenCalledWith("SIGKILL" );
emitProcessExit(fake, { signal: "SIGKILL" });
const result = await runPromise;
expect(result.termination).toBe("no-output-timeout" );
expect(result.noOutputTimedOut).toBe(true );
expect(result.code).not.toBe(0 );
});
it("marks global timeout when overall timeout elapses" , async () => {
vi.useFakeTimers();
const fake = createFakeSpawnedChild();
spawnMock.mockReturnValue(fake.child);
const runPromise = runCommandWithTimeout(["node" , "-e" , "ignored" ], {
timeoutMs: 80 ,
});
await vi.advanceTimersByTimeAsync(81 );
expect(fake.kill).toHaveBeenCalledWith("SIGKILL" );
emitProcessExit(fake, { signal: "SIGKILL" });
const result = await runPromise;
expect(result.termination).toBe("timeout" );
expect(result.noOutputTimedOut).toBe(false );
expect(result.code).not.toBe(0 );
});
});
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland