Files
node/test/parallel/test-stream-iter-duplex.js
Ethan Arrowood c3189d436e test: replace discard loops with dump()
Replaces the `for await (const _ of source) {}` discard idiom with
dump(), which says what it does and does not need an eslint-disable
for the unused loop variable. The eslint-disable directives and
comments that become redundant are removed with it.

Only files that already declare --experimental-stream-iter are
converted, so no test gains an experimental flag it did not already
opt into. Files still using the old idiom are left as they are;
converting them would change what those tests run under and belongs
in a separate discussion.

Loops that break early are left alone: those cancel the source rather
than reading it to completion, which is not what dump() does.

Assisted-by: Claude Opus 5
Signed-off-by: Ethan Arrowood <ethan@arrowood.dev>
PR-URL: https://github.com/nodejs/node/pull/65598
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
2026-10-07 20:01:31 +00:00

251 lines
6.9 KiB
JavaScript

// Flags: --experimental-stream-iter
'use strict';
const common = require('../common');
const assert = require('assert');
const { bytes, dump, duplex, text } = require('stream/iter');
const { setImmediate } = require('timers/promises');
// =============================================================================
// Basic duplex
// =============================================================================
async function testBasicDuplex() {
const [channelA, channelB] = duplex();
// A writes, B reads
await channelA.writer.write('hello from A');
const closing = channelA.close();
const dataAtB = await text(channelB.readable);
await closing;
assert.strictEqual(dataAtB, 'hello from A');
}
async function testBidirectional() {
const [channelA, channelB] = duplex();
await channelA.writer.write('A to B');
await channelB.writer.write('B to A');
const endA = channelA.writer.end();
const endB = channelB.writer.end();
const [dataAtA, dataAtB] = await Promise.all([
text(channelA.readable),
text(channelB.readable),
]);
await Promise.all([endA, endB]);
await Promise.all([channelA.close(), channelB.close()]);
assert.strictEqual(dataAtB, 'A to B');
assert.strictEqual(dataAtA, 'B to A');
}
async function testMultipleWrites() {
const [channelA, channelB] = duplex({ budget: 16384 });
await channelA.writer.write('one');
await channelA.writer.write('two');
await channelA.writer.write('three');
const closing = channelA.close();
const data = await text(channelB.readable);
await closing;
assert.strictEqual(data, 'onetwothree');
}
async function testChannelClose() {
const [channelA, channelB] = duplex();
const iteratorA = channelA.readable[Symbol.asyncIterator]();
const otherIteratorA = channelA.readable[Symbol.asyncIterator]();
const pendingRead = iteratorA.next();
const closing = channelA.close();
assert.strictEqual(channelA.close(), closing);
await closing;
assert.strictEqual((await pendingRead).done, true);
assert.strictEqual((await otherIteratorA.next()).done, true);
assert.strictEqual(
(await channelA.readable[Symbol.asyncIterator]().next()).done, true);
await assert.rejects(channelB.writer.write('late'), {
code: 'ERR_INVALID_STATE',
});
// B's readable should end (A -> B direction is closed)
const batches = [];
for await (const batch of channelB.readable) {
batches.push(batch);
}
assert.strictEqual(batches.length, 0);
}
async function testConcurrentChannelClose() {
const [channelA, channelB] = duplex();
const results = await Promise.allSettled([
channelA.close(),
channelB.close(),
]);
assert.deepStrictEqual(results, [
{ status: 'fulfilled', value: undefined },
{ status: 'fulfilled', value: undefined },
]);
}
async function testWithOptions() {
const [channelA, channelB] = duplex({
budget: 16384,
backpressure: 'strict',
});
await channelA.writer.write('msg');
const closing = channelA.close();
const data = await text(channelB.readable);
await closing;
assert.strictEqual(data, 'msg');
}
async function testPerChannelOptions() {
const [channelA, channelB] = duplex({
a: { budget: 16384 },
b: { budget: 16384 },
});
// Channel A -> B direction uses A's options
// Channel B -> A direction uses B's options
await channelA.writer.write('from-a');
await channelB.writer.write('from-b');
const endA = channelA.writer.end();
const endB = channelB.writer.end();
const [dataAtA, dataAtB] = await Promise.all([
text(channelA.readable),
text(channelB.readable),
]);
await Promise.all([endA, endB]);
await Promise.all([channelA.close(), channelB.close()]);
assert.strictEqual(dataAtB, 'from-a');
assert.strictEqual(dataAtA, 'from-b');
}
async function testAbortSignal() {
const ac = new AbortController();
const [channelA] = duplex({ signal: ac.signal });
ac.abort();
// Both directions should error
await assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars
for await (const _ of channelA.readable) {
assert.fail('Should not reach here');
}
},
(err) => err.name === 'AbortError',
);
}
async function testWriterEndWithPreAbortedSignal() {
const [channelA, channelB] = duplex();
const reason = new Error('end aborted');
await assert.rejects(
channelA.writer.end({ signal: AbortSignal.abort(reason) }),
(error) => error === reason,
);
await channelA.writer.write('still open');
const completedEnd = channelA.writer.end();
assert.strictEqual(await text(channelB.readable), 'still open');
assert.strictEqual(await completedEnd, 10);
await channelB.close();
}
async function testEmptyDuplex() {
const [channelA, channelB] = duplex();
await channelA.writer.end();
await channelB.writer.end();
const dataAtA = await bytes(channelA.readable);
const dataAtB = await bytes(channelB.readable);
await Promise.all([channelA.close(), channelB.close()]);
assert.strictEqual(dataAtA.byteLength, 0);
assert.strictEqual(dataAtB.byteLength, 0);
}
async function testCloseWaitsForDrain() {
const [channelA, channelB] = duplex();
await channelA.writer.write('buffered');
let closed = false;
const closing = channelA.close().then(common.mustCall(() => {
closed = true;
}));
await setImmediate();
assert.strictEqual(closed, false);
assert.strictEqual(await text(channelB.readable), 'buffered');
await closing;
}
async function testClosePropagatesWriterFailure() {
const [channelA] = duplex();
const reason = new Error('writer failed');
channelA.writer.fail(reason);
await assert.rejects(channelA.close(), (error) => error === reason);
}
// Channel fail propagation
async function testChannelFail() {
const [a, b] = duplex();
a.writer.fail(new Error('channel failed'));
await assert.rejects(async () => {
await dump(b.readable);
}, { message: 'channel failed' });
await b.close();
}
// Abort signal affects both channels
async function testAbortSignalBothChannels() {
const ac = new AbortController();
const [channelA, channelB] = duplex({ signal: ac.signal });
ac.abort();
await assert.rejects(async () => {
// eslint-disable-next-line no-unused-vars
for await (const _ of channelA.readable) {
assert.fail('Should not reach here');
}
}, (err) => err.name === 'AbortError');
await assert.rejects(async () => {
// eslint-disable-next-line no-unused-vars
for await (const _ of channelB.readable) {
assert.fail('Should not reach here');
}
}, (err) => err.name === 'AbortError');
}
Promise.all([
testBasicDuplex(),
testBidirectional(),
testMultipleWrites(),
testChannelClose(),
testConcurrentChannelClose(),
testWithOptions(),
testPerChannelOptions(),
testAbortSignal(),
testWriterEndWithPreAbortedSignal(),
testEmptyDuplex(),
testCloseWaitsForDrain(),
testClosePropagatesWriterFailure(),
testChannelFail(),
testAbortSignalBothChannels(),
]).then(common.mustCall());