Bound the remaining fault-path tests against a regressed fault/cleanup path
Same hygiene sweep as the previous commits, applied to the three tests whose own subject is a fault or cleanup path that, if it regressed, would leave a receiveFrame() call with nothing to ever resolve or reject it: the two "fix 1" throwing-handler tests (a malformed frame that should fault the connection, and a peer shutdown that should run cleanup()) and the "fix 3" peer-shutdown test. Each now wraps its transport in try/finally and uses receiveFrameOrTimeout() instead of a bare receiveFrame() behind assert.rejects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f5d1de0a8c
commit
2cd63f3ee9
1 changed files with 55 additions and 36 deletions
|
|
@ -748,14 +748,20 @@ test("fix 1: a throwing onDisposed handler cannot crash the pump when a malforme
|
|||
secondHandlerRan = true;
|
||||
});
|
||||
|
||||
const connectionIdA = await acceptedA;
|
||||
// Random bytes on a reliable stream: not a valid encoded frame, so
|
||||
// decodeRealtimeFrame throws inside onStreamFrame, which native's
|
||||
// dispatch loop (fix 1's native half) catches and faults only this
|
||||
// connection.
|
||||
listener.sendRaw(connectionIdA, new Uint8Array([9, 9, 9, 9, 9, 9, 9, 9]), CULTMESH_QUIC_STREAM_RELIABLE);
|
||||
await assert.rejects(transportA.receiveFrame());
|
||||
assert.equal(secondHandlerRan, true, "a throwing handler must not stop the next onDisposed handler from running");
|
||||
try {
|
||||
const connectionIdA = await acceptedA;
|
||||
// Random bytes on a reliable stream: not a valid encoded frame, so
|
||||
// decodeRealtimeFrame throws inside onStreamFrame, which native's
|
||||
// dispatch loop (fix 1's native half) catches and faults only this
|
||||
// connection. Bounded: if the fault path regresses and never faults
|
||||
// this connection, receiveFrame() would otherwise wait forever for a
|
||||
// frame that will not arrive.
|
||||
listener.sendRaw(connectionIdA, new Uint8Array([9, 9, 9, 9, 9, 9, 9, 9]), CULTMESH_QUIC_STREAM_RELIABLE);
|
||||
await assert.rejects(receiveFrameOrTimeout(transportA));
|
||||
assert.equal(secondHandlerRan, true, "a throwing handler must not stop the next onDisposed handler from running");
|
||||
} finally {
|
||||
transportA.dispose();
|
||||
}
|
||||
|
||||
// The process (and this test file's pump) survives, and a second,
|
||||
// independent connection through the same runtime still works.
|
||||
|
|
@ -792,27 +798,33 @@ test("fix 1: a throwing onDisposed handler on a peer shutdown still releases the
|
|||
const connector = new CultMeshQuicRealtimeConnector();
|
||||
const accepted = listener.acceptOnce();
|
||||
const transport = await connector.connect(candidate, target);
|
||||
const connectionId = await accepted;
|
||||
try {
|
||||
const connectionId = await accepted;
|
||||
|
||||
let secondHandlerRan = false;
|
||||
transport.onDisposed?.(() => {
|
||||
throw new Error("first onDisposed handler throws on purpose");
|
||||
});
|
||||
transport.onDisposed?.(() => {
|
||||
secondHandlerRan = true;
|
||||
});
|
||||
let secondHandlerRan = false;
|
||||
transport.onDisposed?.(() => {
|
||||
throw new Error("first onDisposed handler throws on purpose");
|
||||
});
|
||||
transport.onDisposed?.(() => {
|
||||
secondHandlerRan = true;
|
||||
});
|
||||
|
||||
const before = CultMeshQuicNativeRuntime.refCount;
|
||||
listener.shutdownConnection(connectionId);
|
||||
// Wait for cleanup() to run (driven by the pump's CONNECTION_SHUTDOWN
|
||||
// dispatch), rather than asserting immediately.
|
||||
await assert.rejects(transport.receiveFrame());
|
||||
assert.equal(
|
||||
CultMeshQuicNativeRuntime.refCount,
|
||||
before - 1,
|
||||
"the runtime reference must be released even though a handler threw",
|
||||
);
|
||||
assert.equal(secondHandlerRan, true, "every onDisposed handler must still run despite an earlier one throwing");
|
||||
const before = CultMeshQuicNativeRuntime.refCount;
|
||||
listener.shutdownConnection(connectionId);
|
||||
// Wait for cleanup() to run (driven by the pump's CONNECTION_SHUTDOWN
|
||||
// dispatch), rather than asserting immediately. Bounded: if the
|
||||
// shutdown-cleanup path regresses (F3) and never rejects, this would
|
||||
// otherwise hang instead of failing the assertion below it.
|
||||
await assert.rejects(receiveFrameOrTimeout(transport));
|
||||
assert.equal(
|
||||
CultMeshQuicNativeRuntime.refCount,
|
||||
before - 1,
|
||||
"the runtime reference must be released even though a handler threw",
|
||||
);
|
||||
assert.equal(secondHandlerRan, true, "every onDisposed handler must still run despite an earlier one throwing");
|
||||
} finally {
|
||||
transport.dispose();
|
||||
}
|
||||
} finally {
|
||||
await listener.close();
|
||||
}
|
||||
|
|
@ -896,17 +908,24 @@ test("fix 3: a peer-initiated shutdown rejects a pending receive and cleans up e
|
|||
const connector = new CultMeshQuicRealtimeConnector();
|
||||
const accepted = listener.acceptOnce();
|
||||
const transport = await connector.connect(candidate, target);
|
||||
const connectionId = await accepted;
|
||||
try {
|
||||
const connectionId = await accepted;
|
||||
|
||||
const pendingReceive = transport.receiveFrame();
|
||||
listener.shutdownConnection(connectionId);
|
||||
await assert.rejects(pendingReceive, /closed by the remote peer/i);
|
||||
// Bounded: this is F3's own mutant target (removing the
|
||||
// CONNECTION_SHUTDOWN cleanup call), and a broken cleanup path means
|
||||
// this receive never settles.
|
||||
const pendingReceive = receiveFrameOrTimeout(transport);
|
||||
listener.shutdownConnection(connectionId);
|
||||
await assert.rejects(pendingReceive, /closed by the remote peer/i);
|
||||
|
||||
// Cleanup already ran: a further explicit dispose is a harmless no-op,
|
||||
// and a fresh receive rejects immediately instead of hanging forever
|
||||
// (the leak fix 3 exists for).
|
||||
transport.dispose();
|
||||
await assert.rejects(transport.receiveFrame(), /closed by the remote peer/i);
|
||||
// Cleanup already ran: a further explicit dispose is a harmless no-op,
|
||||
// and a fresh receive rejects immediately instead of hanging forever
|
||||
// (the leak fix 3 exists for).
|
||||
transport.dispose();
|
||||
await assert.rejects(receiveFrameOrTimeout(transport), /closed by the remote peer/i);
|
||||
} finally {
|
||||
transport.dispose();
|
||||
}
|
||||
} finally {
|
||||
await listener.close();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue