Bound sync iterable normalization in from() and fromSync() to FROM_BATCH_SIZE. This avoids unbounded batches for from() sync iterable fallbacks and lets fromSync() coalesce plain Uint8Array values for writev paths. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 PR-URL: https://github.com/nodejs/node/pull/63324 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
178 lines
5.3 KiB
JavaScript
178 lines
5.3 KiB
JavaScript
// Flags: --experimental-stream-iter
|
|
'use strict';
|
|
|
|
// Coverage tests for from.js: sub-batching >128, DataView in generator,
|
|
// non-Uint8Array TypedArray normalization.
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const {
|
|
from,
|
|
fromSync,
|
|
bytes,
|
|
bytesSync,
|
|
} = require('stream/iter');
|
|
|
|
// fromSync: Uint8Array[] with > 128 elements triggers sub-batching
|
|
async function testFromSyncSubBatching() {
|
|
const bigBatch = Array.from({ length: 200 },
|
|
(_, i) => new Uint8Array([i & 0xFF]));
|
|
const batches = [];
|
|
for (const batch of fromSync(bigBatch)) {
|
|
batches.push(batch);
|
|
}
|
|
// Should be split into sub-batches: 128 + 72
|
|
assert.strictEqual(batches.length, 2);
|
|
assert.strictEqual(batches[0].length, 128);
|
|
assert.strictEqual(batches[1].length, 72);
|
|
// Verify no data loss
|
|
let totalChunks = 0;
|
|
for (const batch of batches) totalChunks += batch.length;
|
|
assert.strictEqual(totalChunks, 200);
|
|
}
|
|
|
|
// fromSync: generic sync iterables of Uint8Array use bounded batches
|
|
async function testFromSyncIterableSubBatching() {
|
|
function* gen() {
|
|
for (let i = 0; i < 200; i++) {
|
|
yield new Uint8Array([i & 0xFF]);
|
|
}
|
|
}
|
|
const batches = [];
|
|
for (const batch of fromSync(gen())) {
|
|
batches.push(batch);
|
|
}
|
|
assert.strictEqual(batches.length, 2);
|
|
assert.strictEqual(batches[0].length, 128);
|
|
assert.strictEqual(batches[1].length, 72);
|
|
}
|
|
|
|
// from: Uint8Array[] with > 128 elements triggers sub-batching (async)
|
|
async function testFromAsyncSubBatching() {
|
|
const bigBatch = Array.from({ length: 200 },
|
|
(_, i) => new Uint8Array([i & 0xFF]));
|
|
const batches = [];
|
|
for await (const batch of from(bigBatch)) {
|
|
batches.push(batch);
|
|
}
|
|
assert.strictEqual(batches.length, 2);
|
|
assert.strictEqual(batches[0].length, 128);
|
|
assert.strictEqual(batches[1].length, 72);
|
|
}
|
|
|
|
// from: sync iterables use bounded batches instead of one unbounded batch
|
|
async function testFromAsyncSyncIterableSubBatching() {
|
|
function* gen() {
|
|
for (let i = 0; i < 200; i++) {
|
|
yield new Uint8Array([i & 0xFF]);
|
|
}
|
|
}
|
|
const batches = [];
|
|
for await (const batch of from(gen())) {
|
|
batches.push(batch);
|
|
}
|
|
assert.strictEqual(batches.length, 2);
|
|
assert.strictEqual(batches[0].length, 128);
|
|
assert.strictEqual(batches[1].length, 72);
|
|
}
|
|
|
|
// Exact boundary: 128 elements → single batch (no split)
|
|
async function testFromSubBatchingBoundary() {
|
|
const exactBatch = Array.from({ length: 128 },
|
|
(_, i) => new Uint8Array([i]));
|
|
const batches = [];
|
|
for (const batch of fromSync(exactBatch)) {
|
|
batches.push(batch);
|
|
}
|
|
assert.strictEqual(batches.length, 1);
|
|
assert.strictEqual(batches[0].length, 128);
|
|
}
|
|
|
|
// 129 elements → 2 batches (128 + 1)
|
|
async function testFromSubBatchingBoundaryPlus1() {
|
|
const batch129 = Array.from({ length: 129 },
|
|
(_, i) => new Uint8Array([i & 0xFF]));
|
|
const batches = [];
|
|
for await (const batch of from(batch129)) {
|
|
batches.push(batch);
|
|
}
|
|
assert.strictEqual(batches.length, 2);
|
|
assert.strictEqual(batches[0].length, 128);
|
|
assert.strictEqual(batches[1].length, 1);
|
|
}
|
|
|
|
// DataView yielded from a sync generator → normalizeSyncValue path
|
|
async function testFromSyncDataViewInGenerator() {
|
|
function* gen() {
|
|
const buf = new ArrayBuffer(3);
|
|
const dv = new DataView(buf);
|
|
dv.setUint8(0, 65);
|
|
dv.setUint8(1, 66);
|
|
dv.setUint8(2, 67);
|
|
yield dv;
|
|
}
|
|
const data = bytesSync(fromSync(gen()));
|
|
assert.deepStrictEqual(data, new Uint8Array([65, 66, 67]));
|
|
}
|
|
|
|
// DataView yielded from an async generator → normalizeAsyncValue path
|
|
async function testFromAsyncDataViewInGenerator() {
|
|
async function* gen() {
|
|
const buf = new ArrayBuffer(3);
|
|
const dv = new DataView(buf);
|
|
dv.setUint8(0, 68);
|
|
dv.setUint8(1, 69);
|
|
dv.setUint8(2, 70);
|
|
yield dv;
|
|
}
|
|
const data = await bytes(from(gen()));
|
|
assert.deepStrictEqual(data, new Uint8Array([68, 69, 70]));
|
|
}
|
|
|
|
// Int16Array yielded from generator → primitiveToUint8Array fallback
|
|
async function testFromSyncInt16ArrayInGenerator() {
|
|
function* gen() {
|
|
yield new Int16Array([0x0102, 0x0304]);
|
|
}
|
|
const data = bytesSync(fromSync(gen()));
|
|
assert.strictEqual(data.byteLength, 4); // 2 int16 = 4 bytes
|
|
}
|
|
|
|
// Float64Array as top-level input to from()
|
|
async function testFromFloat64Array() {
|
|
const f64 = new Float64Array([1.0]);
|
|
const batches = [];
|
|
for await (const batch of from(f64)) {
|
|
batches.push(batch);
|
|
}
|
|
assert.strictEqual(batches.length, 1);
|
|
assert.strictEqual(batches[0][0].byteLength, 8); // 1 float64 = 8 bytes
|
|
}
|
|
|
|
// Sync generator yielding invalid type → ERR_INVALID_ARG_TYPE
|
|
async function testFromSyncInvalidYield() {
|
|
function* gen() {
|
|
yield 42; // Not a valid stream value
|
|
}
|
|
assert.throws(
|
|
() => {
|
|
// eslint-disable-next-line no-unused-vars
|
|
for (const batch of fromSync(gen())) { /* consume */ }
|
|
},
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
}
|
|
|
|
Promise.all([
|
|
testFromSyncSubBatching(),
|
|
testFromSyncIterableSubBatching(),
|
|
testFromAsyncSubBatching(),
|
|
testFromAsyncSyncIterableSubBatching(),
|
|
testFromSubBatchingBoundary(),
|
|
testFromSubBatchingBoundaryPlus1(),
|
|
testFromSyncDataViewInGenerator(),
|
|
testFromAsyncDataViewInGenerator(),
|
|
testFromSyncInt16ArrayInGenerator(),
|
|
testFromFloat64Array(),
|
|
testFromSyncInvalidYield(),
|
|
]).then(common.mustCall());
|