Squashed 'third_party/pi/' content from commit d981de122

git-subtree-dir: third_party/pi
git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
2026-09-14 10:40:08 -04:00
commit 67b361f0e9
1669 changed files with 397627 additions and 0 deletions

View File

@@ -0,0 +1,384 @@
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
import {
encodeCbor,
encodeFrame,
encodeServerMessage,
PROTOCOL_VERSION,
ProtocolValidationError,
} from "@earendil-works/pi-protocol";
import { describe, expect, test, vi } from "vitest";
import {
type ByteTransportFactory,
Client,
ClientDisposedError,
createClientServiceTransport,
DisconnectedError,
} from "../src/index.ts";
import { MemoryByteServer } from "./support.ts";
const serverId = "00000000-0000-4000-8000-000000000001";
const serverTarget = { serverId } as const;
async function connectClient(server: MemoryByteServer, expectedServerId = serverId): Promise<Client> {
return Client.connect({ serverId: expectedServerId, transportFactory: (handlers) => server.connect(handlers) });
}
async function attachClient(client: Client, server: MemoryByteServer, sessionId: string): Promise<void> {
const expectedMessages = server.messages.length + 1;
const attaching = client.request(serverTarget, {
serviceId: "pi.session-management",
member: "attach",
args: [sessionId],
});
await server.waitForMessages(expectedMessages);
const request = server.messages.at(-1);
if (request?.type !== "request") throw new Error("Missing attach request");
server.send({
type: "attachment",
attachment: { serverId, sessionId, attachmentId: `attachment-${sessionId}` },
});
server.send({ type: "response", id: request.id, ok: true, result: null });
await attaching;
}
test("requires a canonical UUIDv4 server identity", () => {
expect(() => new Client({ serverId: "invalid-server", transportFactory: () => Promise.reject() })).toThrow(
/serverId/,
);
});
describe("Client service operations", () => {
test("connects only to the expected logical server", async () => {
const matching = new MemoryByteServer();
const client = await connectClient(matching);
expect(client.hello).toMatchObject({ serverId });
await client.dispose();
const wrong = new MemoryByteServer("00000000-0000-4000-8000-000000000002");
await expect(connectClient(wrong)).rejects.toBeInstanceOf(ProtocolValidationError);
});
test("updates attachment state from out-of-band server routing", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const changes: Array<string | undefined> = [];
client.onAttachmentChange((attachment) => changes.push(attachment?.sessionId));
await attachClient(client, server, "session-1");
expect(client.attachment).toMatchObject({ sessionId: "session-1", attachmentId: "attachment-session-1" });
expect(server.messages[1]).toMatchObject({
type: "request",
target: serverTarget,
call: { serviceId: "pi.session-management", member: "attach", args: ["session-1"] },
});
server.send({ type: "attachment", attachment: null });
expect(client.attachment).toBeUndefined();
expect(changes).toEqual(["session-1", undefined]);
await client.dispose();
});
test("buffers service updates until the subscription snapshot arrives", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
await attachClient(client, server, "session-1");
const target = client.attachment!;
const transport = createClientServiceTransport(client, () => client.attachment);
const updates: Array<{ readonly type: string; readonly ops?: readonly unknown[] }> = [];
const opening = transport.subscribe(
"pi.models",
"singleton",
(update) => {
updates.push(update);
},
BACKGROUND_CONTEXT,
);
await server.waitForMessages(3);
expect(server.messages[2]).toMatchObject({
type: "request",
target,
call: {
serviceId: "$chord.service",
member: "subscribe",
args: ["service-1", "pi.models", "singleton"],
},
});
server.send({
type: "service_update",
subscriptionId: "service-1",
update: { type: "state", member: "state", sequence: 1, ops: [["s", ["revision"], 1]] },
});
await Promise.resolve();
expect(updates).toEqual([]);
server.send({
type: "response",
id: "request-2",
ok: true,
result: {
serviceId: "pi.models",
mode: "singleton",
instances: [{ members: [{ name: "state", kind: "state", sequence: 0, ops: [["r", { revision: 0 }]] }] }],
},
});
const subscription = await opening;
expect(updates).toEqual([]);
server.send({
type: "service_update",
subscriptionId: "closed-subscription",
update: { type: "state", member: "state", sequence: 99, ops: [["s", 99, 99]] },
});
await Promise.resolve();
expect(client.connected).toBe(true);
expect(subscription.snapshot.instances[0]?.members).toEqual([
{ name: "state", kind: "state", sequence: 0, ops: [["r", { revision: 0 }]] },
]);
subscription.activate();
await vi.waitFor(() => expect(updates.map(({ type }) => type)).toEqual(["state"]));
server.send({
type: "service_update",
subscriptionId: "service-1",
update: { type: "state", member: "state", sequence: 2, ops: [["s", ["revision"], 2]] },
});
server.send({
type: "service_update",
subscriptionId: "service-1",
update: {
type: "state",
member: "state",
sequence: 3,
ops: [
["#", 0, ["revision"]],
["s", 0, 3],
],
},
});
await vi.waitFor(() => expect(updates).toHaveLength(3));
expect(updates[2]?.ops).toEqual([["s", ["revision"], 3]]);
const disposing = subscription.close(BACKGROUND_CONTEXT);
await server.waitForMessages(4);
expect(server.messages[3]).toMatchObject({
call: { serviceId: "$chord.service", member: "unsubscribe", args: ["service-1"] },
});
server.send({ type: "response", id: "request-3", ok: true });
await disposing;
await client.dispose();
});
test("correlates out-of-order generic service responses", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const first = client.request(serverTarget, { serviceId: "test", member: "first", args: [] });
const second = client.request(serverTarget, { serviceId: "test", member: "second", args: [] });
await server.waitForMessages(3);
server.send({ type: "response", id: "request-2", ok: true, result: "second" });
server.send({ type: "response", id: "request-1", ok: true, result: "first" });
await expect(Promise.all([first, second])).resolves.toEqual(["first", "second"]);
await client.dispose();
});
test("exposes bounded server errors", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const pending = client.request(serverTarget, { serviceId: "test", member: "missing", args: [] });
await server.waitForMessages(2);
server.send({
type: "response",
id: "request-1",
ok: false,
error: { code: "session_not_found", message: "Unknown session" },
});
await expect(pending).rejects.toMatchObject({ code: "session_not_found" });
await client.dispose();
});
test("does not send a pre-aborted untyped RPC request", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const controller = new AbortController();
const reason = new Error("already cancelled");
controller.abort(reason);
await expect(
client.request(serverTarget, { serviceId: "test", member: "noop", args: [] }, controller.signal),
).rejects.toBe(reason);
expect(server.messages).toHaveLength(1);
await client.dispose();
});
test("cancels one untyped RPC request without disconnecting", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const controller = new AbortController();
const reason = new Error("stop this request");
const pending = client.request(
serverTarget,
{ serviceId: "test", member: "mutate", args: [{ value: 42 }] },
controller.signal,
);
await server.waitForMessages(2);
expect(server.messages[1]).toMatchObject({
type: "request",
id: "request-1",
target: serverTarget,
call: { serviceId: "test", member: "mutate", args: [{ value: 42 }] },
});
controller.abort(reason);
await expect(pending).rejects.toBe(reason);
await server.waitForMessages(3);
expect(server.messages[2]).toEqual({ type: "cancel", id: "request-1", target: serverTarget });
server.send({
type: "response",
id: "request-1",
ok: false,
error: { code: "cancelled", message: "cancelled" },
});
expect(client.connected).toBe(true);
await client.dispose();
});
test("rejects pending requests after disconnect or disposal", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const pending = client.request(serverTarget, { serviceId: "test", member: "pending", args: [] });
server.disconnect();
await expect(pending).rejects.toBeInstanceOf(DisconnectedError);
await client.dispose();
await expect(
client.request(serverTarget, { serviceId: "test", member: "disposed", args: [] }),
).rejects.toBeInstanceOf(ClientDisposedError);
});
});
describe("Client connection lifecycle", () => {
test("rejects server data delivered before the client hello is sent", async () => {
let closeCount = 0;
let sendCount = 0;
const client = new Client({
serverId,
transportFactory: (handlers) => {
handlers.onData(encodeServerMessage({ type: "hello", version: PROTOCOL_VERSION, serverId }));
return {
async send() {
sendCount += 1;
},
close() {
closeCount += 1;
},
};
},
});
await expect(client.connect()).rejects.toMatchObject({
name: "ProtocolValidationError",
message: "Received server data before the client hello was sent",
});
expect(client.connectionState).toBe("disconnected");
expect(sendCount).toBe(0);
expect(closeCount).toBe(1);
});
test("rejects typed handshake errors and closes the transport", async () => {
let handlers: Parameters<ByteTransportFactory>[0];
let closeCount = 0;
const client = new Client({
serverId,
transportFactory: (createdHandlers) => {
handlers = createdHandlers;
return {
async send() {
handlers.onData(
encodeServerMessage({
type: "hello_error",
error: { code: "version", message: "Unsupported protocol version" },
}),
);
},
close() {
closeCount += 1;
},
};
},
});
await expect(client.connect()).rejects.toMatchObject({
name: "ServerError",
code: "version",
message: "Unsupported protocol version",
});
expect(client.connectionState).toBe("disconnected");
expect(closeCount).toBe(1);
});
test("rejects pending requests and reconnects through a fresh transport", async () => {
const first = new MemoryByteServer();
const second = new MemoryByteServer();
let connection = 0;
const transportFactory: ByteTransportFactory = (handlers) =>
(connection++ === 0 ? first : second).connect(handlers);
const client = new Client({ serverId, transportFactory });
const states: string[] = [];
client.onConnectionStateChange(({ state }) => states.push(state));
await client.connect();
await attachClient(client, first, "session-1");
const target = client.attachment;
if (target === undefined) throw new Error("Missing attachment");
const pending = client.request(target, { serviceId: "test.session", member: "run", args: [] });
await first.waitForMessages(3);
expect(first.messages[2]).toMatchObject({ call: { serviceId: "test.session", member: "run" } });
first.disconnect();
await expect(pending).rejects.toBeInstanceOf(DisconnectedError);
await expect(client.reconnect()).resolves.toMatchObject({ serverId });
expect(connection).toBe(2);
expect(client.connected).toBe(true);
expect(second.messages).toHaveLength(1);
expect(states).toEqual(["connecting", "connected", "disconnected", "connecting", "connected"]);
await client.dispose();
});
test("reports transport failures without leaving requests pending", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
const pending = client.request(serverTarget, { serviceId: "test", member: "pending", args: [] });
await server.waitForMessages(2);
server.error(new Error("read failed"));
await expect(pending).rejects.toMatchObject({
name: "DisconnectedError",
message: "read failed",
cause: expect.objectContaining({ message: "read failed" }),
});
expect(client.connectionState).toBe("disconnected");
});
test("disconnects on invalid or truncated server framing", async () => {
const invalidServer = new MemoryByteServer();
const invalidClient = await connectClient(invalidServer);
invalidServer.sendRaw(encodeFrame(encodeCbor({ type: "response", id: "unknown", ok: true, result: 1 })));
expect(invalidClient.connectionState).toBe("disconnected");
const truncatedServer = new MemoryByteServer();
const truncatedClient = await connectClient(truncatedServer);
const pending = truncatedClient.request(serverTarget, { serviceId: "test", member: "pending", args: [] });
await truncatedServer.waitForMessages(2);
truncatedServer.sendRaw(new Uint8Array([0, 0, 0, 2, 1]));
truncatedServer.disconnect();
await expect(pending).rejects.toMatchObject({
name: "ProtocolValidationError",
message: expect.stringMatching(/truncated/i),
});
expect(truncatedClient.connectionState).toBe("disconnected");
});
test("disconnects when a response has no matching request", async () => {
const server = new MemoryByteServer();
const client = await connectClient(server);
server.send({ type: "response", id: "unknown-request", ok: true, result: [] });
expect(client.connectionState).toBe("disconnected");
expect(server.clientCloseCount).toBe(1);
});
});

