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>
227 lines
7.6 KiB
JavaScript
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()();
|