takt/src/__tests__/shutdownManager.test.ts
nrs 680f0a6df5
github-issue-237-fix-ctrl-c-provider-graceful-timeout-force (#253)
* dist-tag 検証をリトライ付きに変更(npm レジストリの結果整合性対策)

* takt run 実行時に蓋閉じスリープを抑制

* takt: github-issue-237-fix-ctrl-c-provider-graceful-timeout-force
2026-02-12 11:52:07 +09:00

174 lines
4.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const {
mockWarn,
mockError,
mockBlankLine,
mockGetLabel,
} = vi.hoisted(() => ({
mockWarn: vi.fn(),
mockError: vi.fn(),
mockBlankLine: vi.fn(),
mockGetLabel: vi.fn((key: string) => key),
}));
vi.mock('../shared/ui/index.js', () => ({
warn: mockWarn,
error: mockError,
blankLine: mockBlankLine,
}));
vi.mock('../shared/i18n/index.js', () => ({
getLabel: mockGetLabel,
}));
import { ShutdownManager } from '../features/tasks/execute/shutdownManager.js';
describe('ShutdownManager', () => {
let savedSigintListeners: ((...args: unknown[]) => void)[];
let originalShutdownTimeoutEnv: string | undefined;
beforeEach(() => {
vi.clearAllMocks();
savedSigintListeners = process.rawListeners('SIGINT') as ((...args: unknown[]) => void)[];
originalShutdownTimeoutEnv = process.env.TAKT_SHUTDOWN_TIMEOUT_MS;
delete process.env.TAKT_SHUTDOWN_TIMEOUT_MS;
});
afterEach(() => {
vi.useRealTimers();
process.removeAllListeners('SIGINT');
for (const listener of savedSigintListeners) {
process.on('SIGINT', listener as NodeJS.SignalsListener);
}
if (originalShutdownTimeoutEnv === undefined) {
delete process.env.TAKT_SHUTDOWN_TIMEOUT_MS;
} else {
process.env.TAKT_SHUTDOWN_TIMEOUT_MS = originalShutdownTimeoutEnv;
}
});
it('1回目SIGINTでgracefulコールバックを呼ぶ', () => {
const onGraceful = vi.fn();
const onForceKill = vi.fn();
const manager = new ShutdownManager({
callbacks: { onGraceful, onForceKill },
gracefulTimeoutMs: 1_000,
});
manager.install();
const listeners = process.rawListeners('SIGINT') as Array<() => void>;
listeners[listeners.length - 1]!();
expect(onGraceful).toHaveBeenCalledTimes(1);
expect(onForceKill).not.toHaveBeenCalled();
expect(mockWarn).toHaveBeenCalledWith('piece.sigintGraceful');
manager.cleanup();
});
it('graceful timeoutでforceコールバックを呼ぶ', () => {
vi.useFakeTimers();
const onGraceful = vi.fn();
const onForceKill = vi.fn();
const manager = new ShutdownManager({
callbacks: { onGraceful, onForceKill },
gracefulTimeoutMs: 50,
});
manager.install();
const listeners = process.rawListeners('SIGINT') as Array<() => void>;
listeners[listeners.length - 1]!();
vi.advanceTimersByTime(50);
expect(onGraceful).toHaveBeenCalledTimes(1);
expect(onForceKill).toHaveBeenCalledTimes(1);
expect(mockError).toHaveBeenCalledWith('piece.sigintTimeout');
expect(mockError).toHaveBeenCalledWith('piece.sigintForce');
manager.cleanup();
});
it('2回目SIGINTで即時forceコールバックを呼び、timeoutを待たない', () => {
vi.useFakeTimers();
const onGraceful = vi.fn();
const onForceKill = vi.fn();
const manager = new ShutdownManager({
callbacks: { onGraceful, onForceKill },
gracefulTimeoutMs: 10_000,
});
manager.install();
const listeners = process.rawListeners('SIGINT') as Array<() => void>;
const handler = listeners[listeners.length - 1]!;
handler();
handler();
vi.advanceTimersByTime(10_000);
expect(onGraceful).toHaveBeenCalledTimes(1);
expect(onForceKill).toHaveBeenCalledTimes(1);
expect(mockError).toHaveBeenCalledWith('piece.sigintForce');
manager.cleanup();
});
it('環境変数未設定時はデフォルト10_000msを使う', () => {
vi.useFakeTimers();
const onGraceful = vi.fn();
const onForceKill = vi.fn();
const manager = new ShutdownManager({
callbacks: { onGraceful, onForceKill },
});
manager.install();
const listeners = process.rawListeners('SIGINT') as Array<() => void>;
listeners[listeners.length - 1]!();
vi.advanceTimersByTime(9_999);
expect(onForceKill).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(onForceKill).toHaveBeenCalledTimes(1);
manager.cleanup();
});
it('環境変数設定時はその値をtimeoutとして使う', () => {
vi.useFakeTimers();
process.env.TAKT_SHUTDOWN_TIMEOUT_MS = '25';
const onGraceful = vi.fn();
const onForceKill = vi.fn();
const manager = new ShutdownManager({
callbacks: { onGraceful, onForceKill },
});
manager.install();
const listeners = process.rawListeners('SIGINT') as Array<() => void>;
listeners[listeners.length - 1]!();
vi.advanceTimersByTime(24);
expect(onForceKill).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(onForceKill).toHaveBeenCalledTimes(1);
manager.cleanup();
});
it('不正な環境変数値ではエラーをthrowする', () => {
process.env.TAKT_SHUTDOWN_TIMEOUT_MS = '0';
expect(
() =>
new ShutdownManager({
callbacks: { onGraceful: vi.fn(), onForceKill: vi.fn() },
}),
).toThrowError('TAKT_SHUTDOWN_TIMEOUT_MS must be a positive integer');
});
});