Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions test/unit/miner-attempt-worktree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ describe("createRealWorktreeExec (#5132)", () => {

describe("prepareAttemptWorktree / cleanupAttemptWorktree (#5132)", () => {
it("REGRESSION: worktreePath is a real, checked-out git repo on a real branch, not an empty directory", async () => {
// Six real, sequential git subprocess spawns (origin init/add/commit, the clone inside
// prepareAttemptWorktree, its `git worktree add`, and this test's own rev-parse) -- legitimately more
// wall-clock latency than the default 15s test timeout reliably covers under concurrent full-suite
// load (passes in well under 1s in isolation; the same class of flake fixed for
// test/unit/agent-sdk-driver.test.ts's real-git-subprocess test).
const root = tempRoot("loopover-miner-attempt-worktree-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -72,9 +77,11 @@ describe("prepareAttemptWorktree / cleanupAttemptWorktree (#5132)", () => {
// And it's a real, distinct branch -- not just a copy of main.
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: result.worktreePath, encoding: "utf8" }).trim();
expect(branch).toBe("loopover/attempt/attempt-1");
});
}, 60000);

it("removes a succeeded attempt's worktree but retains a failed one's, per the engine's own retention policy", async () => {
// Real git subprocess round trip -- two full prepareAttemptWorktree/cleanupAttemptWorktree cycles, more
// real spawns than the REGRESSION test above. See its comment for why this needs an explicit timeout.
const root = tempRoot("loopover-miner-attempt-worktree-cleanup-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -90,7 +97,7 @@ describe("prepareAttemptWorktree / cleanupAttemptWorktree (#5132)", () => {
const retainedResult = await cleanupAttemptWorktree(failed.repoPath, failed.worktreePath, false);
expect(retainedResult).toEqual({ ok: true, removed: false });
expect(existsSync(failed.worktreePath)).toBe(true);
});
}, 60000);

it("returns ok:false when the base clone cannot be prepared, without attempting git worktree add", async () => {
const root = tempRoot("loopover-miner-attempt-worktree-clonefail-");
Expand All @@ -111,6 +118,8 @@ describe("prepareAttemptWorktree / cleanupAttemptWorktree (#5132)", () => {
});

it("returns ok:false with git's real stderr when git worktree add fails (e.g. an unknown base branch)", async () => {
// Real git subprocess round trip (origin init + a real clone + a failing `git worktree add`). See the
// REGRESSION test above for why this needs an explicit timeout.
const root = tempRoot("loopover-miner-attempt-worktree-addfail-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -121,5 +130,5 @@ describe("prepareAttemptWorktree / cleanupAttemptWorktree (#5132)", () => {
if (result.ok) throw new Error("expected failure");
expect(result.repoPath).toBe(join(cloneBaseDir, "acme", "widgets"));
expect(result.error).toBeTruthy();
});
}, 60000);
});
26 changes: 21 additions & 5 deletions test/unit/miner-repo-clone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ describe("resolveRepoCloneBaseDir / resolveRepoCloneDir (#5132)", () => {

describe("ensureRepoCloned (#5132)", () => {
it("clones a real repo on first use, and fetches + hard-resets an existing clone to pick up new commits", async () => {
// Nine real, sequential git subprocess spawns (origin init/add/commit, the first ensureRepoCloned's
// clone, a second origin commit, and the second ensureRepoCloned's fetch+checkout+reset) --
// legitimately more wall-clock latency than the default 15s test timeout reliably covers under
// concurrent full-suite load (passes in well under 1s in isolation; the same class of flake fixed for
// test/unit/agent-sdk-driver.test.ts's real-git-subprocess test).
const root = tempRoot("loopover-miner-repo-clone-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -80,9 +85,14 @@ describe("ensureRepoCloned (#5132)", () => {
expect(second.ok).toBe(true);
expect(readFileSync(join(second.repoPath, "README.md"), "utf8")).toBe("hello\n");
expect(readFileSync(join(second.repoPath, "second.txt"), "utf8")).toBe("second file\n");
});
}, 60000);

it("respects a non-default baseBranch on the fetch+reset path", async () => {
// Ten real, sequential git subprocess spawns (origin init/add/commit, the branch checkout, the first
// ensureRepoCloned's clone, the second commitFile's add/commit, and the second ensureRepoCloned's
// fetch+checkout+reset) -- legitimately more wall-clock latency than the default 15s test timeout
// reliably covers under concurrent full-suite load (passes in well under 1s in isolation; the same
// class of flake fixed for test/unit/agent-sdk-driver.test.ts's real-git-subprocess test).
const root = tempRoot("loopover-miner-repo-clone-branch-");
const originPath = initOriginRepo(root);
execFileSync("git", ["checkout", "-b", "develop"], { cwd: originPath, stdio: "ignore" });
Expand All @@ -95,7 +105,7 @@ describe("ensureRepoCloned (#5132)", () => {
const second = await ensureRepoCloned("acme/widgets", { cloneBaseDir, remoteUrl: originPath, baseBranch: "develop" });
expect(second.ok).toBe(true);
expect(readFileSync(join(second.repoPath, "develop-only.txt"), "utf8")).toBe("develop content\n");
});
}, 60000);

it("rejects a malformed repoFullName", async () => {
await expect(ensureRepoCloned("not-a-repo")).rejects.toThrow("invalid_repo_full_name");
Expand All @@ -110,6 +120,8 @@ describe("ensureRepoCloned (#5132)", () => {
});

it("returns ok:false on a fetch failure without touching the existing clone (injected runGit)", async () => {
// Real origin init + a real clone before the injected-runGit assertion. See the first test in this
// block for why this needs an explicit timeout.
const root = tempRoot("loopover-miner-repo-clone-fetchfail-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -120,9 +132,11 @@ describe("ensureRepoCloned (#5132)", () => {
const second = await ensureRepoCloned("acme/widgets", { cloneBaseDir, remoteUrl: originPath, runGit });
expect(second.ok).toBe(false);
expect(second.error).toBe("network unreachable");
});
}, 60000);

it("returns ok:false on a checkout failure and a reset failure (injected runGit)", async () => {
// Real origin init + a real clone before the injected-runGit assertions. See the first test in this
// block for why this needs an explicit timeout.
const root = tempRoot("loopover-miner-repo-clone-checkoutfail-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -137,7 +151,7 @@ describe("ensureRepoCloned (#5132)", () => {
const resetResult = await ensureRepoCloned("acme/widgets", { cloneBaseDir, remoteUrl: originPath, runGit: resetFails });
expect(resetResult.ok).toBe(false);
expect(resetResult.error).toBe("git_reset_failed");
});
}, 60000);

it("returns ok:false with a fallback error message on a clone failure with no stderr (injected runGit)", async () => {
const root = tempRoot("loopover-miner-repo-clone-nostderr-");
Expand All @@ -149,6 +163,8 @@ describe("ensureRepoCloned (#5132)", () => {
});

it("rejects a dash-prefixed baseBranch before invoking git (#5923)", async () => {
// Real origin init + a real clone before the injected-runGit assertion. See the first test in this
// block for why this needs an explicit timeout.
const root = tempRoot("loopover-miner-repo-clone-unsafe-branch-");
const originPath = initOriginRepo(root);
const cloneBaseDir = join(root, "cache");
Expand All @@ -163,7 +179,7 @@ describe("ensureRepoCloned (#5132)", () => {
expect(result.ok).toBe(false);
expect(result.error).toBe("invalid_base_branch");
expect(runGitCalls).toBe(0);
});
}, 60000);

it("rejects a dash-prefixed remoteUrl before invoking git (#5923)", async () => {
const root = tempRoot("loopover-miner-repo-clone-unsafe-url-");
Expand Down