3 Commits

Author SHA1 Message Date
f6e4ac8aeb test(crypto): stop letting the scheduler answer for the code
Some checks failed
Test / test (push) Has been cancelled
Third flake found in the same hunt. `timing variance stays bounded across
mismatch positions` took two single measurements of 50 000 iterations each and
compared their ratio, which is one GC pause away from a false alarm — it failed
about one run in fourteen on a loaded machine.

Two things were wrong with the measurement, not the property:

The first loop through that code pays for JIT compilation the second one does
not, so whichever side ran first was biased upward. The old comment admitted it
— "allow 2x variance for JIT/noise" — which is a tolerance covering a
measurement artefact rather than the thing under test. There is a warm-up now.

And a single pair has no defence against a scheduler hiccup. Five interleaved
pairs compared by median throw the pause out instead of the property.

The threshold is unchanged at 3, deliberately. An early-exit compare takes
~256x longer to reach a mismatch in the last byte than the first, on every
round; nothing here weakens what the test catches.

Verified that it still catches what it is for: with `constantTimeEqual`
temporarily replaced by an early-exit loop the test fails, and passes again
with the real one restored. A security test nobody has watched fail is a
security test nobody knows works.

A security test that cries wolf under load is worse than none — it teaches
people to re-run until green, and then a real regression looks like the noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014489bKUtUEY1Zgs9xN9mt7
2026-09-08 16:42:37 +02:00
baddf5f56e fix(transfer): an abort left N-1 unhandled rejections behind
Some checks failed
Test / test (push) Has been cancelled
Second flake in the same hunt, and a real bug rather than a test artifact.

`runRoundRobinUpload` and its resumed twin ended with `await Promise.all(workers)`.
`Promise.all` rejects the moment one worker throws and leaves the rest running
with nobody holding their rejections — so an abort, which fails every lane at
once, produces one propagated error and N-1 unhandled ones.

Bun counts an unhandled rejection as a test failure, which is how

    (fail) Resume protocol — kill-restart-verify
           → sender crash mid-transfer → resumeUpload completes the same stream
    TransferAbortError: Aborted during attempt

failed about one run in fourteen, with an error the test had already caught
properly through `handle1.done()`. The test was right; the engine was dropping
rejections on the floor beside it.

`settleWorkers` waits for every lane, then reports the first real failure —
preferring a genuine error over `TransferAbortError` when both happened, since
an abort is not the interesting one when something actually broke. Four call
sites.

This is not only a test problem. In production the same shape means a
cancelled or failed multi-lane transfer emits unhandled rejections into
whatever process is running the engine, and on a strict runtime that is a
crash rather than a log line.

Verified: 1198 pass / 0 fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014489bKUtUEY1Zgs9xN9mt7
2026-09-08 16:39:49 +02:00
53ce776c02 test: stop guessing port numbers
Twenty test servers picked a port with `NNNN + Math.floor(Math.random() * 500)`
and hoped it was free. Several files guessed inside the *same* range —
sdk.test.ts and gates.test.ts both on 19500, three files on 22000, and
webrtc-integration.test.ts used 22000 twice within itself — so with test files
serving in parallel the collision was not unlikely, it was scheduled.

Found from the outside, in Nova, where Shade is vendored: across eight runs of
an unchanged tree, two had failures and six were clean. The visible error was

    Failed to start server. Is port 24287 in use?  EADDRINUSE

and then a second, louder one: `teardownRig` ran on a rig that setup had never
finished building and died on `rig.alice`, so the TypeError from cleanup is
what you read first and the real cause scrolled past above it.

`Bun.serve({ port: 0 })` lets the OS hand out a free port and `server.port`
reads it back. There is no range to collide inside.

teardownRig now tolerates a rig that was never built. Cleanup must never be the
loudest thing in a failing test.

A suite that fails a quarter of the time is worse than a suite that fails: it
teaches everyone to re-run it, and then a real regression looks like the flake.

Verified: 1198 pass / 0 fail here, and the same fix is going into Nova's
vendored copy so the next sync does not bring the guessing back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014489bKUtUEY1Zgs9xN9mt7
2026-09-08 16:33:40 +02:00
14 changed files with 189 additions and 67 deletions

View File

