test: Investigated R102 symlink dedup: canonicalizePath already exists…
SF-Task: S01/T07
This commit is contained in:
parent
3915dfda3a
commit
e0fd2076d3
8 changed files with 68 additions and 22 deletions
|
|
@ -483,6 +483,11 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
if (!eventFilter || eventFilter.has("cost_update")) {
|
||||
output(costUpdate);
|
||||
}
|
||||
if (process.env.PI_TOKEN_TELEMETRY === "1") {
|
||||
rawStderrWrite(
|
||||
`[PI_TOKEN] input=${stats.tokens.input} output=${stats.tokens.output} cache_read=${stats.tokens.cacheRead} cache_write=${stats.tokens.cacheWrite} cost=$${stats.cost.toFixed(4)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// execution_complete on agent_end
|
||||
|
|
|
|||
|
|
@ -262,26 +262,36 @@ describe("git-service", async () => {
|
|||
|
||||
assert.deepStrictEqual(
|
||||
RUNTIME_EXCLUSION_PATHS.length,
|
||||
15,
|
||||
"exactly 15 runtime exclusion paths",
|
||||
25,
|
||||
"exactly 25 runtime exclusion paths",
|
||||
);
|
||||
|
||||
const expectedPaths = [
|
||||
".sf/activity/",
|
||||
".sf/audit/",
|
||||
".sf/exec/",
|
||||
".sf/forensics/",
|
||||
".sf/journal/",
|
||||
".sf/model-benchmarks/",
|
||||
".sf/parallel/",
|
||||
".sf/reports/",
|
||||
".sf/runtime/",
|
||||
".sf/worktrees/",
|
||||
".sf/parallel/",
|
||||
".sf/auto.lock",
|
||||
".sf/metrics.json",
|
||||
".sf/completed-units*.json",
|
||||
".sf/state-manifest.json",
|
||||
".sf/STATE.md",
|
||||
".sf/sf.db*",
|
||||
".sf/journal/",
|
||||
".sf/doctor-history.jsonl",
|
||||
".sf/event-log.jsonl",
|
||||
".sf/notifications.jsonl",
|
||||
".sf/routing-history.json",
|
||||
".sf/self-feedback.jsonl",
|
||||
".sf/repo-meta.json",
|
||||
".sf/DISCUSSION-MANIFEST.json",
|
||||
".sf/milestones/**/*-CONTINUE.md",
|
||||
".sf/milestones/**/continue.md",
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(
|
||||
|
|
@ -1602,25 +1612,40 @@ describe("git-service", async () => {
|
|||
rmSync(repo, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// ─── ensureGitignore: always adds .sf to gitignore ──────────────────
|
||||
// ─── ensureGitignore: excludes symlinked .sf per-clone ───────────────
|
||||
|
||||
test("ensureGitignore: adds .sf entry", async () => {
|
||||
test("ensureGitignore: adds symlinked .sf to git/info/exclude", async () => {
|
||||
const { ensureGitignore } = await import("../../gitignore.ts");
|
||||
const repo = mkdtempSync(join(tmpdir(), "sf-gitignore-external-state-"));
|
||||
const externalState = mkdtempSync(join(tmpdir(), "sf-gitignore-state-"));
|
||||
|
||||
// Should add .sf to gitignore (external state dir is a symlink)
|
||||
runGit(repo, ["init", "-b", "main"]);
|
||||
symlinkSync(externalState, join(repo, ".sf"));
|
||||
|
||||
// Symlinked external state must be ignored per-clone so sf does not dirty
|
||||
// a user-managed .gitignore while still hiding the opaque .sf symlink.
|
||||
const modified = ensureGitignore(repo);
|
||||
assert.ok(modified, "ensureGitignore: gitignore was modified");
|
||||
assert.ok(modified, "ensureGitignore: ignore files were modified");
|
||||
|
||||
const { readFileSync } = await import("node:fs");
|
||||
const content = readFileSync(join(repo, ".gitignore"), "utf-8");
|
||||
const lines = content
|
||||
const exclude = readFileSync(join(repo, ".git", "info", "exclude"), "utf-8");
|
||||
const excludeLines = exclude
|
||||
.split("\n")
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l && !l.startsWith("#"));
|
||||
assert.ok(
|
||||
lines.includes(".sf"),
|
||||
"ensureGitignore: .gitignore contains .sf",
|
||||
excludeLines.includes(".sf"),
|
||||
"ensureGitignore: git/info/exclude contains .sf",
|
||||
);
|
||||
|
||||
const gitignore = readFileSync(join(repo, ".gitignore"), "utf-8");
|
||||
const gitignoreLines = gitignore
|
||||
.split("\n")
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l && !l.startsWith("#"));
|
||||
assert.ok(
|
||||
!gitignoreLines.includes(".sf"),
|
||||
"ensureGitignore: committed .gitignore does not contain .sf",
|
||||
);
|
||||
|
||||
// Idempotent — calling again doesn't add duplicates
|
||||
|
|
@ -1628,6 +1653,7 @@ describe("git-service", async () => {
|
|||
assert.ok(!modified2, "ensureGitignore: second call is idempotent");
|
||||
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
rmSync(externalState, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// ─── nativeAddAllWithExclusions: symlinked .sf fallback ───────────────
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe } from 'vitest';
|
||||
import { describe, test } from "vitest";
|
||||
import { createAutoWorktree } from "../auto-worktree.ts";
|
||||
import {
|
||||
closeDatabase,
|
||||
|
|
@ -56,7 +56,8 @@ function createTempRepo(): string {
|
|||
return dir;
|
||||
}
|
||||
|
||||
describe("worktree-db-integration", async () => {
|
||||
describe("worktree-db-integration", () => {
|
||||
test("copies and reconciles worktree database state", async () => {
|
||||
const savedCwd = process.cwd();
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
|
|
@ -245,4 +246,5 @@ describe("worktree-db-integration", async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe } from 'vitest';
|
||||
import { describe, test } from "vitest";
|
||||
import { syncProjectRootToWorktree } from "../auto-worktree.ts";
|
||||
|
||||
function createBase(name: string): string {
|
||||
|
|
@ -37,7 +37,8 @@ function cleanup(base: string): void {
|
|||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
describe("worktree-db-respawn-truncation (#2815)", async () => {
|
||||
describe("worktree-db-respawn-truncation (#2815)", () => {
|
||||
test("preserves populated worktree databases while syncing artifacts", async () => {
|
||||
// ─── 1. Non-empty worktree sf.db preserved after sync ───────────────
|
||||
console.log(
|
||||
"\n=== 1. non-empty worktree sf.db preserved after sync (#2815) ===",
|
||||
|
|
@ -257,4 +258,5 @@ describe("worktree-db-respawn-truncation (#2815)", async () => {
|
|||
cleanup(wtBase);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe } from 'vitest';
|
||||
import { describe, test } from "vitest";
|
||||
import {
|
||||
formatWorktreeStatusLine,
|
||||
getWorktreeHealth,
|
||||
|
|
@ -43,7 +43,8 @@ function createBaseRepo(): string {
|
|||
return dir;
|
||||
}
|
||||
|
||||
describe("worktree-health", async () => {
|
||||
describe("worktree-health", () => {
|
||||
test("classifies worktree health states and formats status lines", async () => {
|
||||
// Skip all tests on Windows — git worktree path resolution issues
|
||||
if (process.platform === "win32") {
|
||||
console.log("(all worktree-health tests skipped on Windows)");
|
||||
|
|
@ -228,4 +229,5 @@ describe("worktree-health", async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe } from 'vitest';
|
||||
import { describe, test } from "vitest";
|
||||
import { _clearSfRootCache } from "../paths.ts";
|
||||
import { deriveState } from "../state.ts";
|
||||
import {
|
||||
|
|
@ -87,7 +87,8 @@ writeFileSync(
|
|||
run("git add .", base);
|
||||
run('git commit -m "chore: init"', base);
|
||||
|
||||
describe("worktree-integration", async () => {
|
||||
describe("worktree-integration", () => {
|
||||
test("runs the full worktree lifecycle with namespaced slice branches", async () => {
|
||||
// Isolate from user's global preferences (which may have git.main_branch set).
|
||||
// Reset caches so getService() creates a fresh instance with empty preferences.
|
||||
const originalHome = process.env.HOME;
|
||||
|
|
@ -310,4 +311,5 @@ describe("worktree-integration", async () => {
|
|||
_clearSfRootCache();
|
||||
_resetServiceCache();
|
||||
rmSync(fakeHome, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe } from 'vitest';
|
||||
import { describe, test } from "vitest";
|
||||
import {
|
||||
createWorktree,
|
||||
listWorktrees,
|
||||
|
|
@ -69,7 +69,8 @@ writeFileSync(join(base, "README.md"), "# Test\n", "utf-8");
|
|||
run("git add .", base);
|
||||
run('git commit -m "init"', base);
|
||||
|
||||
describe("worktree-symlink-removal", async () => {
|
||||
describe("worktree-symlink-removal", () => {
|
||||
test("removes the git-registered worktree when .sf is a symlink", async () => {
|
||||
console.log("\n=== #1852: removeWorktree with symlinked .sf/ ===");
|
||||
|
||||
// Create a worktree — git will resolve the symlink and register
|
||||
|
|
@ -146,4 +147,5 @@ describe("worktree-symlink-removal", async () => {
|
|||
// Cleanup
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
rmSync(externalState, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ const manageRoute = await import(
|
|||
const gitRoute = await import("../../../web/app/api/git/route.ts");
|
||||
const { AuthStorage } = await import("@singularity-forge/pi-coding-agent");
|
||||
|
||||
afterEach(async () => {
|
||||
await bridge.resetBridgeServiceForTests();
|
||||
onboarding.resetOnboardingServiceForTests();
|
||||
});
|
||||
|
||||
class FakeRpcChild extends EventEmitter {
|
||||
stdin = new PassThrough();
|
||||
stdout = new PassThrough();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue