-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest.js
More file actions
177 lines (151 loc) · 6.6 KB
/
test.js
File metadata and controls
177 lines (151 loc) · 6.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
'use strict';
// Import necessary modules
import zlib from 'zlib'; // For compression and decompression functions
import miss from 'mississippi2'; // Utility library for working with streams in a more convenient way
import { httpStreamRecompress } from './index.js'; // Import the function to be tested
/**
* Main function to start the test suite.
*/
async function start() {
const KB = 1024; // Define a kilobyte
const MB = 1024 * KB; // Define a megabyte
// Define compression types to test
const compressions = ['raw', 'deflate', 'gzip', 'br'];
// Define different sizes of data to test with
const sizes = [50, 50 * KB, 2 * MB, 9 * MB, 33 * MB];
// Initialize TestBuffer to manage test buffers
const testBuffer = new TestBuffer();
// Prepare test scenarios with combinations of compression types and sizes
const preparations = [];
for (let compression of compressions) {
for (let size of sizes) {
preparations.push({ size, compression });
}
}
// Pre-generate test buffers for each scenario to ensure readiness
for (let [index, { size, compression }] of preparations.entries()) {
process.stderr.write(`\rprepare test buffers: ${(100 * (index + 1) / preparations.length).toFixed(0)}%`);
testBuffer.get(compression, size);
}
console.log('');
// Generate test cases combining various factors like content type, accept encoding, etc.
const tests = [];
for (let useContentLength of [true, false]) {
for (let contentType of [false, 'image/png', 'application/json']) {
for (let fast of [true, false]) {
for (let acceptEncoding of ['', 'deflate,', 'gzip, deflate, br', 'gzip, deflate']) {
for (let compression of compressions) {
for (let size of sizes) {
tests.push({ contentType, acceptEncoding, compression, size, useContentLength, fast });
}
}
}
}
}
}
// Execute each test case
for (let [index, test] of tests.entries()) {
process.stderr.write(`\rrun tests: ${(100 * (index + 1) / tests.length).toFixed(1)}%`);
const { contentType, acceptEncoding, compression, size, useContentLength, fast } = test;
// Retrieve buffers for input and its raw counterpart
const { bufferIn, bufferRawIn } = testBuffer.get(compression, size);
// Set up request and response headers based on the test case
let headersRequestIn = { 'accept-encoding': acceptEncoding };
let headersResponseIn = {};
if (compression !== 'raw') headersResponseIn['content-encoding'] = compression;
if (contentType) headersResponseIn['content-type'] = contentType;
if (useContentLength) headersResponseIn['content-length'] = bufferIn.length;
// Create an input stream from the buffer
let streamIn = miss.fromValue(bufferIn);
let responseHeaders, statusCode;
// Use a promise to handle the async nature of the stream processing
let bufferOut = await new Promise(resolve => {
let buffers = [];
let streamOut = miss.to(
(chunk, enc, cb) => { buffers.push(chunk); cb(); },
(cb) => { resolve(Buffer.concat(buffers)); cb(); }
);
// Mock response object methods for status and setting headers
streamOut.status = code => {
statusCode = code;
if (code !== 200) throw Error();
return streamOut;
};
streamOut.set = h => {
responseHeaders = h;
return streamOut;
};
// Call the function under test with the prepared input and mock response
httpStreamRecompress(headersRequestIn, Object.assign({}, headersResponseIn), streamIn, streamOut, fast);
});
// Determine the output buffer's raw form based on the content encoding
let bufferRawOut;
let contentEncoding = responseHeaders['content-encoding'];
switch (contentEncoding) {
case undefined: bufferRawOut = bufferOut; break;
case 'br': bufferRawOut = zlib.brotliDecompressSync(bufferOut); break;
case 'deflate': bufferRawOut = zlib.inflateSync(bufferOut); break;
case 'gzip': bufferRawOut = zlib.gunzipSync(bufferOut); break;
default: throw Error(contentEncoding);
}
// Validate the integrity of the data after compression and decompression
if (!bufferRawIn.equals(bufferRawOut)) throw Error('not the same buffers');
if (responseHeaders['content-length'] && (responseHeaders['content-length'] !== bufferOut.length)) throw Error('Content length mismatch');
if (responseHeaders['vary'] !== 'accept-encoding') throw Error('Missing or incorrect Vary header');
// Additional checks for chunked transfer and content length headers based on buffer size
if (bufferOut.length > 32 * MB) {
if (responseHeaders['transfer-encoding'] !== 'chunked') throw Error('Expected chunked transfer encoding');
if (responseHeaders['content-length']) throw Error('Unexpected content length header');
}
}
console.log('\nFinished');
}
/**
* TestBuffer class manages the creation and caching of buffers for different test scenarios.
*/
class TestBuffer {
#data;
#comCache; // Cache for compressed data
#rawCache; // Cache for raw data
constructor() {
// Initialize random data for use in buffers
this.#data = Buffer.from(Uint8Array.from({ length: 256 }, () => Math.floor(Math.random() * 256)));
this.#rawCache = new Map();
this.#comCache = new Map();
}
/**
* Retrieves a raw buffer of the specified size, caching it for future requests.
* @param {number} size The desired size of the buffer
* @returns {Buffer} The raw buffer
*/
#getRaw(size) {
if (this.#rawCache.has(size)) return this.#rawCache.get(size);
const bufferRawIn = Buffer.allocUnsafe(size);
for (let i = 0; i < size; i += this.#data.length) this.#data.copy(bufferRawIn, i);
this.#rawCache.set(size, bufferRawIn);
return bufferRawIn;
}
/**
* Gets a buffer for a given compression type and size, either from cache or by generating and compressing a new one.
* @param {string} compression The compression type ('raw', 'gzip', 'br')
* @param {number} size The size of the buffer
* @returns {Object} An object containing the compressed buffer and its raw counterpart
*/
get(compression, size) {
if (this.#comCache.has(compression + size)) return this.#comCache.get(compression + size);
const bufferRawIn = this.#getRaw(size);
let bufferIn;
switch (compression) {
case 'raw': bufferIn = bufferRawIn.subarray(); break;
case 'deflate': bufferIn = zlib.deflateSync(bufferRawIn, { level: 9 }); break;
case 'gzip': bufferIn = zlib.gzipSync(bufferRawIn, { level: 9 }); break;
case 'br': bufferIn = zlib.brotliCompressSync(bufferRawIn, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 9 } }); break;
default: throw Error('Unsupported compression type');
}
const result = { bufferIn, bufferRawIn };
this.#comCache.set(compression + size, result);
return result;
}
}
// Execute the start function to begin tests
start();