• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const common = require('../common');
24const assert = require('assert');
25const zlib = require('zlib');
26const stream = require('stream');
27const fs = require('fs');
28const fixtures = require('../common/fixtures');
29
30// Should not segfault.
31assert.throws(() => zlib.gzipSync(Buffer.alloc(0), { windowBits: 8 }), {
32  code: 'ERR_OUT_OF_RANGE',
33  name: 'RangeError',
34  message: 'The value of "options.windowBits" is out of range. ' +
35           'It must be >= 9 and <= 15. Received 8',
36});
37
38let zlibPairs = [
39  [zlib.Deflate, zlib.Inflate],
40  [zlib.Gzip, zlib.Gunzip],
41  [zlib.Deflate, zlib.Unzip],
42  [zlib.Gzip, zlib.Unzip],
43  [zlib.DeflateRaw, zlib.InflateRaw],
44  [zlib.BrotliCompress, zlib.BrotliDecompress],
45];
46
47// How fast to trickle through the slowstream
48let trickle = [128, 1024, 1024 * 1024];
49
50// Tunable options for zlib classes.
51
52// several different chunk sizes
53let chunkSize = [128, 1024, 1024 * 16, 1024 * 1024];
54
55// This is every possible value.
56let level = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
57let windowBits = [8, 9, 10, 11, 12, 13, 14, 15];
58let memLevel = [1, 2, 3, 4, 5, 6, 7, 8, 9];
59let strategy = [0, 1, 2, 3, 4];
60
61// It's nice in theory to test every combination, but it
62// takes WAY too long.  Maybe a pummel test could do this?
63if (!process.env.PUMMEL) {
64  trickle = [1024];
65  chunkSize = [1024 * 16];
66  level = [6];
67  memLevel = [8];
68  windowBits = [15];
69  strategy = [0];
70}
71
72let testFiles = ['person.jpg', 'elipses.txt', 'empty.txt'];
73
74if (process.env.FAST) {
75  zlibPairs = [[zlib.Gzip, zlib.Unzip]];
76  testFiles = ['person.jpg'];
77}
78
79const tests = {};
80testFiles.forEach(common.mustCall((file) => {
81  tests[file] = fixtures.readSync(file);
82}, testFiles.length));
83
84
85// Stream that saves everything
86class BufferStream extends stream.Stream {
87  constructor() {
88    super();
89    this.chunks = [];
90    this.length = 0;
91    this.writable = true;
92    this.readable = true;
93  }
94
95  write(c) {
96    this.chunks.push(c);
97    this.length += c.length;
98    return true;
99  }
100
101  end(c) {
102    if (c) this.write(c);
103    // flatten
104    const buf = Buffer.allocUnsafe(this.length);
105    let i = 0;
106    this.chunks.forEach((c) => {
107      c.copy(buf, i);
108      i += c.length;
109    });
110    this.emit('data', buf);
111    this.emit('end');
112    return true;
113  }
114}
115
116class SlowStream extends stream.Stream {
117  constructor(trickle) {
118    super();
119    this.trickle = trickle;
120    this.offset = 0;
121    this.readable = this.writable = true;
122  }
123
124  write() {
125    throw new Error('not implemented, just call ss.end(chunk)');
126  }
127
128  pause() {
129    this.paused = true;
130    this.emit('pause');
131  }
132
133  resume() {
134    const emit = () => {
135      if (this.paused) return;
136      if (this.offset >= this.length) {
137        this.ended = true;
138        return this.emit('end');
139      }
140      const end = Math.min(this.offset + this.trickle, this.length);
141      const c = this.chunk.slice(this.offset, end);
142      this.offset += c.length;
143      this.emit('data', c);
144      process.nextTick(emit);
145    };
146
147    if (this.ended) return;
148    this.emit('resume');
149    if (!this.chunk) return;
150    this.paused = false;
151    emit();
152  }
153
154  end(chunk) {
155    // Walk over the chunk in blocks.
156    this.chunk = chunk;
157    this.length = chunk.length;
158    this.resume();
159    return this.ended;
160  }
161}
162
163// windowBits: 8 shouldn't throw
164zlib.createDeflateRaw({ windowBits: 8 });
165
166{
167  const node = fs.createReadStream(fixtures.path('person.jpg'));
168  const raw = [];
169  const reinflated = [];
170  node.on('data', (chunk) => raw.push(chunk));
171
172  // Usually, the inflate windowBits parameter needs to be at least the
173  // value of the matching deflate’s windowBits. However, inflate raw with
174  // windowBits = 8 should be able to handle compressed data from a source
175  // that does not know about the silent 8-to-9 upgrade of windowBits
176  // that most versions of zlib/Node perform, and which *still* results in
177  // a valid 8-bit-window zlib stream.
178  node.pipe(zlib.createDeflateRaw({ windowBits: 9 }))
179      .pipe(zlib.createInflateRaw({ windowBits: 8 }))
180      .on('data', (chunk) => reinflated.push(chunk))
181      .on('end', common.mustCall(
182        () => assert(Buffer.concat(raw).equals(Buffer.concat(reinflated)))))
183      .on('close', common.mustCall(1));
184}
185
186// For each of the files, make sure that compressing and
187// decompressing results in the same data, for every combination
188// of the options set above.
189
190const testKeys = Object.keys(tests);
191testKeys.forEach(common.mustCall((file) => {
192  const test = tests[file];
193  chunkSize.forEach(common.mustCall((chunkSize) => {
194    trickle.forEach(common.mustCall((trickle) => {
195      windowBits.forEach(common.mustCall((windowBits) => {
196        level.forEach(common.mustCall((level) => {
197          memLevel.forEach(common.mustCall((memLevel) => {
198            strategy.forEach(common.mustCall((strategy) => {
199              zlibPairs.forEach(common.mustCall((pair) => {
200                const Def = pair[0];
201                const Inf = pair[1];
202                const opts = { level, windowBits, memLevel, strategy };
203
204                const def = new Def(opts);
205                const inf = new Inf(opts);
206                const ss = new SlowStream(trickle);
207                const buf = new BufferStream();
208
209                // Verify that the same exact buffer comes out the other end.
210                buf.on('data', common.mustCall((c) => {
211                  const msg = `${file} ${chunkSize} ${
212                    JSON.stringify(opts)} ${Def.name} -> ${Inf.name}`;
213                  let i;
214                  for (i = 0; i < Math.max(c.length, test.length); i++) {
215                    if (c[i] !== test[i]) {
216                      assert.fail(msg);
217                      break;
218                    }
219                  }
220                }));
221
222                // The magic happens here.
223                ss.pipe(def).pipe(inf).pipe(buf);
224                ss.end(test);
225              }, zlibPairs.length));
226            }, strategy.length));
227          }, memLevel.length));
228        }, level.length));
229      }, windowBits.length));
230    }, trickle.length));
231  }, chunkSize.length));
232}, testKeys.length));
233