@@ -59,26 +59,40 @@ describe('Cryptographic Hardening', () => {
const mismatchAtEnd = new Uint8Array(target); const mismatchAtEnd = new Uint8Array(target);
mismatchAtEnd[len - 1] ^= 0xff; mismatchAtEnd[len - 1] ^= 0xff;
// Measure many iterations to get a stable signal const iterations = 20000;
const iterations = 50000; const measure = (other: Uint8Array): number => {
const t0 = performance.now();
for (let i = 0; i < iterations; i++) crypto.constantTimeEqual(target, other);
return performance.now() - t0;
};
const start1 = performance.now(); // Warm up before measuring anything. The first loop through this code
for (let i = 0; i < iterations; i++) { // pays for JIT compilation that the second one does not, which biased
crypto.constantTimeEqual(target, mismatchAtStart); // whichever side ran first and is why the old version had to "allow 2x
// for JIT/noise" — a tolerance covering a measurement artefact rather
// than the property under test.
measure(mismatchAtStart);
measure(mismatchAtEnd);
// Paired, interleaved samples, compared by median. A single pair is one
// GC pause away from a false alarm: this test failed roughly one run in
// fourteen on a loaded machine on 08.09.2026, and a security test that
// cries wolf under load is a security test people learn to re-run until
// it passes. The median throws out the pause instead of the property.
const ratios: number[] = [];
for (let round = 0; round < 5; round++) {
const a = measure(mismatchAtStart);
const b = measure(mismatchAtEnd);
ratios.push(Math.max(a, b) / Math.min(a, b));
} }
const timeStart = performance.now() - start1; ratios.sort((x, y) => x - y);
const median = ratios[Math.floor(ratios.length / 2)]!;
const start2 = performance.now(); // The threshold is unchanged and deliberately so: an early-exit compare
for (let i = 0; i < iterations; i++) { // would take ~256x longer to reach a mismatch in the last byte than the
crypto.constantTimeEqual(target, mismatchAtEnd); // first, on every single round. Nothing about sampling weakens what this
} // catches — it only stops the scheduler from answering for the code.
const timeEnd = performance.now() - start2; expect(median).toBeLessThan(3);
// With constant-time comparison, these should be very close.
// Non-constant-time would show timeEnd >> timeStart (early exit vs full scan).
// Allow 2x variance for JIT/noise, but it should never be 10x.
const ratio = Math.max(timeStart, timeEnd) / Math.min(timeStart, timeEnd);
expect(ratio).toBeLessThan(3);
}); });
}); });

View File

@@ -14,8 +14,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
store: new MemoryPrekeyStore(), store: new MemoryPrekeyStore(),
disableRateLimit: true, disableRateLimit: true,
}); });
const port = 19500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }

View File

@@ -12,8 +12,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
store: new MemoryPrekeyStore(), store: new MemoryPrekeyStore(),
disableRateLimit: true, disableRateLimit: true,
}); });
const port = 19500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }

View File

