blob: 7aa71062ab197bc4499de72e0013214bcfc16744 [file] [edit]
// Flags: --experimental-stream-iter
'use strict';
// Coverage tests for broadcast.js: signal abort on pending write,
// sync iterable from, ringbuffer grow.
const common = require('../common');
const assert = require('assert');
const {
broadcast,
Broadcast,
text,
} = require('stream/iter');
// Signal abort on pending write (covers wireBroadcastWriteSignal + removeAt)
async function testBroadcastWriteAbort() {
const { writer, broadcast: bc } = broadcast({
highWaterMark: 1,
backpressure: 'block',
});
const consumer = bc.push();
// Fill the buffer to capacity
writer.writeSync(new Uint8Array([1]));
// Next write will block — pass a signal
const ac = new AbortController();
const writePromise = writer.write(new Uint8Array([2]),
{ signal: ac.signal });
// Abort the signal
ac.abort();
await assert.rejects(writePromise, { name: 'AbortError' });
// Clean up
writer.endSync();
// Drain the consumer
const result = [];
for await (const batch of consumer) {
result.push(...batch);
}
assert.ok(result.length >= 1);
}
// Broadcast.from with sync iterable (generator)
async function testBroadcastFromSyncIterable() {
function* source() {
yield [new Uint8Array([10, 20])];
yield [new Uint8Array([30, 40])];
}
const { broadcast: bc } = Broadcast.from(source());
const consumer = bc.push();
// Just verify it completes without error and produces data
let count = 0;
for await (const batch of consumer) {
count += batch.length;
}
assert.ok(count > 0);
}
// Broadcast.from with sync iterable — string chunks
async function testBroadcastFromSyncIterableStrings() {
function* source() {
yield 'hello';
yield ' world';
}
const { broadcast: bc } = Broadcast.from(source());
const consumer = bc.push();
const result = await text(consumer);
assert.strictEqual(result, 'hello world');
}
// Ringbuffer grow — push > 16 items without consumer draining
async function testRingbufferGrow() {
const { writer, broadcast: bc } = broadcast({ highWaterMark: 32 });
const consumer = bc.push();
// Push 20 items (exceeds default ringbuffer capacity of 16)
for (let i = 0; i < 20; i++) {
writer.writeSync(new Uint8Array([i]));
}
writer.endSync();
// Read all items back and verify order
const items = [];
for await (const batch of consumer) {
for (const chunk of batch) {
items.push(chunk[0]);
}
}
assert.strictEqual(items.length, 20);
for (let i = 0; i < 20; i++) {
assert.strictEqual(items[i], i);
}
}
// Multiple consumers at the minimum cursor should trim only after the last
// one advances or detaches.
async function testFanOutMinCursorTrimming() {
const { writer, broadcast: bc } = broadcast({ highWaterMark: 4 });
const iter1 = bc.push()[Symbol.asyncIterator]();
const iter2 = bc.push()[Symbol.asyncIterator]();
writer.writeSync(new Uint8Array([1]));
writer.writeSync(new Uint8Array([2]));
assert.strictEqual(bc.bufferSize, 2);
assert.strictEqual((await iter1.next()).done, false);
assert.strictEqual(bc.bufferSize, 2);
assert.strictEqual((await iter2.next()).done, false);
assert.strictEqual(bc.bufferSize, 1);
await iter1.return();
assert.strictEqual(bc.bufferSize, 1);
assert.strictEqual((await iter2.next()).done, false);
assert.strictEqual(bc.bufferSize, 0);
writer.endSync();
assert.strictEqual((await iter2.next()).done, true);
}
// Broadcast drainableProtocol after close returns null
async function testDrainableAfterClose() {
const { drainableProtocol } = require('stream/iter');
const { writer } = broadcast();
writer.endSync();
const result = writer[drainableProtocol]();
// After close, desired should be null
assert.strictEqual(result, null);
}
Promise.all([
testBroadcastWriteAbort(),
testBroadcastFromSyncIterable(),
testBroadcastFromSyncIterableStrings(),
testRingbufferGrow(),
testFanOutMinCursorTrimming(),
testDrainableAfterClose(),
]).then(common.mustCall());