commit 7a88f274dd3016a86e789d0a4fb45d7aa886ed37
parent 4cf569b1a2524df92abda79d195c48dd76c252f9
Author: Florian Dold <dold@taler.net>
Date: Fri, 11 Sep 2026 09:50:51 +0200
taler-harness: keep wallet socket paths short
Allocate wallet sockets in a short temporary directory so long test
names and log roots cannot exceed the Unix socket path limit. Give each
wallet its own path and remove the socket directory during shutdown.
Diffstat:
2 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
@@ -473,6 +473,8 @@ export class GlobalTestState {
private logStreamCompletions: Promise<void>[] = [];
private logStreamFailures: Error[] = [];
private allowedServiceLogs: CountedAllowedServiceLog[] = [];
+ private walletSocketDir?: string;
+ private nextWalletSocket = 0;
spanStack: string[] = [];
@@ -482,6 +484,20 @@ export class GlobalTestState {
this.servers = [];
}
+ allocateWalletSocketPath(): string {
+ // Unix socket paths have a small fixed limit, independent of the path
+ // length accepted for test logs and wallet databases.
+ this.walletSocketDir ??= fs.mkdtempSync("/tmp/taler-wallet-");
+ return path.join(this.walletSocketDir, `${this.nextWalletSocket++}.sock`);
+ }
+
+ private removeWalletSockets(): void {
+ if (this.walletSocketDir) {
+ fs.rmSync(this.walletSocketDir, { recursive: true, force: true });
+ this.walletSocketDir = undefined;
+ }
+ }
+
createLogWriteStream(
filename: fs.PathLike,
options?: Parameters<typeof fs.createWriteStream>[1],
@@ -669,6 +685,7 @@ export class GlobalTestState {
p.proc.kill("SIGTERM");
}
}
+ this.removeWalletSockets();
}
spawnService(
@@ -746,6 +763,7 @@ export class GlobalTestState {
logger.trace(`done waiting for ${p.proc.pid}`);
}
}
+ this.removeWalletSockets();
await this.waitForLogStreams();
}
@@ -3334,11 +3352,13 @@ export class WalletService {
walletProc: ProcessWrapper | undefined;
private internalDbPath: string;
+ private readonly internalSocketPath: string;
constructor(
private globalState: GlobalTestState,
private opts: WalletServiceOptions,
) {
+ this.internalSocketPath = globalState.allocateWalletSocketPath();
if (this.opts.overrideDbPath) {
this.internalDbPath = this.opts.overrideDbPath;
} else {
@@ -3354,11 +3374,7 @@ export class WalletService {
}
get socketPath() {
- const unixPath = path.join(
- this.globalState.testDir,
- `${this.opts.name}.sock`,
- );
- return unixPath;
+ return this.internalSocketPath;
}
get dbPath() {
diff --git a/packages/taler-harness/src/harness/lifecycle.test.ts b/packages/taler-harness/src/harness/lifecycle.test.ts
@@ -20,7 +20,39 @@ import os from "node:os";
import path from "node:path";
import { test } from "node:test";
import { startFakeChallenger } from "./fake-challenger.js";
-import { GlobalTestState, runTestWithState } from "./harness.js";
+import { GlobalTestState, WalletService, runTestWithState } from "./harness.js";
+import net from "node:net";
+import { once } from "node:events";
+
+test("wallet sockets work with long test paths and are removed on shutdown", async (t) => {
+ const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "harness-lifecycle-"));
+ t.after(() => fs.rmSync(testDir, { recursive: true, force: true }));
+ const state = new GlobalTestState({
+ testDir: path.join(testDir, "long-test-name-".repeat(10)),
+ });
+ t.after(() => state.shutdown());
+ const wallets = ["first", "second"].map(
+ (name) => new WalletService(state, { name }),
+ );
+ assert.notEqual(wallets[0].socketPath, wallets[1].socketPath);
+ for (const wallet of wallets) {
+ const server = net.createServer((socket) => socket.end());
+ // Bind and connect to the actual path; a length assertion would miss
+ // truncation and collisions in the platform's socket implementation.
+ server.listen(wallet.socketPath);
+ await once(server, "listening");
+ try {
+ const client = net.createConnection(wallet.socketPath);
+ await once(client, "close");
+ } finally {
+ await new Promise<void>((resolve, reject) => {
+ server.close((error) => (error ? reject(error) : resolve()));
+ });
+ }
+ }
+ await state.shutdown();
+ assert.equal(fs.existsSync(path.dirname(wallets[0].socketPath)), false);
+});
test("test lifecycle removes its process listeners", async (t) => {
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "harness-lifecycle-"));