feat(KB-616): add ProjectRuntime abstraction for task execution isolation
- Define ProjectRuntime interface with unified task execution contract\n- Implement IPC protocol for host-worker communication (messages, streaming, heartbeats)\n- Add InProcessRuntime for synchronous in-process task execution\n- Add ChildProcessRuntime with isolated worker processes for sandboxed execution\n- Implement ProjectManager to coordinate runtime selection and task lifecycle\n- Update engine exports to expose runtime APIs\n- Add comprehensive tests for all runtime implementations and IPC protocol
This commit is contained in:
102
packages/engine/src/runtimes/child-process-runtime.test.ts
Normal file
102
packages/engine/src/runtimes/child-process-runtime.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import type { CentralCore } from "@fusion/core";
|
||||
import { ChildProcessRuntime } from "./child-process-runtime.js";
|
||||
import type { ProjectRuntimeConfig } from "../project-runtime.js";
|
||||
|
||||
// Mock child_process
|
||||
vi.mock("node:child_process", () => ({
|
||||
fork: vi.fn().mockReturnValue({
|
||||
on: vi.fn(),
|
||||
kill: vi.fn(),
|
||||
killed: false,
|
||||
connected: false,
|
||||
send: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("ChildProcessRuntime", () => {
|
||||
let runtime: ChildProcessRuntime;
|
||||
let mockCentralCore: CentralCore;
|
||||
const testConfig: ProjectRuntimeConfig = {
|
||||
projectId: "proj_test123",
|
||||
workingDirectory: "/tmp/test-project",
|
||||
isolationMode: "child-process",
|
||||
maxConcurrent: 2,
|
||||
maxWorktrees: 4,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockCentralCore = {
|
||||
getGlobalConcurrencyState: vi.fn().mockResolvedValue({
|
||||
globalMaxConcurrent: 4,
|
||||
currentlyActive: 0,
|
||||
queuedCount: 0,
|
||||
projectsActive: {},
|
||||
}),
|
||||
} as unknown as CentralCore;
|
||||
|
||||
runtime = new ChildProcessRuntime(testConfig, mockCentralCore);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await runtime.stop();
|
||||
} catch {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("lifecycle", () => {
|
||||
it("should start with status 'stopped'", () => {
|
||||
expect(runtime.getStatus()).toBe("stopped");
|
||||
});
|
||||
|
||||
it("should throw when getting TaskStore", () => {
|
||||
expect(() => runtime.getTaskStore()).toThrow("not accessible in ChildProcessRuntime");
|
||||
});
|
||||
|
||||
it("should throw when getting Scheduler", () => {
|
||||
expect(() => runtime.getScheduler()).toThrow("not accessible in ChildProcessRuntime");
|
||||
});
|
||||
|
||||
it("should return metrics even when stopped", () => {
|
||||
const metrics = runtime.getMetrics();
|
||||
expect(metrics.inFlightTasks).toBe(0);
|
||||
expect(metrics.activeAgents).toBe(0);
|
||||
expect(metrics.lastActivityAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("configuration", () => {
|
||||
it("should store projectId in config", () => {
|
||||
expect(testConfig.projectId).toBe("proj_test123");
|
||||
});
|
||||
|
||||
it("should store workingDirectory in config", () => {
|
||||
expect(testConfig.workingDirectory).toBe("/tmp/test-project");
|
||||
});
|
||||
|
||||
it("should have child-process isolation mode", () => {
|
||||
expect(testConfig.isolationMode).toBe("child-process");
|
||||
});
|
||||
});
|
||||
|
||||
describe("event handling", () => {
|
||||
it("should support health-changed event", () => {
|
||||
const handler = vi.fn();
|
||||
runtime.on("health-changed", handler);
|
||||
|
||||
// The constructor may emit health-changed, so we just verify
|
||||
// the event listener can be registered
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should support error event", () => {
|
||||
const handler = vi.fn();
|
||||
runtime.on("error", handler);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user