CB-576: prove teardown survives an already-gone worktree
This commit is contained in:
@@ -32,6 +32,8 @@ public final class FakeWorktrees implements Worktrees {
|
|||||||
private volatile boolean dirty = false;
|
private volatile boolean dirty = false;
|
||||||
private volatile String repoRoot = "/repo";
|
private volatile String repoRoot = "/repo";
|
||||||
private volatile String prefix = "/worktrees";
|
private volatile String prefix = "/worktrees";
|
||||||
|
/** Worktree paths that currently exist, mirroring real {@code Files.exists} for the gone case. */
|
||||||
|
private final Set<String> worktreePaths = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
public FakeWorktrees withRepoRoot(String root) {
|
public FakeWorktrees withRepoRoot(String root) {
|
||||||
this.repoRoot = root;
|
this.repoRoot = root;
|
||||||
@@ -76,7 +78,15 @@ public final class FakeWorktrees implements Worktrees {
|
|||||||
}
|
}
|
||||||
// The branch already carries a unique nonce, so the derived path is distinct per acquire
|
// The branch already carries a unique nonce, so the derived path is distinct per acquire
|
||||||
// without an extra counter — keep it a pure function of the branch the test can predict.
|
// without an extra counter — keep it a pure function of the branch the test can predict.
|
||||||
return prefix + "/" + branch.replace('/', '_');
|
String path = prefix + "/" + branch.replace('/', '_');
|
||||||
|
worktreePaths.add(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Model an operator / {@code git worktree prune} removing the worktree before release. */
|
||||||
|
public FakeWorktrees markGone(String worktreePath) {
|
||||||
|
worktreePaths.remove(worktreePath);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,6 +96,11 @@ public final class FakeWorktrees implements Worktrees {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasUncommitted(String worktreePath) {
|
public boolean hasUncommitted(String worktreePath) {
|
||||||
|
// A path that does not exist (never added, or marked gone) is reported clean, mirroring
|
||||||
|
// GitWorktrees' already-gone guard — never an error, so teardown still completes.
|
||||||
|
if (!worktreePaths.contains(worktreePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return dirty;
|
return dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -227,6 +228,34 @@ class WorktreeSessionManagerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CB-576 review. A worktree that is already gone (operator cleanup, {@code git worktree prune},
|
||||||
|
* an earlier half-completed release) must not break teardown. {@code hasUncommitted} reports the
|
||||||
|
* missing path clean, so release still runs {@code notifyReleased} (the CB-516 fast-fail for a
|
||||||
|
* blocked {@code bridge_send} caller) and {@code launcher.stop} (so the pane is not orphaned),
|
||||||
|
* and falls through to the already-gone-tolerant {@code remove}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void releaseStillStopsPaneAndNotifiesWhenWorktreeIsGone() {
|
||||||
|
FakeHerdr herdr = new FakeHerdr();
|
||||||
|
FakeWorktrees worktrees = new FakeWorktrees().withRepoRoot("/repo").withPrefix("/wt");
|
||||||
|
SessionManager sessions = new SessionManager(workerService(herdr), worktrees);
|
||||||
|
MemberSession s = sessions.acquire("ltms-local", null, "/caller/proj", null,
|
||||||
|
new WorktreeRequest("cb-576g", null));
|
||||||
|
AtomicReference<String> releasedTerminal = new AtomicReference<>();
|
||||||
|
sessions.onRelease(releasedTerminal::set);
|
||||||
|
|
||||||
|
worktrees.markGone(s.worktree());
|
||||||
|
sessions.release(s.paneId());
|
||||||
|
|
||||||
|
assertEquals(s.terminalId(), releasedTerminal.get(),
|
||||||
|
"notifyReleased must still fire when the worktree is already gone (CB-516)");
|
||||||
|
assertTrue(herdr.called("pane.close"),
|
||||||
|
"the pane must still be stopped when the worktree is already gone");
|
||||||
|
assertEquals(1, worktrees.removeCalls().size(),
|
||||||
|
"release still calls the already-gone-tolerant remove");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void drainAllPreservesWorktreeOfIdleSession() {
|
void drainAllPreservesWorktreeOfIdleSession() {
|
||||||
FakeHerdr herdr = new FakeHerdr();
|
FakeHerdr herdr = new FakeHerdr();
|
||||||
|
|||||||
Reference in New Issue
Block a user