refactor: gpt 스트림 오류 최소화
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { ManagedTaskManager } from "../src/managed-task-manager.js";
|
||||
import { ProcessManager } from "../src/process-manager.js";
|
||||
|
||||
function createProcessManager(): ProcessManager {
|
||||
return new ProcessManager({
|
||||
maxRetainedOutputBytes: 32 * 1024,
|
||||
processRetentionMs: 60_000,
|
||||
maxProcesses: 16,
|
||||
defaultMaxOutputBytes: 64 * 1024,
|
||||
});
|
||||
}
|
||||
|
||||
describe("ManagedTaskManager", () => {
|
||||
let temporaryDirectory: string;
|
||||
let processManager: ProcessManager;
|
||||
let tasks: ManagedTaskManager;
|
||||
|
||||
beforeEach(async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), "cokacremote-managed-task-test-"));
|
||||
processManager = createProcessManager();
|
||||
tasks = new ManagedTaskManager(processManager, {
|
||||
logDirectory: path.join(temporaryDirectory, "logs"),
|
||||
defaultTailBytes: 4096,
|
||||
maxTailBytes: 16 * 1024,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await processManager.shutdown();
|
||||
await rm(temporaryDirectory, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("reuses a retained task key instead of executing the same work twice", async () => {
|
||||
const command = "printf run >> marker.txt; sleep 0.2; printf 'done\\n'";
|
||||
const request = {
|
||||
taskKey: "duplicate-protected",
|
||||
executable: "/bin/bash",
|
||||
args: ["-c", command],
|
||||
commandForDisplay: command,
|
||||
cwd: temporaryDirectory,
|
||||
initialWaitMs: 0,
|
||||
};
|
||||
|
||||
const first = await tasks.start(request);
|
||||
const second = await tasks.start(request);
|
||||
|
||||
expect(first.reused).toBe(false);
|
||||
expect(second.reused).toBe(true);
|
||||
expect(second.sessionId).toBe(first.sessionId);
|
||||
|
||||
await processManager.waitForExit(first.sessionId, 2000);
|
||||
const completed = await tasks.read("duplicate-protected");
|
||||
expect(completed).toMatchObject({ running: false, completed: true, exitCode: 0 });
|
||||
expect(await readFile(path.join(temporaryDirectory, "marker.txt"), "utf8")).toBe("run");
|
||||
});
|
||||
|
||||
it("stores complete output while returning only a bounded diagnostic tail", async () => {
|
||||
const command =
|
||||
"node -e \"process.stdout.write('x'.repeat(100000)); process.stderr.write('\\nERROR final-marker\\n')\"";
|
||||
const started = await tasks.start({
|
||||
taskKey: "large-output",
|
||||
executable: "/bin/bash",
|
||||
args: ["-c", command],
|
||||
commandForDisplay: command,
|
||||
cwd: temporaryDirectory,
|
||||
initialWaitMs: 2000,
|
||||
});
|
||||
|
||||
expect(started.completed).toBe(true);
|
||||
const result = await tasks.read("large-output", { tailBytes: 4096 });
|
||||
const completeLog = await readFile(result.logPath, "utf8");
|
||||
|
||||
expect(completeLog.length).toBeGreaterThan(100_000);
|
||||
expect(result.tail.length).toBeLessThanOrEqual(4096);
|
||||
expect(result.tail).toContain("ERROR final-marker");
|
||||
expect(result.importantLines).toEqual(
|
||||
expect.arrayContaining([expect.stringContaining("ERROR final-marker")]),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps important lines from the full log even when they fall outside the returned tail", async () => {
|
||||
const command =
|
||||
"node -e \"console.log('ERROR early-marker'); process.stdout.write('x'.repeat(100000))\"";
|
||||
await tasks.start({
|
||||
taskKey: "early-error",
|
||||
executable: "/bin/bash",
|
||||
args: ["-c", command],
|
||||
commandForDisplay: command,
|
||||
cwd: temporaryDirectory,
|
||||
initialWaitMs: 2000,
|
||||
});
|
||||
|
||||
const result = await tasks.read("early-error", { tailBytes: 4096 });
|
||||
|
||||
expect(result.tail).not.toContain("ERROR early-marker");
|
||||
expect(result.importantLines).toEqual(
|
||||
expect.arrayContaining([expect.stringContaining("ERROR early-marker")]),
|
||||
);
|
||||
});
|
||||
|
||||
it("reuses completed work by default and reruns only when explicitly requested", async () => {
|
||||
const command = "printf x >> restart-marker.txt";
|
||||
const request = {
|
||||
taskKey: "restartable",
|
||||
executable: "/bin/bash",
|
||||
args: ["-c", command],
|
||||
commandForDisplay: command,
|
||||
cwd: temporaryDirectory,
|
||||
initialWaitMs: 2000,
|
||||
};
|
||||
|
||||
const first = await tasks.start(request);
|
||||
const reused = await tasks.start(request);
|
||||
const restarted = await tasks.start({ ...request, restartCompleted: true });
|
||||
|
||||
expect(first.completed).toBe(true);
|
||||
expect(reused).toMatchObject({ reused: true, sessionId: first.sessionId });
|
||||
expect(restarted.reused).toBe(false);
|
||||
expect(restarted.sessionId).not.toBe(first.sessionId);
|
||||
expect(await readFile(path.join(temporaryDirectory, "restart-marker.txt"), "utf8")).toBe(
|
||||
"xx",
|
||||
);
|
||||
});
|
||||
|
||||
it("lists tasks by stable key and can cancel a running task", async () => {
|
||||
const command = "printf started; sleep 10";
|
||||
const started = await tasks.start({
|
||||
taskKey: "recover-me",
|
||||
executable: "/bin/bash",
|
||||
args: ["-c", command],
|
||||
commandForDisplay: command,
|
||||
cwd: temporaryDirectory,
|
||||
initialWaitMs: 0,
|
||||
});
|
||||
|
||||
expect(await tasks.list()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
taskKey: "recover-me",
|
||||
sessionId: started.sessionId,
|
||||
running: true,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await tasks.cancel("recover-me", "SIGTERM", 20);
|
||||
await processManager.waitForExit(started.sessionId, 2000);
|
||||
const cancelled = await tasks.read("recover-me");
|
||||
expect(cancelled.running).toBe(false);
|
||||
expect(cancelled.completed).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user