| // Flags: --experimental-stream-iter |
| 'use strict'; |
| |
| // Coverage tests for transform.js: abort cleanup, strategy/params options, |
| // early consumer exit triggering finally block. |
| |
| const common = require('../common'); |
| const assert = require('assert'); |
| const { |
| from, |
| pull, |
| bytes, |
| text, |
| } = require('stream/iter'); |
| const { |
| compressGzip, |
| decompressGzip, |
| compressDeflate, |
| decompressDeflate, |
| compressBrotli, |
| decompressBrotli, |
| compressZstd, |
| decompressZstd, |
| } = require('zlib/iter'); |
| const { constants } = require('zlib'); |
| |
| async function roundTrip(input, compress, decompress) { |
| return text(pull(from(input), compress, decompress)); |
| } |
| |
| // Abort mid-compression triggers finally cleanup |
| async function testAbortMidCompression() { |
| const ac = new AbortController(); |
| const largeInput = 'x'.repeat(100_000); |
| const compressed = pull(from(largeInput), compressGzip(), |
| { signal: ac.signal }); |
| const iter = compressed[Symbol.asyncIterator](); |
| |
| // Read one batch then abort |
| const first = await iter.next(); |
| assert.strictEqual(first.done, false); |
| ac.abort(); |
| await assert.rejects(iter.next(), { name: 'AbortError' }); |
| } |
| |
| // Early consumer exit (break from for-await) triggers finally |
| async function testEarlyConsumerExit() { |
| const largeInput = 'y'.repeat(100_000); |
| const compressed = pull(from(largeInput), compressGzip()); |
| |
| // eslint-disable-next-line no-unused-vars |
| for await (const batch of compressed) { |
| break; // Early exit — should trigger finally block cleanup |
| } |
| // If we get here without hanging or crashing, cleanup worked |
| } |
| |
| // Gzip with explicit strategy option |
| async function testGzipWithStrategy() { |
| const input = 'strategy test data '.repeat(100); |
| const c = compressGzip({ strategy: constants.Z_DEFAULT_STRATEGY }); |
| const result = await roundTrip(input, c, decompressGzip()); |
| assert.strictEqual(result, input); |
| } |
| |
| // Deflate with Z_FIXED strategy |
| async function testDeflateWithFixedStrategy() { |
| const input = 'fixed strategy '.repeat(100); |
| const c = compressDeflate({ strategy: constants.Z_FIXED }); |
| const result = await roundTrip(input, c, decompressDeflate()); |
| assert.strictEqual(result, input); |
| } |
| |
| // Brotli with custom quality param |
| async function testBrotliWithParams() { |
| const input = 'brotli params test '.repeat(100); |
| const params = { [constants.BROTLI_PARAM_QUALITY]: 5 }; |
| const result = await roundTrip(input, compressBrotli({ params }), |
| decompressBrotli()); |
| assert.strictEqual(result, input); |
| } |
| |
| // Zstd with custom compression level param |
| async function testZstdWithParams() { |
| const input = 'zstd params test '.repeat(100); |
| const params = { [constants.ZSTD_c_compressionLevel]: 10 }; |
| const result = await roundTrip(input, compressZstd({ params }), |
| decompressZstd()); |
| assert.strictEqual(result, input); |
| } |
| |
| // Gzip with custom chunkSize |
| async function testGzipWithChunkSize() { |
| const input = 'chunk size test'; |
| const c = compressGzip({ chunkSize: 256 }); |
| const d = decompressGzip({ chunkSize: 256 }); |
| const result = await roundTrip(input, c, d); |
| assert.strictEqual(result, input); |
| } |
| |
| // Invalid chunkSize throws when transform is invoked |
| async function testInvalidChunkSize() { |
| const tx = compressGzip({ chunkSize: 8 }); |
| await assert.rejects( |
| async () => await bytes(pull(from('data'), tx)), |
| { code: 'ERR_OUT_OF_RANGE' }, |
| ); |
| } |
| |
| Promise.all([ |
| testAbortMidCompression(), |
| testEarlyConsumerExit(), |
| testGzipWithStrategy(), |
| testDeflateWithFixedStrategy(), |
| testBrotliWithParams(), |
| testZstdWithParams(), |
| testGzipWithChunkSize(), |
| testInvalidChunkSize(), |
| ]).then(common.mustCall()); |