mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus PR-URL: https://github.com/nodejs/node/pull/64464 Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
256 lines
7.7 KiB
JavaScript
256 lines
7.7 KiB
JavaScript
// Flags: --experimental-stream-iter
|
|
'use strict';
|
|
|
|
// Tests for pipeTo writev/writevSync paths and writeBatchAsyncFallback.
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const { setImmediate: setImmediatePromise } = require('timers/promises');
|
|
const { pipeTo, pipeToSync, push, text } = require('stream/iter');
|
|
|
|
// Multi-chunk batch with writevSync (sync success path)
|
|
async function testWritevSyncSuccess() {
|
|
const batches = [];
|
|
const writer = {
|
|
write(chunk) {},
|
|
writevSync(chunks) { batches.push(chunks); return true; },
|
|
writev(chunks) { batches.push(chunks); },
|
|
writeSync(chunk) { return true; },
|
|
endSync() { return 0; },
|
|
};
|
|
// Source that yields multi-chunk batches
|
|
async function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])];
|
|
yield [new Uint8Array([4]), new Uint8Array([5])];
|
|
}
|
|
const total = await pipeTo(source(), writer);
|
|
assert.ok(batches.length > 0);
|
|
// writevSync was used for multi-chunk batches
|
|
assert.ok(batches.some((b) => b.length > 1));
|
|
assert.strictEqual(total, 5);
|
|
}
|
|
|
|
// Multi-chunk batch with writev async (no writevSync)
|
|
async function testWritevAsyncFallback() {
|
|
const batches = [];
|
|
const writer = {
|
|
async writev(chunks) { batches.push(chunks); },
|
|
async write(chunk) { batches.push([chunk]); },
|
|
async end() {},
|
|
};
|
|
async function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])];
|
|
}
|
|
await pipeTo(source(), writer);
|
|
assert.ok(batches.length > 0);
|
|
assert.ok(batches.some((b) => b.length > 1));
|
|
}
|
|
|
|
// Multi-chunk batch with synchronous writev success (returns undefined)
|
|
async function testWritevSyncUndefinedSuccess() {
|
|
const chunks = [];
|
|
const writer = {
|
|
write(chunk) {
|
|
chunks.push(chunk);
|
|
},
|
|
writev(batch) {
|
|
chunks.push(...batch);
|
|
},
|
|
end() {},
|
|
};
|
|
async function* source() {
|
|
yield [new Uint8Array([65]), new Uint8Array([66])];
|
|
}
|
|
const total = await pipeTo(source(), writer);
|
|
assert.strictEqual(total, 2);
|
|
assert.strictEqual(Buffer.concat(chunks).toString(), 'AB');
|
|
}
|
|
|
|
// writevSync returns false — falls through to async writev
|
|
async function testWritevSyncFails() {
|
|
const asyncCalls = [];
|
|
const writer = {
|
|
write() {},
|
|
writevSync() { return false; },
|
|
async writev(chunks) { asyncCalls.push(chunks); },
|
|
writeSync() { return true; },
|
|
endSync() { return 0; },
|
|
};
|
|
async function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2])];
|
|
}
|
|
await pipeTo(source(), writer);
|
|
assert.strictEqual(asyncCalls.length, 1);
|
|
assert.strictEqual(asyncCalls[0].length, 2);
|
|
}
|
|
|
|
// writeSync fails mid-batch — triggers writeBatchAsyncFallback
|
|
async function testWriteSyncFailsMidBatch() {
|
|
const asyncWrites = [];
|
|
const writer = {
|
|
writeSync(chunk) {
|
|
// Fail for chunk value 2 — always, including retries
|
|
if (chunk[0] === 2) return false;
|
|
return true;
|
|
},
|
|
async write(chunk) { asyncWrites.push(chunk); },
|
|
async end() {},
|
|
};
|
|
// Single batch with 3 chunks
|
|
async function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])];
|
|
}
|
|
const total = await pipeTo(source(), writer);
|
|
// Chunk 1: writeSync succeeds
|
|
// Chunk 2: writeSync fails → writeBatchAsyncFallback → write() called
|
|
// Chunk 3: writeBatchAsyncFallback retries writeSync → succeeds
|
|
assert.ok(asyncWrites.length >= 1);
|
|
assert.deepStrictEqual(asyncWrites[0], new Uint8Array([2]));
|
|
assert.strictEqual(total, 3);
|
|
}
|
|
|
|
// writeSync always fails — all chunks go through async
|
|
async function testWriteSyncAlwaysFails() {
|
|
const asyncWrites = [];
|
|
const writer = {
|
|
writeSync() { return false; },
|
|
async write(chunk) { asyncWrites.push(chunk); },
|
|
async end() {},
|
|
};
|
|
async function* source() {
|
|
yield [new Uint8Array([10]), new Uint8Array([20])];
|
|
}
|
|
const total = await pipeTo(source(), writer);
|
|
assert.strictEqual(asyncWrites.length, 2);
|
|
assert.strictEqual(total, 2);
|
|
}
|
|
|
|
// PushWriter block mode accepts sync writes even when returning false for
|
|
// backpressure. pipeTo must wait for drain, not retry the same write.
|
|
async function assertPushWriterBlockPipeTo(source, expected, expectedTotal) {
|
|
const { writer, readable } = push({
|
|
budget: 16384,
|
|
backpressure: 'unbounded',
|
|
});
|
|
|
|
const pipe = pipeTo(source, writer);
|
|
await setImmediatePromise();
|
|
const data = await text(readable);
|
|
const total = await pipe;
|
|
|
|
assert.strictEqual(data, expected);
|
|
assert.strictEqual(total, expectedTotal);
|
|
}
|
|
|
|
async function testPushWriterBlockSyncFalseAccepted() {
|
|
await assertPushWriterBlockPipeTo((async function*() {
|
|
yield [new Uint8Array([97])];
|
|
yield [new Uint8Array([98])];
|
|
})(), 'ab', 2);
|
|
|
|
await assertPushWriterBlockPipeTo((async function*() {
|
|
yield [new Uint8Array([97, 98])];
|
|
yield [new Uint8Array([99]), new Uint8Array([100])];
|
|
})(), 'abcd', 4);
|
|
}
|
|
|
|
async function testPipeToSyncPushWriterStrictFalseRejected() {
|
|
const kChunk = new Uint8Array(16384);
|
|
const { writer } = push({ budget: 16384 });
|
|
|
|
// Pre-fill the buffer so it's at capacity
|
|
writer.writeSync(kChunk);
|
|
|
|
// pipeToSync should throw when writeSync returns false (budget exhausted)
|
|
assert.throws(
|
|
() => pipeToSync([kChunk], writer,
|
|
{ preventClose: true, preventFail: true }),
|
|
{ code: 'ERR_OUT_OF_RANGE' },
|
|
);
|
|
}
|
|
|
|
async function testPipeToSyncWritevFalseNotCounted() {
|
|
const writer = {
|
|
writevSync() { return false; },
|
|
writeSync: common.mustNotCall(),
|
|
endSync() { return 0; },
|
|
};
|
|
function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2])];
|
|
}
|
|
|
|
// pipeToSync throws when writevSync returns false (budget exhausted)
|
|
assert.throws(
|
|
() => pipeToSync(source(), writer),
|
|
{ code: 'ERR_OUT_OF_RANGE' },
|
|
);
|
|
}
|
|
|
|
// pipeToSync with writevSync
|
|
async function testPipeToSyncWritev() {
|
|
const batches = [];
|
|
const writer = {
|
|
writevSync(chunks) { batches.push(chunks); },
|
|
writeSync(chunk) { return true; },
|
|
endSync() { return 0; },
|
|
};
|
|
function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])];
|
|
yield [new Uint8Array([4])];
|
|
}
|
|
pipeToSync(source(), writer);
|
|
// Multi-chunk batch should have used writevSync
|
|
assert.ok(batches.some((b) => b.length > 1));
|
|
}
|
|
|
|
// pipeToSync batches plain Uint8Array chunks for writevSync
|
|
async function testPipeToSyncPlainChunksWritev() {
|
|
const batches = [];
|
|
const writes = [];
|
|
const writer = {
|
|
writevSync(chunks) { batches.push(chunks); },
|
|
writeSync(chunk) { writes.push(chunk); return true; },
|
|
endSync() { return 0; },
|
|
};
|
|
function* source() {
|
|
yield new Uint8Array([1]);
|
|
yield new Uint8Array([2]);
|
|
yield new Uint8Array([3]);
|
|
}
|
|
const total = pipeToSync(source(), writer);
|
|
assert.strictEqual(total, 3);
|
|
assert.strictEqual(batches.length, 1);
|
|
assert.strictEqual(batches[0].length, 3);
|
|
assert.strictEqual(writes.length, 0);
|
|
}
|
|
|
|
// pipeToSync with writer that has write() and writeSync() — writeSync preferred
|
|
async function testPipeToSyncWriteFallback() {
|
|
const syncWrites = [];
|
|
const writer = {
|
|
writeSync(chunk) { syncWrites.push(chunk); return true; },
|
|
write(chunk) { /* should not be called */ },
|
|
endSync() { return 0; },
|
|
};
|
|
function* source() {
|
|
yield [new Uint8Array([1]), new Uint8Array([2])];
|
|
}
|
|
pipeToSync(source(), writer);
|
|
assert.strictEqual(syncWrites.length, 2);
|
|
}
|
|
|
|
Promise.all([
|
|
testWritevSyncSuccess(),
|
|
testWritevAsyncFallback(),
|
|
testWritevSyncUndefinedSuccess(),
|
|
testWritevSyncFails(),
|
|
testWriteSyncFailsMidBatch(),
|
|
testWriteSyncAlwaysFails(),
|
|
testPushWriterBlockSyncFalseAccepted(),
|
|
testPipeToSyncPushWriterStrictFalseRejected(),
|
|
testPipeToSyncWritevFalseNotCounted(),
|
|
testPipeToSyncWritev(),
|
|
testPipeToSyncPlainChunksWritev(),
|
|
testPipeToSyncWriteFallback(),
|
|
]).then(common.mustCall());
|