v / vlib / compress / snappy / snappy_framing_test.v
284 lines · 239 sloc · 8.94 KB · a2be3749cecadd4d63802c9f14cbe20e04cc39fa
Raw
1module snappy
2
3import arrays
4import io
5
6// ---------------------------------------------------------------------------
7// Helpers
8// ---------------------------------------------------------------------------
9
10fn stream_round_trip(label string, input []u8) ! {
11 encoded := encode_stream(input)
12 decoded := decode_stream(encoded)!
13
14 assert decoded == input, '${label}: stream round-trip mismatch (len=${input.len})'
15}
16
17// drain_decoder closes dec and returns all decoded bytes accumulated so far.
18fn drain_decoder(mut dec StreamDecoder) []u8 {
19 dec.close() or { panic(err) }
20 mut collected := []u8{}
21 mut chunk := []u8{len: 4096}
22 for {
23 n := dec.read(mut chunk) or { break }
24 if n == 0 {
25 break
26 }
27 collected << chunk[..n]
28 }
29 return collected
30}
31
32// ---------------------------------------------------------------------------
33// CRC32C tests
34// ---------------------------------------------------------------------------
35
36fn test_crc32c_empty() {
37 // Known value: CRC32C("") = 0x00000000
38 assert crc32c([]) == u32(0x00000000)
39}
40
41fn test_crc32c_known_vector() {
42 // CRC32C("123456789") = 0xE3069283 — standard test vector.
43 assert crc32c('123456789'.bytes()) == u32(0xe3069283)
44}
45
46fn test_mask_unmask_roundtrip() {
47 for v in [u32(0), u32(1), u32(0xdeadbeef), u32(0xffffffff)] {
48 assert unmask_crc(mask_crc(v)) == v, 'mask/unmask failed for 0x${v:08x}'
49 }
50}
51
52// ---------------------------------------------------------------------------
53// One-shot encode / decode
54// ---------------------------------------------------------------------------
55
56fn test_stream_empty() {
57 stream_round_trip('empty', []u8{}) or { panic(err) }
58}
59
60fn test_stream_single_byte() {
61 stream_round_trip('single byte', [u8(0x42)]) or { panic(err) }
62}
63
64fn test_stream_small() {
65 stream_round_trip('hello world', 'Hello, World!'.bytes()) or { panic(err) }
66}
67
68fn test_stream_compressible() {
69 input := []u8{len: 8192, init: u8(index % 7)}
70 stream_round_trip('compressible', input) or { panic(err) }
71}
72
73fn test_stream_incompressible() {
74 mut input := []u8{len: 2048}
75 mut seed := u32(0xc0ffee42)
76 for i in 0 .. input.len {
77 seed = seed * 1664525 + 1013904223
78 input[i] = u8(seed >> 24)
79 }
80 stream_round_trip('incompressible', input) or { panic(err) }
81}
82
83fn test_stream_exactly_one_block() {
84 // Exactly max_chunk_data_size bytes — exercises the boundary condition.
85 input := []u8{len: max_chunk_data_size, init: u8(index & 0xff)}
86 stream_round_trip('one-full-block', input) or { panic(err) }
87}
88
89fn test_stream_multi_block() {
90 // Spans multiple 64 KiB blocks.
91 input := []u8{len: max_chunk_data_size * 3 + 100, init: u8(index & 0xff)}
92 stream_round_trip('multi-block', input) or { panic(err) }
93}
94
95fn test_stream_identifier_present() {
96 // The encoded stream must start with the identifier chunk.
97 encoded := encode_stream('test'.bytes())
98 assert encoded.len >= 10, 'stream too short to contain identifier'
99 assert encoded[0] == chunk_type_stream_id, 'first byte must be stream identifier type'
100 // The 6-byte magic must follow the 4-byte header.
101 magic := encoded[4..10]
102 assert magic == stream_identifier_magic, 'stream identifier magic mismatch'
103}
104
105fn test_stream_crc_mismatch_detected() {
106 mut encoded := encode_stream('hello'.bytes())
107 // Corrupt a byte in the CRC field of the first data chunk (after the
108 // 10-byte identifier: 4-byte header + 4-byte CRC starts at byte 14).
109 if encoded.len > 14 {
110 encoded[14] ^= 0xff
111 }
112 decode_stream(encoded) or { return } // expected error
113 panic('expected decode_stream to fail on CRC mismatch')
114}
115
116fn test_stream_missing_identifier() {
117 // A stream that starts with a compressed chunk instead of the identifier.
118 bad := [u8(chunk_type_compressed), 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
119 decode_stream(bad) or { return }
120 panic('expected decode_stream to fail without identifier chunk')
121}
122
123fn test_stream_unskippable_reserved_chunk() {
124 mut s := encode_stream('hello'.bytes())
125 // Inject a reserved unskippable chunk (type=0x02) before the data.
126 injected := [u8(0x02), 0x01, 0x00, 0x00, u8(0x00)] // type + 3-byte len=1 + 1 payload byte
127 // Insert after the 10-byte stream identifier.
128 s = arrays.reduce([s[..10], injected, s[10..]], fn (acc []u8, x []u8) []u8 {
129 mut out := acc.clone()
130 out << x
131 return out
132 })!
133 decode_stream(s) or { return }
134 panic('expected decode_stream to fail on unskippable reserved chunk')
135}
136
137fn test_stream_padding_skipped() {
138 // Build a stream manually: identifier + padding + compressed data.
139 mut s := []u8{}
140 // Stream identifier
141 s << chunk_type_stream_id
142 write_u24le(mut s, stream_identifier_magic.len)
143 s << stream_identifier_magic
144 // Padding chunk (type=0xfe, length=4, 4 zero bytes)
145 s << chunk_type_padding
146 write_u24le(mut s, 4)
147 s << [u8(0), 0, 0, 0]
148 // A real data chunk
149 inner := encode_stream('after padding'.bytes())
150 // Skip the inner identifier (first 10 bytes) and append the data chunk.
151 s << inner[10..]
152
153 decoded := decode_stream(s) or { panic(err) }
154 assert decoded == 'after padding'.bytes(), 'padding chunk should be silently skipped'
155}
156
157// ---------------------------------------------------------------------------
158// Incremental encoder
159// ---------------------------------------------------------------------------
160
161fn test_incremental_encoder_matches_oneshot() {
162 input := []u8{len: max_chunk_data_size + 500, init: u8(index & 0xff)}
163
164 // One-shot
165 oneshot := encode_stream(input)
166
167 // Incremental: feed in 1000-byte pieces via write/close/peek.
168 mut enc := StreamEncoder{}
169 mut pos := 0
170 for pos < input.len {
171 end := if pos + 1000 < input.len { pos + 1000 } else { input.len }
172 enc.write(input[pos..end]) or { panic(err) }
173 pos = end
174 }
175 enc.close()
176 incremental := enc.peek()
177
178 assert incremental == oneshot, 'incremental encoder output differs from one-shot'
179}
180
181fn test_incremental_encoder_decodable() {
182 input := 'Streaming is fun!'.bytes().repeat(3000)
183
184 mut enc := StreamEncoder{}
185 enc.write(input[..input.len / 2]) or { panic(err) }
186 enc.write(input[input.len / 2..]) or { panic(err) }
187 enc.close()
188 stream := enc.peek()
189
190 decoded := decode_stream(stream) or { panic(err) }
191 assert decoded == input, 'incremental encoder produced undecodable stream'
192}
193
194fn test_incremental_encoder_write_after_close_errors() {
195 // write() on a closed encoder must return an error.
196 mut enc := StreamEncoder{}
197 enc.write('hello'.bytes()) or { panic(err) }
198 enc.close()
199 enc.write('world'.bytes()) or { return } // expected error
200 panic('expected write on closed encoder to fail')
201}
202
203fn test_incremental_encoder_read_drains_output() {
204 // read() should drain bytes from the encoded output incrementally.
205 input := []u8{len: max_chunk_data_size + 100, init: u8(index & 0xff)}
206 mut enc := StreamEncoder{}
207 enc.write(input) or { panic(err) }
208 enc.close()
209
210 // Drain via read() in small pieces and reassemble.
211 mut collected := []u8{}
212 mut chunk := []u8{len: 256}
213 for {
214 n := enc.read(mut chunk) or { break }
215 if n == 0 {
216 break
217 }
218 collected << chunk[..n]
219 }
220
221 expected := encode_stream(input)
222 assert collected == expected, 'read() did not produce the same bytes as encode_stream'
223}
224
225// ---------------------------------------------------------------------------
226// Incremental decoder
227// ---------------------------------------------------------------------------
228
229fn test_incremental_decoder_byte_by_byte() {
230 input := 'Byte by byte delivery'.bytes().repeat(500)
231 stream := encode_stream(input)
232
233 mut dec := StreamDecoder{}
234 for b in stream {
235 dec.write([b]) or { panic(err) }
236 }
237 assert drain_decoder(mut dec) == input, 'incremental decoder failed (byte-by-byte)'
238}
239
240fn test_incremental_decoder_large_chunks() {
241 input := []u8{len: max_chunk_data_size * 2 + 99, init: u8(index % 13)}
242 stream := encode_stream(input)
243
244 mut dec := StreamDecoder{}
245 mut pos := 0
246 for pos < stream.len {
247 end := if pos + 8192 < stream.len { pos + 8192 } else { stream.len }
248 dec.write(stream[pos..end]) or { panic(err) }
249 pos = end
250 }
251 assert drain_decoder(mut dec) == input, 'incremental decoder failed (large chunks)'
252}
253
254fn test_incremental_decoder_read_drains_output() {
255 // A first read() call should return all decoded bytes; a second call on a
256 // closed decoder with no remaining output should return io.Eof.
257 stream := encode_stream('hello world'.bytes())
258 mut dec := StreamDecoder{}
259 dec.write(stream) or { panic(err) }
260 dec.close() or { panic(err) }
261
262 mut buf := []u8{len: 4096}
263
264 // First read — should yield the decoded payload.
265 n := dec.read(mut buf) or { panic(err) }
266 assert buf[..n] == 'hello world'.bytes(), 'first read should return decoded bytes'
267
268 // Second read — decoder is closed and output is empty; expect Eof.
269 dec.read(mut buf) or {
270 assert err is io.Eof, 'expected io.Eof after output is drained, got: ${err}'
271 return
272 }
273 panic('expected io.Eof on second read after output is drained')
274}
275
276fn test_incremental_decoder_write_after_close_errors() {
277 // write() on a closed decoder must return an error.
278 stream := encode_stream('hello'.bytes())
279 mut dec := StreamDecoder{}
280 dec.write(stream) or { panic(err) }
281 dec.close() or { panic(err) }
282 dec.write(stream) or { return } // expected error
283 panic('expected write on closed decoder to fail')
284}
285