| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| 'use strict'; |
| const common = require('../common'); |
| const assert = require('assert'); |
| const zlib = require('zlib'); |
| const stream = require('stream'); |
| const fs = require('fs'); |
| const fixtures = require('../common/fixtures'); |
| |
| let zlibPairs = [ |
| [zlib.Deflate, zlib.Inflate], |
| [zlib.Gzip, zlib.Gunzip], |
| [zlib.Deflate, zlib.Unzip], |
| [zlib.Gzip, zlib.Unzip], |
| [zlib.DeflateRaw, zlib.InflateRaw], |
| [zlib.BrotliCompress, zlib.BrotliDecompress], |
| ]; |
| |
| // How fast to trickle through the slowstream |
| let trickle = [128, 1024, 1024 * 1024]; |
| |
| // Tunable options for zlib classes. |
| |
| // several different chunk sizes |
| let chunkSize = [128, 1024, 1024 * 16, 1024 * 1024]; |
| |
| // This is every possible value. |
| let level = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| let windowBits = [8, 9, 10, 11, 12, 13, 14, 15]; |
| let memLevel = [1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| let strategy = [0, 1, 2, 3, 4]; |
| |
| // It's nice in theory to test every combination, but it |
| // takes WAY too long. Maybe a pummel test could do this? |
| if (!process.env.PUMMEL) { |
| trickle = [1024]; |
| chunkSize = [1024 * 16]; |
| level = [6]; |
| memLevel = [8]; |
| windowBits = [15]; |
| strategy = [0]; |
| } |
| |
| let testFiles = ['person.jpg', 'elipses.txt', 'empty.txt']; |
| |
| if (process.env.FAST) { |
| zlibPairs = [[zlib.Gzip, zlib.Unzip]]; |
| testFiles = ['person.jpg']; |
| } |
| |
| const tests = {}; |
| testFiles.forEach(common.mustCall((file) => { |
| tests[file] = fixtures.readSync(file); |
| }, testFiles.length)); |
| |
| |
| // Stream that saves everything |
| class BufferStream extends stream.Stream { |
| constructor() { |
| super(); |
| this.chunks = []; |
| this.length = 0; |
| this.writable = true; |
| this.readable = true; |
| } |
| |
| write(c) { |
| this.chunks.push(c); |
| this.length += c.length; |
| return true; |
| } |
| |
| end(c) { |
| if (c) this.write(c); |
| // flatten |
| const buf = Buffer.allocUnsafe(this.length); |
| let i = 0; |
| this.chunks.forEach((c) => { |
| c.copy(buf, i); |
| i += c.length; |
| }); |
| this.emit('data', buf); |
| this.emit('end'); |
| return true; |
| } |
| } |
| |
| class SlowStream extends stream.Stream { |
| constructor(trickle) { |
| super(); |
| this.trickle = trickle; |
| this.offset = 0; |
| this.readable = this.writable = true; |
| } |
| |
| write() { |
| throw new Error('not implemented, just call ss.end(chunk)'); |
| } |
| |
| pause() { |
| this.paused = true; |
| this.emit('pause'); |
| } |
| |
| resume() { |
| const emit = () => { |
| if (this.paused) return; |
| if (this.offset >= this.length) { |
| this.ended = true; |
| return this.emit('end'); |
| } |
| const end = Math.min(this.offset + this.trickle, this.length); |
| const c = this.chunk.slice(this.offset, end); |
| this.offset += c.length; |
| this.emit('data', c); |
| process.nextTick(emit); |
| }; |
| |
| if (this.ended) return; |
| this.emit('resume'); |
| if (!this.chunk) return; |
| this.paused = false; |
| emit(); |
| } |
| |
| end(chunk) { |
| // Walk over the chunk in blocks. |
| this.chunk = chunk; |
| this.length = chunk.length; |
| this.resume(); |
| return this.ended; |
| } |
| } |
| |
| // windowBits: 8 shouldn't throw |
| zlib.createDeflateRaw({ windowBits: 8 }); |
| |
| { |
| const node = fs.createReadStream(fixtures.path('person.jpg')); |
| const raw = []; |
| const reinflated = []; |
| node.on('data', (chunk) => raw.push(chunk)); |
| |
| // Usually, the inflate windowBits parameter needs to be at least the |
| // value of the matching deflateās windowBits. However, inflate raw with |
| // windowBits = 8 should be able to handle compressed data from a source |
| // that does not know about the silent 8-to-9 upgrade of windowBits |
| // that most versions of zlib/Node perform, and which *still* results in |
| // a valid 8-bit-window zlib stream. |
| node.pipe(zlib.createDeflateRaw({ windowBits: 9 })) |
| .pipe(zlib.createInflateRaw({ windowBits: 8 })) |
| .on('data', (chunk) => reinflated.push(chunk)) |
| .on('end', common.mustCall( |
| () => assert(Buffer.concat(raw).equals(Buffer.concat(reinflated))))); |
| } |
| |
| // For each of the files, make sure that compressing and |
| // decompressing results in the same data, for every combination |
| // of the options set above. |
| |
| const testKeys = Object.keys(tests); |
| testKeys.forEach(common.mustCall((file) => { |
| const test = tests[file]; |
| chunkSize.forEach(common.mustCall((chunkSize) => { |
| trickle.forEach(common.mustCall((trickle) => { |
| windowBits.forEach(common.mustCall((windowBits) => { |
| level.forEach(common.mustCall((level) => { |
| memLevel.forEach(common.mustCall((memLevel) => { |
| strategy.forEach(common.mustCall((strategy) => { |
| zlibPairs.forEach(common.mustCall((pair) => { |
| const Def = pair[0]; |
| const Inf = pair[1]; |
| const opts = { level, windowBits, memLevel, strategy }; |
| |
| const def = new Def(opts); |
| const inf = new Inf(opts); |
| const ss = new SlowStream(trickle); |
| const buf = new BufferStream(); |
| |
| // Verify that the same exact buffer comes out the other end. |
| buf.on('data', common.mustCall((c) => { |
| const msg = `${file} ${chunkSize} ${ |
| JSON.stringify(opts)} ${Def.name} -> ${Inf.name}`; |
| let i; |
| for (i = 0; i < Math.max(c.length, test.length); i++) { |
| if (c[i] !== test[i]) { |
| assert.fail(msg); |
| break; |
| } |
| } |
| })); |
| |
| // The magic happens here. |
| ss.pipe(def).pipe(inf).pipe(buf); |
| ss.end(test); |
| }, zlibPairs.length)); |
| }, strategy.length)); |
| }, memLevel.length)); |
| }, level.length)); |
| }, windowBits.length)); |
| }, trickle.length)); |
| }, chunkSize.length)); |
| }, testKeys.length)); |