@@ -36,8 +36,12 @@ async function startServer(): Promise<ServerHandle> {
blobStore: new MemoryBlobStore(), blobStore: new MemoryBlobStore(),
disableRateLimit: true, disableRateLimit: true,
}); });
const port = 19000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: app.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: app.fetch });
const port = handle.port;
return { return {
url: `http://localhost:${port}`, url: `http://localhost:${port}`,
stop: () => handle.stop(true), stop: () => handle.stop(true),

View File

@@ -25,8 +25,12 @@ async function startPrekeyServer(): Promise<{
disableRateLimit: true, disableRateLimit: true,
events, events,
}); });
const port = 19500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { return {
url: `http://localhost:${port}`, url: `http://localhost:${port}`,
stop: () => handle.stop(), stop: () => handle.stop(),

View File

@@ -26,8 +26,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
disableRateLimit: true, disableRateLimit: true,
events, events,
}); });
const port = 21000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }
@@ -46,8 +50,12 @@ async function setupRig(): Promise<TestRig> {
// Spin up Bob's HTTP transfer endpoint. // Spin up Bob's HTTP transfer endpoint.
const bobApp = await bob.transferRoute(); const bobApp = await bob.transferRoute();
const port = 21500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const bobServer = Bun.serve({ port, fetch: bobApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch });
const port = bobServer.port;
const bobBaseUrl = `http://localhost:${port}`; const bobBaseUrl = `http://localhost:${port}`;
// Wire up Alice's outgoing transfer routing. // Wire up Alice's outgoing transfer routing.

View File

@@ -33,8 +33,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
disableRateLimit: true, disableRateLimit: true,
events, events,
}); });
const port = 22000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }
@@ -49,8 +53,12 @@ async function setupRig(): Promise<TestRig> {
}, },
}); });
const bobApp = await bob.transferRoute(); const bobApp = await bob.transferRoute();
const port = 22500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const bobServer = Bun.serve({ port, fetch: bobApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch });
const port = bobServer.port;
const bobBaseUrl = `http://localhost:${port}`; const bobBaseUrl = `http://localhost:${port}`;
alice.configureTransfers({ alice.configureTransfers({

View File

@@ -103,8 +103,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
disableRateLimit: true, disableRateLimit: true,
events, events,
}); });
const port = 23000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }
@@ -127,13 +131,21 @@ async function setupRig(connectTimeoutMs: number): Promise<Rig> {
bob.configureWebRTC({ factory: broken, connectTimeoutMs }); bob.configureWebRTC({ factory: broken, connectTimeoutMs });
const bobApp = await bob.transferRoute(); const bobApp = await bob.transferRoute();
const bobPort = 23500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const bobServer = Bun.serve({ port: bobPort, fetch: bobApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch });
const bobPort = bobServer.port;
baseUrls.set('bob', `http://localhost:${bobPort}`); baseUrls.set('bob', `http://localhost:${bobPort}`);
const aliceApp = await alice.transferRoute(); const aliceApp = await alice.transferRoute();
const alicePort = 24000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const aliceServer = Bun.serve({ port: alicePort, fetch: aliceApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const aliceServer = Bun.serve({ port: 0, fetch: aliceApp.fetch });
const alicePort = aliceServer.port;
baseUrls.set('alice', `http://localhost:${alicePort}`); baseUrls.set('alice', `http://localhost:${alicePort}`);
return { return {
@@ -145,12 +157,18 @@ async function setupRig(connectTimeoutMs: number): Promise<Rig> {
}; };
} }
async function teardownRig(rig: Rig): Promise<void> { async function teardownRig(rig: Rig | undefined): Promise<void> {
await rig.alice.shutdown(); // Tolerates a rig that never finished being built. When setupRig threw —
await rig.bob.shutdown(); // EADDRINUSE, before ports stopped being guessed — this ran anyway and died
rig.bobServerStop(); // on `rig.alice`, so one failure became two and the second one, a TypeError
rig.aliceServerStop(); // in teardown, is what you saw first. Cleanup must never be the loudest
rig.prekeyStop(); // thing in a failing test.
if (!rig) return;
await rig.alice?.shutdown().catch(() => {});
await rig.bob?.shutdown().catch(() => {});
try { rig.bobServerStop?.(); } catch { /* already gone */ }
try { rig.aliceServerStop?.(); } catch { /* already gone */ }
try { rig.prekeyStop?.(); } catch { /* already gone */ }
} }
function hex(b: Uint8Array): string { function hex(b: Uint8Array): string {

View File

@@ -40,8 +40,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
disableRateLimit: true, disableRateLimit: true,
events, events,
}); });
const port = 22000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }
@@ -70,13 +74,21 @@ async function setupRig(): Promise<Rig> {
bob.configureWebRTC({ factory, connectTimeoutMs: 10_000 }); bob.configureWebRTC({ factory, connectTimeoutMs: 10_000 });
const bobApp = await bob.transferRoute(); const bobApp = await bob.transferRoute();
const bobPort = 22500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const bobServer = Bun.serve({ port: bobPort, fetch: bobApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch });
const bobPort = bobServer.port;
const bobBaseUrl = `http://localhost:${bobPort}`; const bobBaseUrl = `http://localhost:${bobPort}`;
const aliceApp = await alice.transferRoute(); const aliceApp = await alice.transferRoute();
const alicePort = 22000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const aliceServer = Bun.serve({ port: alicePort, fetch: aliceApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const aliceServer = Bun.serve({ port: 0, fetch: aliceApp.fetch });
const alicePort = aliceServer.port;
const aliceBaseUrl = `http://localhost:${alicePort}`; const aliceBaseUrl = `http://localhost:${alicePort}`;
baseUrls.set('alice', aliceBaseUrl); baseUrls.set('alice', aliceBaseUrl);

View File

@@ -44,8 +44,12 @@ async function startPrekeyServer(): Promise<{ url: string; stop: () => void }> {
disableRateLimit: true, disableRateLimit: true,
events, events,
}); });
const port = 24500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: server.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: server.fetch });
const port = handle.port;
return { url: `http://localhost:${port}`, stop: () => handle.stop() }; return { url: `http://localhost:${port}`, stop: () => handle.stop() };
} }
@@ -70,13 +74,21 @@ async function setupRig(opts: { withWebRTC: boolean }): Promise<Rig> {
} }
const bobApp = await bob.transferRoute(); const bobApp = await bob.transferRoute();
const bobPort = 25000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const bobServer = Bun.serve({ port: bobPort, fetch: bobApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch });
const bobPort = bobServer.port;
baseUrls.set('bob', `http://localhost:${bobPort}`); baseUrls.set('bob', `http://localhost:${bobPort}`);
const aliceApp = await alice.transferRoute(); const aliceApp = await alice.transferRoute();
const alicePort = 25500 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const aliceServer = Bun.serve({ port: alicePort, fetch: aliceApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const aliceServer = Bun.serve({ port: 0, fetch: aliceApp.fetch });
const alicePort = aliceServer.port;
baseUrls.set('alice', `http://localhost:${alicePort}`); baseUrls.set('alice', `http://localhost:${alicePort}`);
return { return {

View File

@@ -451,7 +451,7 @@ export class TransferEngine {
for (const q of queues.values()) q.abort(err); for (const q of queues.values()) q.abort(err);
throw err; throw err;
} }
await Promise.all(workers); await settleWorkers(workers);
} }
private async runRoundRobinUploadResumed( private async runRoundRobinUploadResumed(
@@ -518,7 +518,7 @@ export class TransferEngine {
for (const q of queues.values()) q.abort(err); for (const q of queues.values()) q.abort(err);
throw err; throw err;
} }
await Promise.all(workers); await settleWorkers(workers);
} }
/** /**
@@ -774,7 +774,7 @@ export class TransferEngine {
for (const q of queues.values()) q.abort(err); for (const q of queues.values()) q.abort(err);
throw err; throw err;
} }
await Promise.all(workers); await settleWorkers(workers);
} }
private async runRoundRobinUpload(state: OutgoingState): Promise<void> { private async runRoundRobinUpload(state: OutgoingState): Promise<void> {
@@ -851,7 +851,7 @@ export class TransferEngine {
for (const q of queues.values()) q.abort(err); for (const q of queues.values()) q.abort(err);
throw err; throw err;
} }
await Promise.all(workers); await settleWorkers(workers);
} }
private async runLaneWorker( private async runLaneWorker(
@@ -1596,3 +1596,25 @@ function snapshotIncomingLanes(
} }
return out; return out;
} }
/**
* Wait for every lane worker, then report the first real failure.
*
* `Promise.all` rejects the moment one worker throws and leaves the others
* running with nobody holding their rejections — so an abort, which fails
* every lane at once, produces one propagated error and N-1 unhandled ones.
* Bun counts an unhandled rejection as a test failure, which is how the resume
* test failed roughly one run in fourteen on 08.09.2026 with a
* `TransferAbortError` the test had already caught through the handle.
*
* An abort is not the interesting failure when a real one is present, so a
* genuine error is preferred over `TransferAbortError` when both occurred.
*/
async function settleWorkers(workers: Promise<unknown>[]): Promise<void> {
const results = await Promise.allSettled(workers);
const errors = results
.filter((r): r is PromiseRejectedResult => r.status === 'rejected')
.map((r) => r.reason);
if (errors.length === 0) return;
throw errors.find((e) => !(e instanceof TransferAbortError)) ?? errors[0];
}