View File

@@ -0,0 +1,9 @@
import { createServer } from "node:net";
const path = process.argv[2];
if (!path) throw new Error("Socket path argument is required");
const server = createServer();
server.listen(path, () => {
if (process.send) process.send("listening");
});

View File

@@ -0,0 +1,84 @@
import {
type ClientMessage,
ClientMessageDecoder,
encodeServerMessage,
PROTOCOL_VERSION,
type ServerMessage,
} from "@earendil-works/pi-protocol";
import type { ByteTransport, ByteTransportHandlers } from "../src/index.ts";
export class MemoryByteServer {
readonly messages: ClientMessage[] = [];
readonly serverId: string;
clientCloseCount = 0;
private handlers?: ByteTransportHandlers;
private decoder = new ClientMessageDecoder();
private readonly messageWaiters: Array<{ count: number; resolve: () => void }> = [];
constructor(serverId = "00000000-0000-4000-8000-000000000001") {
this.serverId = serverId;
}
connect(handlers: ByteTransportHandlers): ByteTransport {
this.handlers = handlers;
this.decoder = new ClientMessageDecoder();
let closed = false;
return {
send: async (chunk) => {
for (const message of this.decoder.push(chunk)) {
this.messages.push(message);
this.resolveMessageWaiters();
if (message.type === "hello") {
this.send({
type: "hello",
version: PROTOCOL_VERSION,
serverId: this.serverId,
});
}
}
},
close: () => {
if (closed) return;
closed = true;
this.clientCloseCount += 1;
if (this.handlers === handlers) this.handlers = undefined;
},
};
}
waitForMessages(count: number): Promise<void> {
if (this.messages.length >= count) return Promise.resolve();
return new Promise((resolve) => this.messageWaiters.push({ count, resolve }));
}
send(message: ServerMessage): void {
if (!this.handlers) throw new Error("No client connection");
this.handlers.onData(encodeServerMessage(message));
}
sendRaw(chunk: Uint8Array): void {
if (!this.handlers) throw new Error("No client connection");
this.handlers.onData(chunk);
}
disconnect(): void {
const handlers = this.handlers;
this.handlers = undefined;
handlers?.onClose();
}
error(error: Error): void {
const handlers = this.handlers;
this.handlers = undefined;
handlers?.onError(error);
}
private resolveMessageWaiters(): void {
for (let index = this.messageWaiters.length - 1; index >= 0; index--) {
const waiter = this.messageWaiters[index]!;
if (this.messages.length < waiter.count) continue;
this.messageWaiters.splice(index, 1);
waiter.resolve();
}
}
}

