Skip to content
13 changes: 2 additions & 11 deletions cli/commands/skills/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { withCwd } from "#veryfront/testing/cwd.ts";
import "#veryfront/schemas/_test-setup.ts";
import { assertEquals } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
Expand Down Expand Up @@ -25,16 +26,6 @@ async function withTempSkill(
}
}

async function withTempCwd(dir: string, fn: () => Promise<void>): Promise<void> {
const previous = Deno.cwd();
try {
Deno.chdir(dir);
await fn();
} finally {
Deno.chdir(previous);
}
}

describe("Skills Validate", () => {
it("accepts a project skill with SKILL.md frontmatter", async () => {
await withTempSkill({
Expand Down Expand Up @@ -66,7 +57,7 @@ description: Review code changes.
Review the submitted changes.
`,
}, async (dir) => {
await withTempCwd(dir, async () => {
await withCwd(dir, async () => {
const issues = await validateSkillDirectory(".");
assertEquals(issues, []);
});
Expand Down
46 changes: 46 additions & 0 deletions src/testing/cwd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Serialized access to the process working directory in tests.
*
* `Deno.chdir` mutates state shared by every test in the process, and the CLI
* suite has several files that need it -- each one resolving a relative path
* the way the command under test would. Run two of them at once and the second
* `chdir` lands while the first is still awaiting, so the first resolves its
* path against the wrong directory. That is a race by construction: it depends
* on which files a shard happens to group, so it fails rarely, somewhere
* unrelated to whatever change is being tested.
*
* Every caller queues here instead, so at most one has the working directory at
* a time and each is restored before the next begins.
*
* @module testing/cwd
*/

/** Tail of the queue. Each caller awaits the previous one before it chdirs. */
let queue: Promise<void> = Promise.resolve();

Comment thread
kojiwakayama marked this conversation as resolved.
Resolved
/**
* Run `fn` with the process working directory set to `dir`.
*
* Waits for any other caller to finish first, and restores the previous
* directory afterwards even if `fn` throws.
*
* @param dir directory to enter
* @param fn work to run inside it
* @returns whatever `fn` returns
*/
export function withCwd<T>(dir: string, fn: () => Promise<T> | T): Promise<T> {
const run = queue.then(async () => {
const previous = Deno.cwd();
try {
Deno.chdir(dir);
return await fn();
} finally {
Deno.chdir(previous);
}
});

// The queue advances whether or not this caller succeeded, so one failure
// does not strand everyone behind it.
queue = run.then(() => {}, () => {});
return run;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
Loading