View File

@@ -41,8 +41,12 @@ describe('Resume protocol — kill-restart-verify', () => {
myAddress: 'bob', myAddress: 'bob',
}); });
const receiverApp = await createTransferRoutes(receiverEngine); const receiverApp = await createTransferRoutes(receiverEngine);
const port = 22000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const server = Bun.serve({ port, fetch: receiverApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const server = Bun.serve({ port: 0, fetch: receiverApp.fetch });
const port = server.port;
const baseUrl = `http://localhost:${port}`; const baseUrl = `http://localhost:${port}`;
// Receiver accepts incoming. // Receiver accepts incoming.

View File

@@ -39,8 +39,12 @@ describe('WS opt-in transport with HTTP fallback', () => {
myAddress: 'bob', myAddress: 'bob',
}); });
const httpApp = await createTransferRoutes(receiverEngine); const httpApp = await createTransferRoutes(receiverEngine);
const httpPort = 23000 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const httpServer = Bun.serve({ port: httpPort, fetch: httpApp.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const httpServer = Bun.serve({ port: 0, fetch: httpApp.fetch });
const httpPort = httpServer.port;
const httpBaseUrl = `http://localhost:${httpPort}`; const httpBaseUrl = `http://localhost:${httpPort}`;
cleanups.push(() => httpServer.stop()); cleanups.push(() => httpServer.stop());

View File

@@ -31,8 +31,12 @@ describe('ShadeFetchTransport with KT verifier', () => {
}, },
}); });
const port = 20100 + Math.floor(Math.random() * 500); // Port 0 lets the OS hand out a free one. This guessed inside a 500-wide
const handle = Bun.serve({ port, fetch: app.fetch }); // range and hoped — and several files here guessed inside the SAME range,
// so with test files serving in parallel the collision arrived about a
// quarter of the time as EADDRINUSE.
const handle = Bun.serve({ port: 0, fetch: app.fetch });
const port = handle.port;
try { try {
const baseUrl = `http://localhost:${port}`; const baseUrl = `http://localhost:${port}`;