View File

@@ -0,0 +1,147 @@
import { mkdtemp, rm } from "node:fs/promises";
import { createServer, type Server, type Socket } from "node:net";
import { join } from "node:path";
import { parseServiceCall } from "@earendil-works/chord";
import { ClientMessageDecoder, encodeServerMessage, PROTOCOL_VERSION } from "@earendil-works/pi-protocol";
import { afterEach, describe, expect, test } from "vitest";
import { Client } from "../src/index.ts";
import { createUnixTransportFactory } from "../src/unix.ts";
const serverId = "00000000-0000-4000-8000-000000000001";
const tempDirectories = new Set<string>();
const servers = new Set<Server>();
const sockets = new Set<Socket>();
async function makeSocketPath(): Promise<string> {
const directory = await mkdtemp(join("/tmp", "pi-client-transport-"));
tempDirectories.add(directory);
return join(directory, "pi.sock");
}
async function listen(server: Server, path: string): Promise<void> {
servers.add(server);
server.on("connection", (socket) => {
sockets.add(socket);
socket.once("close", () => sockets.delete(socket));
});
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(path, resolve);
});
}
afterEach(async () => {
for (const socket of sockets) socket.destroy();
sockets.clear();
await Promise.all(
[...servers].map(
(server) =>
new Promise<void>((resolve) => {
server.close(() => resolve());
}),
),
);
servers.clear();
await Promise.all([...tempDirectories].map((directory) => rm(directory, { recursive: true, force: true })));
tempDirectories.clear();
});
test("rejects invalid Unix transport options", () => {
expect(() => createUnixTransportFactory({ path: "" })).toThrow(/must not be empty/);
expect(() => createUnixTransportFactory({ path: "/tmp/pi.sock", maxPendingBytes: 0 })).toThrow(/positive/);
});
describe.runIf(process.platform !== "win32")("createUnixTransportFactory", () => {
test("carries a complete Client handshake and request over a real Unix socket", async () => {
const path = await makeSocketPath();
const receivedMembers: string[] = [];
const server = createServer((socket) => {
const decoder = new ClientMessageDecoder();
socket.on("data", (chunk) => {
for (const message of decoder.push(chunk)) {
if (message.type === "hello") {
const frame = encodeServerMessage({
type: "hello",
version: PROTOCOL_VERSION,
serverId,
});
for (const byte of frame) socket.write(Uint8Array.of(byte));
continue;
}
if (message.type === "cancel") continue;
const call = parseServiceCall(message.call);
receivedMembers.push(`${call.serviceId}.${call.member}`);
const frame = encodeServerMessage({
type: "response",
id: message.id,
ok: true,
result: [],
});
const split = Math.floor(frame.byteLength / 2);
socket.write(frame.subarray(0, split));
socket.write(frame.subarray(split));
}
});
});
await listen(server, path);
const client = new Client({ serverId, transportFactory: createUnixTransportFactory({ path }) });
try {
await expect(client.connect()).resolves.toMatchObject({ serverId });
await expect(
client.request({ serverId }, { serviceId: "test.server", member: "list", args: [] }),
).resolves.toEqual([]);
expect(receivedMembers).toEqual(["test.server.list"]);
} finally {
await client.dispose();
}
});
test("reports truncated final frames through Client", async () => {
const path = await makeSocketPath();
const server = createServer((socket) => {
const decoder = new ClientMessageDecoder();
socket.on("data", (chunk) => {
for (const message of decoder.push(chunk)) {
if (message.type === "hello") {
socket.write(
encodeServerMessage({
type: "hello",
version: PROTOCOL_VERSION,
serverId,
}),
);
} else {
socket.end(new Uint8Array([0, 0, 0, 2, 1]));
}
}
});
});
await listen(server, path);
const client = new Client({ serverId, transportFactory: createUnixTransportFactory({ path }) });
try {
await client.connect();
await expect(
client.request({ serverId }, { serviceId: "test.server", member: "list", args: [] }),
).rejects.toMatchObject({
name: "ProtocolValidationError",
message: expect.stringMatching(/truncated/i),
});
expect(client.connectionState).toBe("disconnected");
} finally {
await client.dispose();
}
});
test("rejects connection attempts to missing sockets", async () => {
const path = await makeSocketPath();
await expect(
createUnixTransportFactory({ path })({
onData: () => {},
onClose: () => {},
onError: () => {},
}),
).rejects.toMatchObject({ code: "ENOENT" });
});
});

