feat(FN-5455): merge fusion/fn-5455
This commit is contained in:
@@ -239,18 +239,54 @@ describe("FN-5420 reliability interactions: PR mode worktree invariants", () =>
|
||||
}
|
||||
});
|
||||
|
||||
it.fails("FN-5420/FN-4954/FN-4811 follow-up required: cleanup should release pool lease and clear active-session registry", async () => {
|
||||
it("FN-5455: PR-mode cleanupMergedTaskArtifacts releases WorktreePool lease for task worktree", async () => {
|
||||
const pool = new WorktreePool();
|
||||
const path = "/tmp/fn-5420-leased";
|
||||
(pool as any).getLeasedPaths().set(path, "FN-5420-LEAK");
|
||||
activeSessionRegistry.registerPath(path, { taskId: "FN-5420-LEAK", kind: "executor", ownerKey: "FN-5420-LEAK" });
|
||||
const path = "/tmp/fn-5455-leased";
|
||||
(pool as any).getLeasedPaths().set(path, "FN-5455-LEAK");
|
||||
|
||||
const spy = vi.spyOn((pool as any).constructor.prototype, "release");
|
||||
const releaseSpy = vi.spyOn(pool, "release");
|
||||
const { cleanupMergedTaskArtifacts } = await loadPrLifecycleModule();
|
||||
await cleanupMergedTaskArtifacts("/tmp", { id: "FN-5420-LEAK", worktree: path } as any);
|
||||
await cleanupMergedTaskArtifacts("/tmp", { id: "FN-5455-LEAK", worktree: path } as any, { pool });
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(activeSessionRegistry.lookupByPath(path)).toBeNull();
|
||||
expect(releaseSpy).toHaveBeenCalledTimes(1);
|
||||
expect(releaseSpy).toHaveBeenCalledWith(path, "FN-5455-LEAK");
|
||||
expect(pool.getLeasedPaths().has(path)).toBe(false);
|
||||
expect(pool.getLeasedPaths().get(path)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("FN-5455: cleanupMergedTaskArtifacts is a no-op for pool when options.pool is omitted", async () => {
|
||||
const { cleanupMergedTaskArtifacts } = await loadPrLifecycleModule();
|
||||
await expect(
|
||||
cleanupMergedTaskArtifacts("/tmp", { id: "FN-5455-NO-POOL", worktree: "/tmp/fn-5455-no-pool" } as any),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("FN-5455: cleanupMergedTaskArtifacts calls pool.release even when worktree directory is already gone", async () => {
|
||||
const pool = new WorktreePool();
|
||||
const path = "/tmp/fn-5455-missing";
|
||||
(pool as any).getLeasedPaths().set(path, "FN-5455-MISSING");
|
||||
const releaseSpy = vi.spyOn(pool, "release");
|
||||
const { cleanupMergedTaskArtifacts } = await loadPrLifecycleModule();
|
||||
await cleanupMergedTaskArtifacts("/tmp", { id: "FN-5455-MISSING", worktree: path } as any, { pool });
|
||||
expect(releaseSpy).toHaveBeenCalledWith(path, "FN-5455-MISSING");
|
||||
expect(pool.getLeasedPaths().has(path)).toBe(false);
|
||||
});
|
||||
|
||||
it("FN-5455: cleanupMergedTaskArtifacts swallows pool.release errors (best-effort)", async () => {
|
||||
const pool = new WorktreePool();
|
||||
const path = "/tmp/fn-5455-release-throws";
|
||||
const releaseSpy = vi.spyOn(pool, "release").mockImplementation(() => {
|
||||
throw new Error("release failed");
|
||||
});
|
||||
const { cleanupMergedTaskArtifacts } = await loadPrLifecycleModule();
|
||||
await expect(cleanupMergedTaskArtifacts("/tmp", { id: "FN-5455-THROW", worktree: path } as any, { pool })).resolves.toBeUndefined();
|
||||
expect(releaseSpy).toHaveBeenCalledWith(path, "FN-5455-THROW");
|
||||
});
|
||||
|
||||
it.skip("FN-5456 follow-up required: cleanup should clear active-session registry entry", async () => {
|
||||
const path = "/tmp/fn-5456-session";
|
||||
activeSessionRegistry.registerPath(path, { taskId: "FN-5456", kind: "executor", ownerKey: "FN-5456" });
|
||||
expect(activeSessionRegistry.lookupByPath(path)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("FN-5420/FN-5279: mergeIntegrationWorktree setting does not gate PR-mode processing", async () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { compareTasksByPriorityThenAgeAndId, getTaskHardMergeBlocker, sortTasksB
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { InProcessRuntime } from "./runtimes/in-process-runtime.js";
|
||||
import type { WorktreePool } from "./worktree-pool.js";
|
||||
import type { ProjectRuntimeConfig } from "./project-runtime.js";
|
||||
import { PrMonitor } from "./pr-monitor.js";
|
||||
import { PrCommentHandler } from "./pr-comment-handler.js";
|
||||
@@ -55,6 +56,7 @@ export type ProcessPullRequestMergeFn = (
|
||||
store: TaskStore,
|
||||
cwd: string,
|
||||
taskId: string,
|
||||
pool?: WorktreePool,
|
||||
) => Promise<"merged" | "waiting" | "skipped">;
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
@@ -1483,7 +1485,12 @@ export class ProjectEngine {
|
||||
if (mergeStrategy === "pull-request" && this.options.processPullRequestMerge) {
|
||||
this.activeMergeTaskId = taskId;
|
||||
runtimeLog.log(`${manualResolver ? "Manual" : "Auto"}-merge processing PR flow for ${taskId}...`);
|
||||
const result = await this.options.processPullRequestMerge(store, cwd, taskId);
|
||||
const result = await this.options.processPullRequestMerge(
|
||||
store,
|
||||
cwd,
|
||||
taskId,
|
||||
(this.runtime as any).worktreePool,
|
||||
);
|
||||
if (result === "merged") {
|
||||
runtimeLog.log(`${manualResolver ? "Manual" : "Auto"}-merge PR merged: ${taskId}`);
|
||||
const mergedTask = await store.getTask(taskId).catch(() => null);
|
||||
|
||||
Reference in New Issue
Block a user