node/test/parallel/test-stream-iter-transform-sync.js
James M Snell 2fa9c1bc58 test: add tests for experimental stream/iter implementation
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-By: Claude/Opus 4.6
PR-URL: https://github.com/nodejs/node/pull/62066
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2026-03-27 19:55:17 -07:00

227 lines
7.6 KiB
JavaScript

// Flags: --experimental-stream-iter
'use strict';
const common = require('../common');
const assert = require('assert');
const {
fromSync,
pullSync,
bytesSync,
textSync,
} = require('stream/iter');
const {
compressGzipSync,
compressDeflateSync,
compressBrotliSync,
compressZstdSync,
decompressGzipSync,
decompressDeflateSync,
decompressBrotliSync,
decompressZstdSync,
} = require('zlib/iter');
// =============================================================================
// Helper: sync compress then decompress, verify round-trip equality
// =============================================================================
function roundTrip(input, compress, decompress) {
return textSync(pullSync(pullSync(fromSync(input), compress), decompress));
}
function roundTripBytes(inputBuf, compress, decompress) {
return bytesSync(pullSync(pullSync(fromSync(inputBuf), compress), decompress));
}
// =============================================================================
// Gzip sync round-trip tests
// =============================================================================
function testGzipRoundTrip() {
const input = 'Hello, sync gzip compression!';
const result = roundTrip(input, compressGzipSync(), decompressGzipSync());
assert.strictEqual(result, input);
}
function testGzipLargeData() {
const input = 'gzip sync large data test. '.repeat(5000);
const result = roundTrip(input, compressGzipSync(), decompressGzipSync());
assert.strictEqual(result, input);
}
function testGzipActuallyCompresses() {
const input = 'Repeated data compresses well. '.repeat(1000);
const inputBuf = Buffer.from(input);
const compressed = bytesSync(pullSync(fromSync(inputBuf),
compressGzipSync()));
assert.ok(compressed.byteLength < inputBuf.byteLength,
`Compressed ${compressed.byteLength} should be < ` +
`original ${inputBuf.byteLength}`);
}
function testGzipBinaryData() {
const inputBuf = Buffer.alloc(10000);
for (let i = 0; i < inputBuf.length; i++) inputBuf[i] = i & 0xff;
const result = roundTripBytes(inputBuf, compressGzipSync(),
decompressGzipSync());
assert.deepStrictEqual(result, inputBuf);
}
// =============================================================================
// Deflate sync round-trip tests
// =============================================================================
function testDeflateRoundTrip() {
const input = 'Hello, sync deflate compression!';
const result = roundTrip(input, compressDeflateSync(),
decompressDeflateSync());
assert.strictEqual(result, input);
}
function testDeflateLargeData() {
const input = 'deflate sync large data test. '.repeat(5000);
const result = roundTrip(input, compressDeflateSync(),
decompressDeflateSync());
assert.strictEqual(result, input);
}
// =============================================================================
// Brotli sync round-trip tests
// =============================================================================
function testBrotliRoundTrip() {
const input = 'Hello, sync brotli compression!';
const result = roundTrip(input, compressBrotliSync(),
decompressBrotliSync());
assert.strictEqual(result, input);
}
function testBrotliLargeData() {
const input = 'brotli sync large data test. '.repeat(5000);
const result = roundTrip(input, compressBrotliSync(),
decompressBrotliSync());
assert.strictEqual(result, input);
}
// =============================================================================
// Zstd sync round-trip tests
// =============================================================================
function testZstdRoundTrip() {
const input = 'Hello, sync zstd compression!';
const result = roundTrip(input, compressZstdSync(), decompressZstdSync());
assert.strictEqual(result, input);
}
function testZstdLargeData() {
const input = 'zstd sync large data test. '.repeat(5000);
const result = roundTrip(input, compressZstdSync(), decompressZstdSync());
assert.strictEqual(result, input);
}
// =============================================================================
// Cross-algorithm: compress async-compatible, decompress sync (and vice versa)
// The sync transforms should produce output compatible with the standard format
// =============================================================================
function testGzipWithOptions() {
const input = 'options test data '.repeat(100);
const result = roundTrip(input,
compressGzipSync({ level: 1 }),
decompressGzipSync());
assert.strictEqual(result, input);
}
function testBrotliWithOptions() {
const zlib = require('zlib');
const input = 'brotli options test data '.repeat(100);
const result = roundTrip(input,
compressBrotliSync({
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 3,
},
}),
decompressBrotliSync());
assert.strictEqual(result, input);
}
// =============================================================================
// Stateless + stateful sync transform pipeline
// =============================================================================
function testMixedStatelessAndStateful() {
// Uppercase stateless transform + gzip stateful transform
const upper = (chunks) => {
if (chunks === null) return null;
const out = new Array(chunks.length);
for (let j = 0; j < chunks.length; j++) {
const src = chunks[j];
const buf = Buffer.allocUnsafe(src.length);
for (let i = 0; i < src.length; i++) {
const b = src[i];
buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b;
}
out[j] = buf;
}
return out;
};
const input = 'hello world '.repeat(100);
const result = textSync(
pullSync(
pullSync(fromSync(input), upper, compressGzipSync()),
decompressGzipSync(),
),
);
assert.strictEqual(result, input.toUpperCase());
}
// =============================================================================
// Early consumer exit (break from for-of) triggers cleanup
// =============================================================================
function testEarlyExit() {
const input = 'y'.repeat(100_000);
const compressed = pullSync(fromSync(input), compressGzipSync());
// eslint-disable-next-line no-unused-vars
for (const batch of compressed) {
break; // Early exit - should trigger finally block cleanup
}
// If we get here without crashing, cleanup worked
}
// =============================================================================
// Empty input
// =============================================================================
function testEmptyInput() {
const result = textSync(
pullSync(
pullSync(fromSync(''), compressGzipSync()),
decompressGzipSync(),
),
);
assert.strictEqual(result, '');
}
// =============================================================================
// Run all tests
// =============================================================================
testGzipRoundTrip();
testGzipLargeData();
testGzipActuallyCompresses();
testGzipBinaryData();
testDeflateRoundTrip();
testDeflateLargeData();
testBrotliRoundTrip();
testBrotliLargeData();
testZstdRoundTrip();
testZstdLargeData();
testGzipWithOptions();
testBrotliWithOptions();
testMixedStatelessAndStateful();
testEarlyExit();
testEmptyInput();
common.mustCall()();