Files
node/test/parallel/test-stream-iter-pipeto.js
James M Snell 2964712265 test: avoid using promise wrapped timers
Just say no to `new Promise(() => timer(...))` use
promisified timers

Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66079
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-09-24 23:06:21 +00:00

370 lines
9.7 KiB
JavaScript

// Flags: --experimental-stream-iter
'use strict';
const common = require('../common');
const assert = require('assert');
const { pipeTo, pipeToSync, from, fromSync } = require('stream/iter');
const { setTimeout } = require('timers/promises');
async function testPipeToSync() {
const written = [];
const writer = {
writeSync(chunk) { written.push(chunk); return true; },
endSync() { return written.length; },
fail() {},
};
const totalBytes = pipeToSync(fromSync('pipe-data'), writer);
assert.strictEqual(totalBytes, 9); // 'pipe-data' = 9 UTF-8 bytes
assert.ok(written.length > 0);
const result = new TextDecoder().decode(
new Uint8Array(written.reduce((acc, c) => [...acc, ...c], [])));
assert.strictEqual(result, 'pipe-data');
}
async function testPipeTo() {
const written = [];
const writer = {
async write(chunk) { written.push(chunk); },
async end() { return written.length; },
async fail() {},
};
const totalBytes = await pipeTo(from('async-pipe-data'), writer);
assert.strictEqual(totalBytes, 15); // 'async-pipe-data' = 15 UTF-8 bytes
assert.ok(written.length > 0);
}
async function testPipeToPreventClose() {
let endCalled = false;
const writer = {
async write() {},
async end() { endCalled = true; },
async fail() {},
};
await pipeTo(from('data'), writer, { preventClose: true });
assert.strictEqual(endCalled, false);
}
// PipeTo source error calls writer.fail()
async function testPipeToSourceError() {
let failCalled = false;
let failReason;
const writer = {
write() {},
fail(reason) { failCalled = true; failReason = reason; },
};
async function* failingSource() {
yield [new TextEncoder().encode('a')];
throw new Error('pipe source boom');
}
await assert.rejects(
() => pipeTo(failingSource(), writer),
{ message: 'pipe source boom' },
);
assert.strictEqual(failCalled, true);
assert.strictEqual(failReason.message, 'pipe source boom');
}
// PipeToSync source error calls writer.fail()
async function testPipeToSyncSourceError() {
let failCalled = false;
const writer = {
writeSync() { return true; },
endSync: common.mustNotCall(),
fail(reason) { failCalled = true; },
};
function* failingSource() {
yield [new TextEncoder().encode('a')];
throw new Error('sync pipe boom');
}
assert.throws(
() => pipeToSync(failingSource(), writer),
{ message: 'sync pipe boom' },
);
assert.strictEqual(failCalled, true);
}
// PipeTo with AbortSignal
async function testPipeToWithSignal() {
const ac = new AbortController();
const chunks = [];
const writer = {
write(chunk) { chunks.push(chunk); },
};
async function* slowSource() {
yield [new TextEncoder().encode('a')];
await setTimeout(50);
yield [new TextEncoder().encode('b')];
}
ac.abort();
await assert.rejects(
() => pipeTo(slowSource(), writer, { signal: ac.signal }),
{ name: 'AbortError' },
);
}
// PipeTo with transforms
async function testPipeToWithTransforms() {
const chunks = [];
const writer = {
write(chunk) { chunks.push(new TextDecoder().decode(chunk)); },
};
const upper = (batch) => {
if (batch === null) return null;
return batch.map((c) => {
const out = new Uint8Array(c);
for (let i = 0; i < out.length; i++)
out[i] -= (out[i] >= 97 && out[i] <= 122) * 32;
return out;
});
};
await pipeTo(from('hello'), upper, writer);
assert.strictEqual(chunks.join(''), 'HELLO');
}
// PipeToSync with transforms
async function testPipeToSyncWithTransforms() {
const chunks = [];
const writer = {
writeSync(chunk) { chunks.push(new TextDecoder().decode(chunk)); return true; },
endSync() { return 0; },
};
const upper = (batch) => {
if (batch === null) return null;
return batch.map((c) => {
const out = new Uint8Array(c);
for (let i = 0; i < out.length; i++)
out[i] -= (out[i] >= 97 && out[i] <= 122) * 32;
return out;
});
};
pipeToSync(fromSync('hello'), upper, writer);
assert.strictEqual(chunks.join(''), 'HELLO');
}
async function testPipeToWriterTransformMethodIgnored() {
const chunks = [];
const writer = {
transform: common.mustNotCall(),
write(chunk) { chunks.push(new TextDecoder().decode(chunk)); },
};
await pipeTo(from('hello'), writer);
assert.strictEqual(chunks.join(''), 'hello');
}
async function testPipeToSyncWriterTransformMethodIgnored() {
const chunks = [];
const writer = {
transform: common.mustNotCall(),
writeSync(chunk) {
chunks.push(new TextDecoder().decode(chunk));
return true;
},
endSync() { return 0; },
};
pipeToSync(fromSync('hello'), writer);
assert.strictEqual(chunks.join(''), 'hello');
}
// PipeTo with writev writer
async function testPipeToWithWritevWriter() {
const allChunks = [];
const writer = {
write(chunk) { allChunks.push(chunk); },
writev(chunks) { allChunks.push(...chunks); },
};
await pipeTo(from('hello world'), writer);
assert.strictEqual(allChunks.length > 0, true);
}
// PipeTo with writeSync/writevSync fallback
async function testPipeToSyncFallback() {
const chunks = [];
const writer = {
writeSync(chunk) { chunks.push(chunk); return true; },
write(chunk) { chunks.push(chunk); },
};
await pipeTo(from('hello'), writer);
assert.strictEqual(chunks.length > 0, true);
}
// PipeTo preventFail option
async function testPipeToPreventFail() {
let failCalled = false;
const writer = {
write() {},
fail() { failCalled = true; },
};
// eslint-disable-next-line require-yield
async function* failingSource() {
throw new Error('boom');
}
await assert.rejects(
() => pipeTo(failingSource(), writer, { preventFail: true }),
{ message: 'boom' },
);
assert.strictEqual(failCalled, false);
}
// PipeToSync preventClose option
async function testPipeToSyncPreventClose() {
let endCalled = false;
const writer = {
writeSync() { return true; },
endSync() { endCalled = true; return 0; },
};
pipeToSync(fromSync('hello'), writer, { preventClose: true });
assert.strictEqual(endCalled, false);
}
// Regression test: pipeTo should work with a minimal writer that only
// implements write(). end(), fail(), and all *Sync methods are optional.
async function testPipeToMinimalWriter() {
const chunks = [];
const minimalWriter = {
write(chunk) {
chunks.push(chunk);
},
};
await pipeTo(from('minimal'), minimalWriter);
assert.strictEqual(chunks.length > 0, true);
}
async function testPipeToSyncMinimalWriter() {
const chunks = [];
const minimalWriter = {
writeSync(chunk) {
chunks.push(chunk);
return true;
},
};
pipeToSync(fromSync('minimal-sync'), minimalWriter, { preventClose: true });
assert.strictEqual(chunks.length > 0, true);
}
async function testPipeToSyncIterableUsesFromBatching() {
let pulled = 0;
let firstWritePulled = 0;
const chunks = [];
function* source() {
for (let i = 0; i < 3; i++) {
pulled++;
yield new Uint8Array([0x61 + i]);
}
}
const writer = {
write: common.mustNotCall(),
writeSync(chunk) {
if (firstWritePulled === 0) {
firstWritePulled = pulled;
}
chunks.push(chunk);
return true;
},
};
const totalBytes = await pipeTo(source(), writer);
assert.strictEqual(totalBytes, 3);
assert.strictEqual(firstWritePulled, 3);
assert.deepStrictEqual(chunks, [
new Uint8Array([0x61]),
new Uint8Array([0x62]),
new Uint8Array([0x63]),
]);
}
async function testPipeToSourceNormalizationIndependentOfWriter() {
function source() {
return {
*[Symbol.iterator]() {
yield {
async *[Symbol.asyncIterator]() {
yield 'nested';
},
};
},
};
}
for (const hasWriteSync of [false, true]) {
const writer = { write: common.mustNotCall() };
if (hasWriteSync) writer.writeSync = common.mustNotCall();
await assert.rejects(
pipeTo(source(), writer, { preventClose: true, preventFail: true }),
{ code: 'ERR_INVALID_ARG_TYPE' },
);
}
}
async function testPipeToSyncIterableWriteFallback() {
const asyncWrites = [];
const writer = {
writeSync(chunk) {
return chunk[0] !== 0x62;
},
async write(chunk) {
asyncWrites.push(chunk);
},
};
function* source() {
yield new Uint8Array([0x61]);
yield new Uint8Array([0x62]);
yield new Uint8Array([0x63]);
}
const totalBytes = await pipeTo(source(), writer);
assert.strictEqual(totalBytes, 3);
assert.deepStrictEqual(asyncWrites, [new Uint8Array([0x62])]);
}
async function testPipeToSyncIterableAsyncValue() {
const chunks = [];
const writer = {
write: common.mustNotCall(),
writeSync(chunk) {
chunks.push(chunk);
return true;
},
};
function* source() {
yield Promise.resolve('a');
yield new Uint8Array([0x62]);
}
const totalBytes = await pipeTo(source(), writer);
assert.strictEqual(totalBytes, 2);
const result = new TextDecoder().decode(
new Uint8Array(chunks.reduce((acc, c) => [...acc, ...c], [])));
assert.strictEqual(result, 'ab');
}
Promise.all([
testPipeToSync(),
testPipeTo(),
testPipeToPreventClose(),
testPipeToSourceError(),
testPipeToSyncSourceError(),
testPipeToWithSignal(),
testPipeToWithTransforms(),
testPipeToSyncWithTransforms(),
testPipeToWriterTransformMethodIgnored(),
testPipeToSyncWriterTransformMethodIgnored(),
testPipeToWithWritevWriter(),
testPipeToSyncFallback(),
testPipeToPreventFail(),
testPipeToSyncPreventClose(),
testPipeToMinimalWriter(),
testPipeToSyncMinimalWriter(),
testPipeToSyncIterableUsesFromBatching(),
testPipeToSyncIterableWriteFallback(),
testPipeToSyncIterableAsyncValue(),
testPipeToSourceNormalizationIndependentOfWriter(),
]).then(common.mustCall());