View File

@@ -0,0 +1,184 @@
import { type ChildProcess, fork } from "node:child_process";
import { once } from "node:events";
import { lstat, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { createServer, type Server, type Socket } from "node:net";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { Server as RuntimeServer } from "../../server/src/server.ts";
import { createTestServerServices } from "../../server/src/testing/host.ts";
import { createUnixListener } from "../../server/src/transports/unix/listener.ts";
import { discoverUnixServers } from "../src/unix.ts";
const tempDirectories = new Set<string>();
const servers = new Set<RuntimeServer>();
const rawServers = new Set<Server>();
const rawSockets = new Set<Socket>();
const children = new Set<ChildProcess>();
async function makeDirectory(): Promise<string> {
const directory = await mkdtemp(join("/tmp", "pc-"));
tempDirectories.add(directory);
return directory;
}
function serverId(value: number): string {
return `00000000-0000-4000-8000-${value.toString(16).padStart(12, "0")}`;
}
async function startServer(
directory: string,
fileServerId: string,
reportedServerId = fileServerId,
): Promise<RuntimeServer> {
const path = join(directory, `${fileServerId}.sock`);
const server = new RuntimeServer(
{
serverServices: createTestServerServices(),
resolveSession: async () => Promise.reject(new Error("unused")),
openSession: async () => Promise.reject(new Error("unused")),
},
{ listeners: [createUnixListener({ path })], serverId: reportedServerId },
);
servers.add(server);
await server.start();
return server;
}
async function startSilentSocket(
path: string,
connections?: { active: number; maximum: number; total: number },
): Promise<void> {
const server = createServer((socket) => {
rawSockets.add(socket);
if (connections) {
connections.active += 1;
connections.maximum = Math.max(connections.maximum, connections.active);
connections.total += 1;
}
socket.once("close", () => {
rawSockets.delete(socket);
if (connections) connections.active -= 1;
});
});
rawServers.add(server);
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(path, resolve);
});
}
afterEach(async () => {
for (const child of children) {
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
}
await Promise.all([...children].map((child) => (child.exitCode === null ? once(child, "exit") : undefined)));
children.clear();
await Promise.all([...servers].map((server) => server.close()));
servers.clear();
for (const socket of rawSockets) socket.destroy();
rawSockets.clear();
await Promise.all(
[...rawServers].map(
(server) =>
new Promise<void>((resolve) => {
server.close(() => resolve());
}),
),
);
rawServers.clear();
await Promise.all([...tempDirectories].map((directory) => rm(directory, { recursive: true, force: true })));
tempDirectories.clear();
});
describe("discoverUnixServers", () => {
test("returns no routes when the server directory is missing", async () => {
const directory = join(await makeDirectory(), "missing");
await expect(discoverUnixServers({ directory })).resolves.toEqual([]);
});
test("discovers reachable servers in server ID order", async () => {
const directory = await makeDirectory();
const first = serverId(1);
const second = serverId(2);
await startServer(directory, second);
await startServer(directory, first);
await expect(discoverUnixServers({ directory })).resolves.toEqual([
{ serverId: first, path: join(directory, `${first}.sock`) },
{ serverId: second, path: join(directory, `${second}.sock`) },
]);
});
test("ignores malformed entries, non-sockets, and mismatched servers", async () => {
const directory = await makeDirectory();
await writeFile(join(directory, `${serverId(1)}.sock`), "not a socket");
await writeFile(join(directory, "not-a-server.sock"), "ignored");
await mkdir(join(directory, `${serverId(2)}.sock`));
await startServer(directory, serverId(3), serverId(4));
await expect(discoverUnixServers({ directory })).resolves.toEqual([]);
});
test("ignores stale sockets without deleting them", async () => {
const directory = await makeDirectory();
const id = serverId(1);
const path = join(directory, `${id}.sock`);
const child = fork(new URL("fixtures/stale-socket-server.mjs", import.meta.url), [path], {
stdio: ["ignore", "ignore", "inherit", "ipc"],
});
children.add(child);
await once(child, "message");
child.kill("SIGKILL");
await once(child, "exit");
children.delete(child);
await expect(discoverUnixServers({ directory })).resolves.toEqual([]);
expect((await lstat(path)).isSocket()).toBe(true);
});
test("times out an unresponsive socket without deleting it", async () => {
const directory = await makeDirectory();
const id = serverId(1);
const path = join(directory, `${id}.sock`);
await startSilentSocket(path);
await expect(discoverUnixServers({ directory, timeoutMs: 20 })).resolves.toEqual([]);
expect((await lstat(path)).isSocket()).toBe(true);
});
test("limits concurrent probes to 16", async () => {
const directory = await makeDirectory();
const connections = { active: 0, maximum: 0, total: 0 };
for (let index = 1; index <= 20; index++) {
await startSilentSocket(join(directory, `${serverId(index)}.sock`), connections);
}
const discovery = discoverUnixServers({ directory, timeoutMs: 100 });
await expect.poll(() => connections.active).toBe(16);
expect(connections.maximum).toBe(16);
await expect(discovery).resolves.toEqual([]);
expect(connections.total).toBe(20);
});
test("ignores an endpoint that closes before its handshake", async () => {
const directory = await makeDirectory();
const id = serverId(1);
const path = join(directory, `${id}.sock`);
const server = createServer((socket) => socket.destroy());
rawServers.add(server);
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(path, resolve);
});
await expect(discoverUnixServers({ directory })).resolves.toEqual([]);
});
test("propagates unexpected filesystem errors", async () => {
const directory = await makeDirectory();
const file = join(directory, "not-a-directory");
await writeFile(file, "content");
await expect(discoverUnixServers({ directory: file })).rejects.toMatchObject({ code: "ENOTDIR" });
});
});