node/test/parallel/test-stream-iter-from-coverage.js
Trivikram Kamat 265679b5ac
stream: limit iter from sync iterable batches
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>
2026-05-17 08:04:30 +00:00

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());