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
63 changes: 63 additions & 0 deletions tests/helpers/path-arrival.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { watch } from "node:fs";
import { access } from "node:fs/promises";
import { dirname } from "node:path";

export interface PathArrivalWatch {
readonly wait: Promise<void>;
close(): void;
}

/** Arms a filesystem event before child-process work, avoiding a polling race with process startup. */
export function watchForPathArrival(path: string): PathArrivalWatch {
let watcher: ReturnType<typeof watch> | undefined;
let fallbackPoll: NodeJS.Timeout | undefined;
let settled = false;
let resolveWait!: () => void;
let rejectWait!: (error: unknown) => void;
const wait = new Promise<void>((resolve, reject) => {
resolveWait = resolve;
rejectWait = reject;
});
/** Completes the watch exactly once and releases every observation resource. */
const settle = (error?: unknown) => {
if (settled) return;
settled = true;
watcher?.close();
if (fallbackPoll !== undefined) clearInterval(fallbackPoll);
if (error === undefined) resolveWait();
else rejectWait(error);
};
/** Checks the exact path after a directory event or fallback polling tick. */
const verify = () => {
void access(path).then(
() => settle(),
(error: unknown) => {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") settle(error);
}
);
};
try {
watcher = watch(dirname(path), { persistent: false }, verify);
watcher.on("error", settle);
} catch (error) {
settle(error);
}
if (!settled) {
// fs.watch can drop directory events under load, so keep verifying the exact
// path without imposing a shorter deadline than the owning test.
fallbackPoll = setInterval(verify, 50);
fallbackPoll.unref();
verify();
}
return {
wait,
close: () => {
watcher?.close();
if (fallbackPoll !== undefined) clearInterval(fallbackPoll);
if (!settled) {
settled = true;
resolveWait();
}
}
};
}
60 changes: 1 addition & 59 deletions tests/mcp-wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import type { Transport, TransportSendOptions } from "@modelcontextprotocol/sdk/shared/transport.js";
import { UriTemplate } from "@modelcontextprotocol/sdk/shared/uriTemplate.js";
import { randomUUID } from "node:crypto";
import { watch } from "node:fs";
import { setTimeout as delay } from "node:timers/promises";
import {
CallToolResultSchema,
Expand All @@ -21,6 +20,7 @@ import { dirname, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { describe, expect, it, vi } from "vitest";
import { expectExactlyOneNotification } from "./helpers/notifications.js";
import { type PathArrivalWatch, watchForPathArrival } from "./helpers/path-arrival.js";
import { validateConfig } from "../src/config/validate-config.js";
import type { MiftahConfig } from "../src/config/types.js";
import type { AuditScope } from "../src/audit/audit-trail.js";
Expand Down Expand Up @@ -50,64 +50,6 @@ async function fixtureLifecycleState(initializedPath: string, toolListStartedPat
return { initialized, toolListStarted };
}

interface PathArrivalWatch {
readonly wait: Promise<void>;
close(): void;
}

/** Arms a filesystem event before a child-process request, avoiding a polling race with child startup. */
function watchForPathArrival(path: string): PathArrivalWatch {
let watcher: ReturnType<typeof watch> | undefined;
let fallbackPoll: NodeJS.Timeout | undefined;
let settled = false;
let resolveWait!: () => void;
let rejectWait!: (error: unknown) => void;
const wait = new Promise<void>((resolve, reject) => {
resolveWait = resolve;
rejectWait = reject;
});
const settle = (error?: unknown) => {
if (settled) return;
settled = true;
watcher?.close();
if (fallbackPoll !== undefined) clearInterval(fallbackPoll);
if (error === undefined) resolveWait();
else rejectWait(error);
};
const verify = () => {
void access(path).then(
() => settle(),
(error: unknown) => {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") settle(error);
}
);
};
try {
watcher = watch(dirname(path), { persistent: false }, verify);
watcher.on("error", settle);
} catch (error) {
settle(error);
}
if (!settled) {
// fs.watch can drop directory events under load, so keep verifying the exact
// path without imposing a shorter deadline than the test itself.
fallbackPoll = setInterval(verify, 50);
fallbackPoll.unref();
verify();
}
return {
wait,
close: () => {
watcher?.close();
if (fallbackPoll !== undefined) clearInterval(fallbackPoll);
if (!settled) {
settled = true;
resolveWait();
}
}
};
}

function registeredTool(originalName: string): RegisteredTool {
return {
exposedName: "trusted__shared_tool",
Expand Down
25 changes: 13 additions & 12 deletions tests/package-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,27 +876,28 @@ describe("packed artifact contract", () => {
});

it("does not pass test-only V8 coverage collection to npm subprocesses", async () => {
const coverageDirectory = await mkdtemp(join(tmpdir(), "miftah-npm-coverage-"));
const previousCoverageDirectory = process.env.NODE_V8_COVERAGE;
process.env.NODE_V8_COVERAGE = coverageDirectory;
process.env.NODE_V8_COVERAGE = join(tmpdir(), "miftah-parent-coverage");
const child = new TermIgnoringNpmProcess();
let childCoverageDirectory: string | undefined;
const spawnInspectingChild: NpmSpawner = (_command, _args, options) => {
childCoverageDirectory = options.env.NODE_V8_COVERAGE;
setImmediate(() => child.emit("close", 0, null));
return child;
};

try {
const child = await runNpm([
"exec",
"--",
process.execPath,
"--eval",
"process.stdout.write(process.env.NODE_V8_COVERAGE ?? '')"
]);

expect(child.stdout).toBe("");
await expect(
runNpm(["diagnostic"], repositoryRoot, npmCommandTimeoutMs, spawnInspectingChild)
).resolves.toMatchObject({ status: 0 });

expect(childCoverageDirectory).toBe("");
} finally {
if (previousCoverageDirectory === undefined) {
delete process.env.NODE_V8_COVERAGE;
} else {
process.env.NODE_V8_COVERAGE = previousCoverageDirectory;
}
await rm(coverageDirectory, { recursive: true, force: true });
}
});

Expand Down
4 changes: 2 additions & 2 deletions tests/release-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@ describe("continuous integration workflow contract", () => {
expect(lockedPackages["node_modules/postcss"]).toMatchObject({ version: "8.5.23", dev: true });
});

it("keeps serial process-backed tests in one isolated fork", () => {
it("keeps process-backed files serial while replacing their isolated fork", () => {
const config = readRepositoryFile("vitest.config.ts");

expect(config).toContain("fileParallelism: false");
expect(config).toMatch(
/poolOptions:\s*\{\s*forks:\s*\{\s*singleFork:\s*true,\s*isolate:\s*true\s*\}\s*\}/u
/poolOptions:\s*\{\s*forks:\s*\{\s*singleFork:\s*false,\s*isolate:\s*true\s*\}\s*\}/u
);
});
});
Expand Down
10 changes: 9 additions & 1 deletion tests/setup-profile-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AuditTrail } from "../src/audit/audit-trail.js";
import { runProfileReadiness } from "../src/setup/profile-readiness.js";
import { watchForPathArrival } from "./helpers/path-arrival.js";

const temporaryDirectories: string[] = [];

Expand Down Expand Up @@ -385,13 +386,19 @@ export default {
await writeFile(fixture.configPath, JSON.stringify(config));

const controller = new AbortController();
const pluginStarted = watchForPathArrival(startedPath);
const pending = fixture.run({ profile: "google-work", signal: controller.signal });
const settlement = pending.then(
() => ({ kind: "fulfilled" as const }),
(error: unknown) => ({ kind: "rejected" as const, error })
);
try {
await expect.poll(async () => access(startedPath).then(() => true, () => false)).toBe(true);
await Promise.race([
pluginStarted.wait,
settlement.then((outcome) => {
throw new Error(`Secret-plugin readiness settled before its started marker: ${outcome.kind}`);
})
]);
controller.abort("test caller disconnect during secret resolution");
const outcome = await Promise.race([
settlement,
Expand All @@ -403,6 +410,7 @@ export default {
expect(outcome.error).toMatchObject({ code: "UPSTREAM_CALL_FAILED" });
}
} finally {
pluginStarted.close();
await settlement;
}
});
Expand Down
5 changes: 3 additions & 2 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ export default defineConfig({
clearMocks: true,
// Real upstream fixtures have one-second startup limits; run files serially to prevent contention.
fileParallelism: false,
// Reuse that serial worker instead of cold-forking once per file; module isolation remains enabled.
poolOptions: { forks: { singleFork: true, isolate: true } },
// Replace the fork between serial files so process-backed tests cannot retain
// handles or lifecycle state from an earlier file; module isolation remains enabled.
poolOptions: { forks: { singleFork: false, isolate: true } },
coverage: {
provider: "v8",
include: [
Expand Down