mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 18:22:38 +00:00
test(workspaces): make Git isolation deterministic
This commit is contained in:
@@ -296,11 +296,10 @@ test("replayed common-ref observation survives runtime service reconstruction",
|
||||
await reconstructed.destroy(workspaceId);
|
||||
}, 20_000);
|
||||
|
||||
test("selected sibling worktrees share common-ref fan-out without touching an unrelated workspace", async () => {
|
||||
test("selected sibling worktrees retain common-ref fan-out across lifecycle transitions", async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), "paseo-runtime-git-fanout-"));
|
||||
cleanupRoots.push(root);
|
||||
const source = await createRepository(path.join(root, "source"));
|
||||
const unrelated = await createRepository(path.join(root, "unrelated"));
|
||||
const runtimeIds = new Map<string, string>();
|
||||
const service = createWorkspaceRuntimeService({
|
||||
paseoHome: path.join(root, "paseo-home"),
|
||||
@@ -325,13 +324,6 @@ test("selected sibling worktrees share common-ref fan-out without touching an un
|
||||
placement: { kind: "branch", branchName, baseRef: "main", worktreeSlug: branchName },
|
||||
});
|
||||
}
|
||||
await service.create({
|
||||
workspaceId: "unrelated",
|
||||
runtimeId: "local",
|
||||
project: { id: "unrelated-project", source: { kind: "host-directory", path: unrelated } },
|
||||
placement: { kind: "existing" },
|
||||
});
|
||||
|
||||
let resolveFirst!: () => void;
|
||||
let resolveSibling!: () => void;
|
||||
const firstChanged = new Promise<void>((resolve) => {
|
||||
@@ -349,10 +341,8 @@ test("selected sibling worktrees share common-ref fan-out without touching an un
|
||||
const siblingChangedAfterOwnerDestroy = new Promise<void>((resolve) => {
|
||||
resolveSiblingAfterOwnerDestroy = resolve;
|
||||
});
|
||||
let unrelatedChanges = 0;
|
||||
const siblingA = await service.bind("sibling-a");
|
||||
const siblingB = await service.bind("sibling-b");
|
||||
const unrelatedGit = await service.bind("unrelated");
|
||||
const subscriptions = [
|
||||
await observeWorkspaceGit(siblingA, () => {
|
||||
if (phase === "first-change") resolveFirst();
|
||||
@@ -362,32 +352,22 @@ test("selected sibling worktrees share common-ref fan-out without touching an un
|
||||
if (phase === "after-pause-change") resolveSiblingAfterOwnerPause();
|
||||
if (phase === "after-destroy-change") resolveSiblingAfterOwnerDestroy();
|
||||
}),
|
||||
await observeWorkspaceGit(unrelatedGit, () => {
|
||||
unrelatedChanges += 1;
|
||||
}),
|
||||
];
|
||||
await createBranchThroughRuntime(service, "sibling-a", "shared-ref-change");
|
||||
await Promise.all([
|
||||
eventWithin(firstChanged, "first sibling ref update"),
|
||||
eventWithin(siblingChanged, "second sibling ref update"),
|
||||
]);
|
||||
expect(unrelatedChanges).toBe(0);
|
||||
|
||||
await service.pause("sibling-a");
|
||||
phase = "after-pause-change";
|
||||
await createBranchThroughRuntime(service, "sibling-b", "shared-ref-after-owner-pause");
|
||||
await eventWithin(siblingChangedAfterOwnerPause, "sibling ref update after owner pause");
|
||||
expect(unrelatedChanges).toBe(0);
|
||||
|
||||
await service.destroy("sibling-a");
|
||||
phase = "after-destroy-change";
|
||||
await createBranchThroughRuntime(service, "sibling-b", "shared-ref-after-owner-destroy");
|
||||
await eventWithin(siblingChangedAfterOwnerDestroy, "sibling ref update after owner destroy");
|
||||
expect(unrelatedChanges).toBe(0);
|
||||
|
||||
await Promise.all(subscriptions.map((subscription) => subscription.unsubscribe()));
|
||||
await Promise.all([service.destroy("sibling-a"), service.destroy("sibling-b")]);
|
||||
await service.destroy("unrelated");
|
||||
}, 20_000);
|
||||
|
||||
test("sibling Git observations stay workspace-bound across pause, resume, and unsubscribe", async () => {
|
||||
|
||||
+31
-8
@@ -1,5 +1,5 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type FileObserverCallback,
|
||||
type FileObserverOptions,
|
||||
} from "../../file-observer/index.js";
|
||||
import { getGitCommonDir } from "../../../utils/worktree.js";
|
||||
import { createHostGitObservationOwner } from "./host-git-observation.js";
|
||||
|
||||
describe.runIf(process.platform !== "win32")("host Git observation on POSIX", () => {
|
||||
@@ -22,17 +23,32 @@ describe.runIf(process.platform !== "win32")("host Git observation on POSIX", ()
|
||||
git(source, ["branch", "sibling"]);
|
||||
git(source, ["worktree", "add", sibling, "sibling"]);
|
||||
const unrelated = await repository(path.join(root, "unrelated"));
|
||||
const owner = createHostGitObservationOwner();
|
||||
const observer = new InstrumentedFileObserver();
|
||||
const owner = createHostGitObservationOwner(observer);
|
||||
let sourceChanges = 0;
|
||||
let siblingChanges = 0;
|
||||
let unrelatedChanges = 0;
|
||||
|
||||
const [sourceSubscription, siblingSubscription] = await Promise.all([
|
||||
owner.observe(source, () => undefined),
|
||||
owner.observe(sibling, () => undefined),
|
||||
owner.observe(source, () => {
|
||||
sourceChanges += 1;
|
||||
}),
|
||||
owner.observe(sibling, () => {
|
||||
siblingChanges += 1;
|
||||
}),
|
||||
]);
|
||||
expect(owner.getDiagnostics().activeObservationCount).toBe(1);
|
||||
|
||||
const unrelatedSubscription = await owner.observe(unrelated, () => undefined);
|
||||
const unrelatedSubscription = await owner.observe(unrelated, () => {
|
||||
unrelatedChanges += 1;
|
||||
});
|
||||
expect(owner.getDiagnostics().activeObservationCount).toBe(2);
|
||||
|
||||
observer.emit(await realpath(await getGitCommonDir(source)));
|
||||
expect(sourceChanges).toBe(1);
|
||||
expect(siblingChanges).toBe(1);
|
||||
expect(unrelatedChanges).toBe(0);
|
||||
|
||||
await sourceSubscription.unsubscribe();
|
||||
expect(owner.getDiagnostics().activeObservationCount).toBe(2);
|
||||
await siblingSubscription.unsubscribe();
|
||||
@@ -85,7 +101,7 @@ describe.runIf(process.platform !== "win32")("host Git observation on POSIX", ()
|
||||
|
||||
class InstrumentedFileObserver implements FileObserver {
|
||||
readonly observer = createFileObserver();
|
||||
readonly callbacks = new Set<FileObserverCallback>();
|
||||
readonly callbacks = new Map<FileObserverCallback, string>();
|
||||
subscribeCount = 0;
|
||||
|
||||
async subscribe(
|
||||
@@ -94,7 +110,7 @@ class InstrumentedFileObserver implements FileObserver {
|
||||
options?: FileObserverOptions,
|
||||
) {
|
||||
this.subscribeCount += 1;
|
||||
this.callbacks.add(callback);
|
||||
this.callbacks.set(callback, directory);
|
||||
const subscription = await this.observer.subscribe(directory, callback, options);
|
||||
let active = true;
|
||||
return {
|
||||
@@ -109,7 +125,14 @@ class InstrumentedFileObserver implements FileObserver {
|
||||
}
|
||||
|
||||
failActive(error: Error): void {
|
||||
for (const callback of this.callbacks) callback(error, []);
|
||||
for (const callback of this.callbacks.keys()) callback(error, []);
|
||||
}
|
||||
|
||||
emit(directory: string): void {
|
||||
for (const [callback, observedDirectory] of this.callbacks) {
|
||||
if (observedDirectory === directory)
|
||||
callback(null, [{ path: "refs/heads/main", type: "update" }]);
|
||||
}
|
||||
}
|
||||
|
||||
getDiagnostics() {
|
||||
|
||||
Reference in New Issue
Block a user