MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1import { test, testThrows, summary } from './helpers.js';
2
3console.log('CompressionStream / DecompressionStream Tests\n');
4
5test('CS typeof', typeof CompressionStream, 'function');
6test('CS toStringTag', Object.prototype.toString.call(new CompressionStream('gzip')), '[object CompressionStream]');
7
8testThrows('CS requires new', () => CompressionStream('gzip'));
9testThrows('CS requires format', () => new CompressionStream());
10testThrows('CS rejects invalid format', () => new CompressionStream('invalid'));
11
12const cs0 = new CompressionStream('gzip');
13test('CS readable is ReadableStream', cs0.readable instanceof ReadableStream, true);
14test('CS writable is WritableStream', cs0.writable instanceof WritableStream, true);
15
16test('DS typeof', typeof DecompressionStream, 'function');
17test('DS toStringTag', Object.prototype.toString.call(new DecompressionStream('gzip')), '[object DecompressionStream]');
18
19testThrows('DS requires new', () => DecompressionStream('gzip'));
20testThrows('DS requires format', () => new DecompressionStream());
21testThrows('DS rejects invalid format', () => new DecompressionStream('invalid'));
22
23const ds0 = new DecompressionStream('gzip');
24test('DS readable is ReadableStream', ds0.readable instanceof ReadableStream, true);
25test('DS writable is WritableStream', ds0.writable instanceof WritableStream, true);
26
27const csBrotli = new CompressionStream('brotli');
28test('CS brotli toStringTag', Object.prototype.toString.call(csBrotli), '[object CompressionStream]');
29
30async function collectStream(readable) {
31 const reader = readable.getReader();
32 const chunks = [];
33 while (true) {
34 const { value, done } = await reader.read();
35 if (done) break;
36 chunks.push(value);
37 }
38 let total = 0;
39 for (const c of chunks) total += c.byteLength;
40 const result = new Uint8Array(total);
41 let offset = 0;
42 for (const c of chunks) {
43 result.set(c, offset);
44 offset += c.byteLength;
45 }
46 return result;
47}
48
49async function testGzipRoundTrip() {
50 const input = new TextEncoder().encode('Hello, compression world!');
51 const cs = new CompressionStream('gzip');
52 const writer = cs.writable.getWriter();
53 writer.write(input);
54 writer.close();
55 const compressed = await collectStream(cs.readable);
56 test('gzip compressed is smaller or has gzip header', compressed[0], 0x1f);
57 test('gzip compressed header byte 2', compressed[1], 0x8b);
58
59 const ds = new DecompressionStream('gzip');
60 const writer2 = ds.writable.getWriter();
61 writer2.write(compressed);
62 writer2.close();
63 const decompressed = await collectStream(ds.readable);
64 test('gzip roundtrip length', decompressed.length, input.length);
65 test('gzip roundtrip value', new TextDecoder().decode(decompressed), 'Hello, compression world!');
66}
67
68async function testDeflateRoundTrip() {
69 const input = new TextEncoder().encode('Deflate test data');
70 const cs = new CompressionStream('deflate');
71 const writer = cs.writable.getWriter();
72 writer.write(input);
73 writer.close();
74 const compressed = await collectStream(cs.readable);
75 test('deflate compressed length > 0', compressed.length > 0, true);
76
77 const ds = new DecompressionStream('deflate');
78 const writer2 = ds.writable.getWriter();
79 writer2.write(compressed);
80 writer2.close();
81 const decompressed = await collectStream(ds.readable);
82 test('deflate roundtrip', new TextDecoder().decode(decompressed), 'Deflate test data');
83}
84
85async function testDeflateRawRoundTrip() {
86 const input = new TextEncoder().encode('Raw deflate test');
87 const cs = new CompressionStream('deflate-raw');
88 const writer = cs.writable.getWriter();
89 writer.write(input);
90 writer.close();
91 const compressed = await collectStream(cs.readable);
92 test('deflate-raw compressed length > 0', compressed.length > 0, true);
93
94 const ds = new DecompressionStream('deflate-raw');
95 const writer2 = ds.writable.getWriter();
96 writer2.write(compressed);
97 writer2.close();
98 const decompressed = await collectStream(ds.readable);
99 test('deflate-raw roundtrip', new TextDecoder().decode(decompressed), 'Raw deflate test');
100}
101
102async function testBrotliRoundTrip() {
103 const input = new TextEncoder().encode('brotli stream test data');
104 const cs = new CompressionStream('brotli');
105 const writer = cs.writable.getWriter();
106 writer.write(input);
107 writer.close();
108 const compressed = await collectStream(cs.readable);
109 test('brotli compressed length > 0', compressed.length > 0, true);
110
111 const ds = new DecompressionStream('brotli');
112 const writer2 = ds.writable.getWriter();
113 writer2.write(compressed);
114 writer2.close();
115 const reader = ds.readable.getReader();
116 const first = await reader.read();
117 test('brotli decompressed chunk is Uint8Array', first.value.constructor, Uint8Array);
118 test('brotli roundtrip', new TextDecoder().decode(first.value), 'brotli stream test data');
119}
120
121async function testPipeThrough() {
122 const input = 'pipe through compression test';
123 const encoded = new TextEncoder().encode(input);
124 const rs = new ReadableStream({
125 start(c) {
126 c.enqueue(encoded);
127 c.close();
128 }
129 });
130 const compressed = await collectStream(rs.pipeThrough(new CompressionStream('gzip')));
131
132 const rs2 = new ReadableStream({
133 start(c) {
134 c.enqueue(compressed);
135 c.close();
136 }
137 });
138 const decompressed = await collectStream(rs2.pipeThrough(new DecompressionStream('gzip')));
139 test('pipeThrough roundtrip', new TextDecoder().decode(decompressed), input);
140}
141
142async function testMultipleChunks() {
143 const cs = new CompressionStream('gzip');
144 const writer = cs.writable.getWriter();
145 writer.write(new TextEncoder().encode('chunk1 '));
146 writer.write(new TextEncoder().encode('chunk2 '));
147 writer.write(new TextEncoder().encode('chunk3'));
148 writer.close();
149 const compressed = await collectStream(cs.readable);
150
151 const ds = new DecompressionStream('gzip');
152 const writer2 = ds.writable.getWriter();
153 writer2.write(compressed);
154 writer2.close();
155 const decompressed = await collectStream(ds.readable);
156 test('multi-chunk roundtrip', new TextDecoder().decode(decompressed), 'chunk1 chunk2 chunk3');
157}
158
159async function testLargeData() {
160 const str = 'x'.repeat(100000);
161 const input = new TextEncoder().encode(str);
162 const cs = new CompressionStream('gzip');
163 const writer = cs.writable.getWriter();
164 writer.write(input);
165 writer.close();
166 const compressed = await collectStream(cs.readable);
167 test('large data compresses', compressed.length < input.length, true);
168
169 const ds = new DecompressionStream('gzip');
170 const writer2 = ds.writable.getWriter();
171 writer2.write(compressed);
172 writer2.close();
173 const decompressed = await collectStream(ds.readable);
174 test('large data roundtrip length', decompressed.length, input.length);
175}
176
177await testGzipRoundTrip();
178await testDeflateRoundTrip();
179await testDeflateRawRoundTrip();
180await testBrotliRoundTrip();
181await testPipeThrough();
182await testMultipleChunks();
183await testLargeData();
184
185summary();