/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/unit_node/zlib_test.ts
531 строка
16 KB
Bartek Iwańczuk
chore: bump node_compat test suite to Node.js 26.5.1 (#36391)
03 авг 2026, 12:52
Не верифицирован
03 авг 2026, 12:52
bca6182
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. import { assert, assertEquals, assertThrows } from "@std/assert"; import { fromFileUrl, relative } from "@std/path"; import { randomBytes } from "node:crypto"; import { BrotliCompress, brotliCompress, brotliCompressSync, BrotliDecompress, brotliDecompress, brotliDecompressSync, constants, crc32, createBrotliCompress, createBrotliDecompress, createDeflate, createGunzip, createGzip, createInflate, createZstdCompress, createZstdDecompress, deflateSync, gunzip, gzip, gzipSync, unzipSync, zstdCompressSync, } from "node:zlib"; import { Buffer } from "node:buffer"; import { createReadStream, createWriteStream } from "node:fs"; import { Readable } from "node:stream"; import { buffer } from "node:stream/consumers"; Deno.test("brotli compression sync", () => { const buf = Buffer.from("hello world"); const compressed = brotliCompressSync(buf); const decompressed = brotliDecompressSync(compressed); assertEquals(decompressed.toString(), "hello world"); }); Deno.test("brotli compression async", async () => { const buf = Buffer.from("hello world"); const compressed: Buffer = await new Promise((resolve) => brotliCompress(buf, (_, res) => { return resolve(res); }) ); assertEquals(compressed instanceof Buffer, true); const decompressed: Buffer = await new Promise((resolve) => brotliDecompress(compressed, (_, res) => { return resolve(res); }) ); assertEquals(decompressed.toString(), "hello world"); }); Deno.test("gzip compression sync", () => { const buf = Buffer.from("hello world"); const compressed = gzipSync(buf); const decompressed = unzipSync(compressed); assertEquals(decompressed.toString(), "hello world"); }); Deno.test("brotli compression", { ignore: true, }, async () => { const promise = Promise.withResolvers<void>(); const compress = createBrotliCompress(); const filePath = relative( Deno.cwd(), fromFileUrl(new URL("./testdata/lorem_ipsum.txt", import.meta.url)), ); const input = createReadStream(filePath); const output = createWriteStream("lorem_ipsum.txt.br"); const stream = input.pipe(compress).pipe(output); stream.on("finish", () => { const decompress = createBrotliDecompress(); const input2 = createReadStream("lorem_ipsum.txt.br"); const output2 = createWriteStream("lorem_ipsum.txt"); const stream2 = input2.pipe(decompress).pipe(output2); stream2.on("close", () => promise.resolve()); }); await Promise.all([ promise.promise, new Promise<void>((r) => stream.on("close", r)), ]); const content = Deno.readTextFileSync("lorem_ipsum.txt"); assert(content.startsWith("Lorem ipsum dolor sit amet"), content); try { Deno.removeSync("lorem_ipsum.txt.br"); } catch { // pass } try { Deno.removeSync("lorem_ipsum.txt"); } catch { // pass } }); Deno.test("brotli end-to-end with 4097 bytes", () => { const a = "a".repeat(4097); const compressed = brotliCompressSync(a); const decompressed = brotliDecompressSync(compressed); assertEquals(decompressed.toString(), a); }); Deno.test( "zlib create deflate with dictionary", async () => { const { promise, resolve } = Promise.withResolvers<void>(); const handle = createDeflate({ dictionary: Buffer.alloc(0), }); handle.on("close", () => resolve()); handle.end(); handle.destroy(); await promise; }, ); Deno.test( "zlib flush i32", function () { assertThrows(() => createDeflate({ // @ts-expect-error: passing non-int flush value flush: "", }), TypeError); }, ); Deno.test("should work with dataview", () => { const buf = Buffer.from("hello world"); const compressed = brotliCompressSync( new DataView(buf.buffer, buf.byteOffset, buf.byteLength), ); const decompressed = brotliDecompressSync(compressed); assertEquals(decompressed.toString(), "hello world"); }); Deno.test("should work with a buffer from an encoded string", () => { const encoder = new TextEncoder(); const buffer = encoder.encode("hello world"); const buf = Buffer.from(buffer); const compressed = brotliCompressSync(buf); const decompressed = brotliDecompressSync(compressed); assertEquals(decompressed.toString(), "hello world"); }); // https://github.com/denoland/deno/issues/24572 Deno.test("Brotli quality 10 doesn't panic", () => { const e = brotliCompressSync("abc", { params: { [constants.BROTLI_PARAM_QUALITY]: 10, }, }); assertEquals( new Uint8Array(e.buffer, e.byteOffset, e.byteLength), new Uint8Array([11, 1, 128, 97, 98, 99, 3]), ); }); Deno.test( "zlib compression with dataview", () => { const buf = Buffer.from("hello world"); const compressed = gzipSync( new DataView(buf.buffer, buf.byteOffset, buf.byteLength), ); const decompressed = unzipSync(compressed); assertEquals(decompressed.toString(), "hello world"); }, ); Deno.test("zlib compression with an encoded string", () => { const encoder = new TextEncoder(); const buffer = encoder.encode("hello world"); const compressed = gzipSync(buffer); const decompressed = unzipSync(compressed); assertEquals(decompressed.toString(), "hello world"); }); Deno.test("brotli large chunk size", async () => { const input = new Uint8Array(1000000); for (let i = 0; i < input.length; i++) { input[i] = Math.random() * 256; } const output = await buffer( Readable.from([input]) .pipe(createBrotliCompress()) .pipe(createBrotliDecompress()), ); assertEquals(output.length, input.length); }); Deno.test("brotli decompress flush restore size", async () => { const input = new Uint8Array(1000000); const output = await buffer( Readable.from([input]) .pipe(createBrotliCompress()) .pipe(createBrotliDecompress()), ); assertEquals(output.length, input.length); }); Deno.test("createBrotliCompress params", async () => { const compress = createBrotliCompress({ params: { [constants.BROTLI_PARAM_QUALITY]: 11, }, }); const input = new Uint8Array(10000); for (let i = 0; i < input.length; i++) { input[i] = Math.random() * 256; } const output = await buffer( Readable.from([input]) .pipe(compress) .pipe(createBrotliDecompress()), ); assertEquals(output.length, input.length); }); Deno.test("gzip() and gzipSync() accept ArrayBuffer", async () => { const deffered = Promise.withResolvers<void>(); const buf = new ArrayBuffer(0); let output: Buffer; gzip(buf, (_err, data) => { output = data; deffered.resolve(); }); await deffered.promise; assert(output! instanceof Buffer); const outputSync = gzipSync(buf); assert(outputSync instanceof Buffer); }); Deno.test("crc32()", () => { assertEquals(crc32("hello world"), 222957957); // @ts-expect-error: passing an object assertThrows(() => crc32({}), TypeError); }); Deno.test("crc32 doesn't overflow", () => { let checksum = 0; checksum = crc32(Buffer.from("H4sIAAAAAAAACg==", "base64"), checksum); checksum = crc32("aaa", checksum); assertEquals(checksum, 1466848669); }); Deno.test("crc32 large input", () => { let checkSum = 0xffffffff; for (let i = 0; i < 2 ** 16; i++) { checkSum = crc32("", checkSum); } assertEquals(checkSum, 0xffffffff); }); Deno.test("BrotliCompress", async () => { const deffered = Promise.withResolvers<void>(); // @ts-ignore: BrotliCompress is not typed const brotliCompress = new BrotliCompress(); // @ts-ignore: BrotliDecompress is not typed const brotliDecompress = new BrotliDecompress(); brotliCompress.pipe(brotliDecompress); let data = ""; brotliDecompress.on("data", (v: Buffer) => { data += v.toString(); }); brotliDecompress.on("end", () => { deffered.resolve(); }); brotliCompress.write("hello"); brotliCompress.end(); await deffered.promise; assertEquals(data, "hello"); }); Deno.test("ERR_BUFFER_TOO_LARGE works correctly", () => { assertThrows( () => { deflateSync(randomBytes(1024), { maxOutputLength: 1, }); }, "Cannot create a Buffer larger than 1 bytes", ); }); // https://github.com/denoland/deno/issues/30829 Deno.test("gunzip doesn't cause stack overflow with 64MiB data", async () => { const data = Buffer.alloc(64 * 1024 * 1024); const compressed = gzipSync(data); const { promise, resolve, reject } = Promise.withResolvers<void>(); gunzip(compressed, (err, result) => { if (err) { reject(err); return; } if (!result) { reject(new Error("expected gunzip to return a Buffer")); return; } if (result.length !== data.length) { reject( new Error(`expected ${data.length} bytes, got ${result.length}`), ); return; } resolve(); }); await promise; }); // Every compression/decompression backend whose native handle writes the // post-write avail_out/avail_in pair back into `_writeState`. Decoders are // paired with valid compressed input so the write reaches the result buffer // instead of bailing out on a decode error. const raw = new Uint8Array(16).fill(0x61); const writeStateCases: [() => unknown, Uint8Array][] = [ [createDeflate, raw], [createGzip, raw], [createGunzip, gzipSync(raw)], [createBrotliCompress, raw], [createBrotliDecompress, brotliCompressSync(raw)], [createZstdCompress, raw], [createZstdDecompress, zstdCompressSync(raw)], ]; // The native zlib/brotli/zstd bindings must not retain a pointer into the JS // `_writeState` buffer across calls. The result buffer is passed per write and // bounds checked, so detaching it (e.g. via a structuredClone transfer) before // a write is harmless rather than writing into a freed backing store. Deno.test("zlib writeSync does not write through a detached _writeState", () => { const output = new Uint8Array(128); // Each of these would crash or corrupt the heap before the fix. With the // buffer detached the result write must be a no-op; the only thing asserted // is that the process survives and nothing lands in the freed backing store. for (const [factory, input] of writeStateCases) { // deno-lint-ignore no-explicit-any const stream = factory() as any; const handle = stream._handle; const state = stream._writeState; // Detach the underlying ArrayBuffer, freeing the backing store. structuredClone(state.buffer, { transfer: [state.buffer] }); assertEquals(state.buffer.byteLength, 0); handle.writeSync( 0, input, 0, input.length, output, 0, output.length, state, ); assertEquals(state.buffer.byteLength, 0); } }); // Guards the other direction: with a live buffer the per-write result must // actually be written. A regression that dropped or misrouted the argument // would silently leave `_writeState` untouched and pass the detach test above. Deno.test("zlib writeSync populates _writeState per call", () => { const SENTINEL = 0xffffffff; const input = new Uint8Array(32).fill(0x61); const output = new Uint8Array(128); // Decoders need valid input to reach the result write, so only the encoders // are exercised here; the shared native path covers the decoders. for ( const factory of [ createDeflate, createGzip, createBrotliCompress, createZstdCompress, ] ) { // deno-lint-ignore no-explicit-any const stream = factory() as any; const handle = stream._handle; const state = stream._writeState; state[0] = SENTINEL; state[1] = SENTINEL; // flush 0 means "process / no flush" across zlib, brotli and zstd. handle.writeSync( 0, input, 0, input.length, output, 0, output.length, state, ); // avail_out and avail_in were written, replacing the sentinel, and stay // within the bounds of the output and input buffers. assert(state[0] !== SENTINEL, "avail_out was not written"); assert(state[1] !== SENTINEL, "avail_in was not written"); assert(state[0] <= output.length, "avail_out out of range"); assert(state[1] <= input.length, "avail_in out of range"); } }); // Same regression as the writeSync detach test above, but for the async // `handle.write` op. That op is a separately duplicated native function which // also writes the result pair back into `_writeState`, so it carries its own // copy of the bounds check and needs its own coverage. The op is invoked // directly (rather than through the stream) so the detach is observed against // the freed backing store before any other state mutates. brotli/zstd run // `processCallback` synchronously from the op, which then reads the now-detached // state and throws downstream; that throw is unrelated to the native result // write under test and is swallowed. Surviving the call with the buffer still // detached is the assertion. Deno.test("zlib write does not write through a detached _writeState", () => { const output = new Uint8Array(128); for (const [factory, input] of writeStateCases) { // deno-lint-ignore no-explicit-any const stream = factory() as any; const handle = stream._handle; const state = stream._writeState; // Detach the underlying ArrayBuffer, freeing the backing store. structuredClone(state.buffer, { transfer: [state.buffer] }); assertEquals(state.buffer.byteLength, 0); try { handle.write( 0, input, 0, input.length, output, 0, output.length, state, ); } catch { // Expected for the engines whose op runs processCallback synchronously: // it reads the detached state and throws. The native result write (the // thing under test) already happened as a no-op before the callback. } assertEquals(state.buffer.byteLength, 0); } }); // Some npm packages (e.g. pngjs's synchronous inflate) call the low-level // `_handle.writeSync()` directly using Node's binding signature, which omits // the trailing write-state buffer argument and instead reads the result back // from the stream's `_writeState`. Moving the result buffer to a per-write // argument must not break that calling convention: the handle injects the // stream's `_writeState` when the caller leaves it out. Deno.test("zlib writeSync works with the Node 7-argument signature", () => { // zlib stream of an empty payload, decoded with the low-level handle. const input = Buffer.from([0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01]); const output = Buffer.allocUnsafe(1024); // deno-lint-ignore no-explicit-any const stream = createInflate() as any; const state = stream._writeState; state[0] = 0xffffffff; state[1] = 0xffffffff; // Note: only seven arguments, no trailing write-state buffer. stream._handle.writeSync( constants.Z_FINISH, input, 0, input.length, output, 0, output.length, ); // The avail_out/avail_in pair was written back into `_writeState`. assert(state[0] !== 0xffffffff, "avail_out was not written"); assert(state[1] !== 0xffffffff, "avail_in was not written"); assert(state[0] <= output.length, "avail_out out of range"); assert(state[1] <= input.length, "avail_in out of range"); }); // Callers that drive `_handle.writeSync()` directly (see above) choose the // offsets and lengths themselves. The Brotli encoder ops used to index the // backing store with them unchecked, which panicked the process; they must // report an out-of-range window as an error instead. Deno.test("brotli writeSync rejects out-of-range offsets", () => { const input = Buffer.from("hello"); const output = Buffer.allocUnsafe(1024); // deno-lint-ignore no-explicit-any const handle = (createBrotliCompress() as any)._handle; const writeSync = ( inOff: number, inLen: number, outOff: number, outLen: number, ) => handle.writeSync( constants.BROTLI_OPERATION_PROCESS, input, inOff, inLen, output, outOff, outLen, ); assertThrows(() => writeSync(0, input.length + 1, 0, output.length)); assertThrows(() => writeSync(input.length, 1, 0, output.length)); assertThrows(() => writeSync(0xffffffff, 1, 0, output.length)); assertThrows(() => writeSync(0, input.length, 0, output.length + 1)); assertThrows(() => writeSync(0, input.length, output.length, 1)); assertThrows(() => writeSync(0, input.length, 0xffffffff, 1)); // The in-range window still works. writeSync(0, input.length, 0, output.length); });