Skip to content

Commit 4342f68

Browse files
committed
test(kubo): pre-init repos with ephemeral swarm so parallel tests don't collide on 4001
Before this branch, mergeCliDefaultsIntoIpfsConfig overrode Swarm to /tcp/0 which (incidentally) gave each test daemon a kernel-assigned port. Now that production preserves kubo's default Swarm (port 4001), parallel test daemons spawned by vitest fileParallelism race for the same port and fail with "IPFS Swarm port 0.0.0.0:4001 already in use". Add a test helper that pre-initializes the kubo repo, applies bitsocial defaults, and rewrites Swarm to ephemeral addresses. The daemon's own `ipfs init` then bails because the config already exists, mergeCliDefaults is skipped, and kubo binds to per-process kernel-assigned ports. Refs #44
1 parent 83d6c2d commit 4342f68

3 files changed

Lines changed: 58 additions & 1 deletion

File tree

test/helpers/daemon-helpers.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { ChildProcess, spawn } from "child_process";
22
import net from "net";
3+
import path from "path";
34
import { directory as randomDirectory } from "tempy";
45
import WebSocket from "ws";
56
import defaults from "../../dist/common-utils/defaults.js";
7+
import { preInitKuboWithEphemeralSwarm } from "./kubo-helpers.js";
68

79
export type ManagedChildProcess = ChildProcess & { kuboRpcUrl?: URL; capturedStdout?: string; capturedStderr?: string };
810

@@ -72,7 +74,20 @@ export const startPkcDaemon = (args: string[], env?: Record<string, string>): Pr
7274
const hasCustomDataPath = args.some((arg) => arg.startsWith("--pkcOptions.dataPath"));
7375
const hasCustomLogPath = args.some((arg) => arg === "--logPath");
7476
const logPathArgs = hasCustomLogPath ? [] : ["--logPath", randomDirectory()];
75-
const daemonArgs = hasCustomDataPath ? args : ["--pkcOptions.dataPath", randomDirectory(), ...args];
77+
const dataPath = hasCustomDataPath
78+
? (args[args.findIndex((a) => a.startsWith("--pkcOptions.dataPath")) + 1] as string)
79+
: randomDirectory();
80+
const daemonArgs = hasCustomDataPath ? args : ["--pkcOptions.dataPath", dataPath, ...args];
81+
82+
// Pre-init kubo so parallel test daemons don't collide on swarm port 4001.
83+
const apiUrl = new URL(env?.KUBO_RPC_URL ?? defaults.KUBO_RPC_URL.toString());
84+
const gatewayUrl = new URL(env?.IPFS_GATEWAY_URL ?? defaults.IPFS_GATEWAY_URL.toString());
85+
try {
86+
await preInitKuboWithEphemeralSwarm(path.join(dataPath, ".bitsocial-cli.ipfs"), apiUrl, gatewayUrl);
87+
} catch (error) {
88+
return reject(error);
89+
}
90+
7691
const daemonProcess = spawn("node", ["./bin/run", "daemon", ...logPathArgs, ...daemonArgs], {
7792
stdio: ["pipe", "pipe", "pipe"],
7893
env: env ? { ...process.env, ...env } : undefined

test/helpers/kubo-helpers.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as fs from "fs/promises";
2+
import path from "path";
3+
import { execFile } from "child_process";
4+
import { promisify } from "util";
5+
import { path as resolveKuboBinary } from "kubo";
6+
import { mergeCliDefaultsIntoIpfsConfig } from "../../src/ipfs/startIpfs.js";
7+
8+
const execFileAsync = promisify(execFile);
9+
10+
const EPHEMERAL_SWARM_ADDRESSES = [
11+
"/ip4/0.0.0.0/tcp/0",
12+
"/ip6/::/tcp/0",
13+
"/ip4/0.0.0.0/udp/0/quic-v1",
14+
"/ip4/0.0.0.0/udp/0/quic-v1/webtransport",
15+
"/ip6/::/udp/0/quic-v1",
16+
"/ip6/::/udp/0/quic-v1/webtransport"
17+
];
18+
19+
// Pre-init a kubo repo so each parallel test daemon gets its own kernel-assigned
20+
// swarm port instead of fighting over the default 4001. Mirrors what
21+
// startKuboNode does on a fresh config (init + server profile + merge defaults),
22+
// then overrides Swarm to ephemeral addresses. When the bitsocial daemon later
23+
// runs `ipfs init` against this dir it'll bail with "configuration file already
24+
// exists", skip mergeCliDefaultsIntoIpfsConfig, and spawn kubo with our Swarm.
25+
export const preInitKuboWithEphemeralSwarm = async (ipfsDataPath: string, apiUrl: URL, gatewayUrl: URL) => {
26+
await fs.mkdir(ipfsDataPath, { recursive: true });
27+
const kuboBinaryPath = await resolveKuboBinary();
28+
const env = { ...process.env, IPFS_PATH: ipfsDataPath };
29+
30+
await execFileAsync(kuboBinaryPath, ["init"], { env });
31+
await execFileAsync(kuboBinaryPath, ["config", "profile", "apply", "server"], { env });
32+
33+
const configPath = path.join(ipfsDataPath, "config");
34+
await mergeCliDefaultsIntoIpfsConfig(() => {}, configPath, apiUrl, gatewayUrl);
35+
36+
const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
37+
config.Addresses = { ...(config.Addresses ?? {}), Swarm: EPHEMERAL_SWARM_ADDRESSES };
38+
await fs.writeFile(configPath, JSON.stringify(config, null, 4));
39+
};

test/kubo/kuboRpcGateway.integration.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { directory as tempDirectory } from "tempy";
99
import { setTimeout as delay } from "timers/promises";
1010
import { promisify } from "util";
1111
import { startKuboNode } from "../../src/ipfs/startIpfs.js";
12+
import { preInitKuboWithEphemeralSwarm } from "../helpers/kubo-helpers.js";
1213

1314
const execFileAsync = promisify(execFile);
1415

@@ -184,6 +185,8 @@ describe("kubo RPC + gateway integration", { timeout: 120_000 }, () => {
184185
apiUrl = new URL(`http://127.0.0.1:${apiPort}`);
185186
gatewayUrl = new URL(`http://127.0.0.1:${gatewayPort}`);
186187

188+
await preInitKuboWithEphemeralSwarm(ipfsRepoPath, apiUrl, gatewayUrl);
189+
187190
kuboProcess = await startKuboNode(apiUrl, gatewayUrl, dataPath);
188191

189192
await waitForOkResponse(() => fetch(new URL("/api/v0/version", apiUrl), { method: "POST" }));

0 commit comments

Comments
 (0)