| 1 | module zstd |
| 2 | |
| 3 | import os |
| 4 | |
| 5 | const samples_folder = os.join_path(os.dir(@FILE), 'samples') |
| 6 | |
| 7 | fn s(fname string) string { |
| 8 | return os.join_path(samples_folder, fname) |
| 9 | } |
| 10 | |
| 11 | fn test_zstd() { |
| 12 | assert version_number() >= 10505 |
| 13 | |
| 14 | uncompressed := 'Hello world!'.repeat(10000) |
| 15 | compressed := compress(uncompressed.bytes())! |
| 16 | decompressed := decompress(compressed)! |
| 17 | assert decompressed == uncompressed.bytes() |
| 18 | } |
| 19 | |
| 20 | fn test_zstd_deferent_compression_level() { |
| 21 | uncompressed := 'Hello world!'.repeat(10000) |
| 22 | |
| 23 | compressed_1000 := compress(uncompressed.bytes(), compression_level: 1000)! |
| 24 | decompressed_1000 := decompress(compressed_1000)! |
| 25 | assert decompressed_1000 == uncompressed.bytes() |
| 26 | |
| 27 | compressed_0 := compress(uncompressed.bytes(), compression_level: 0)! |
| 28 | decompressed_0 := decompress(compressed_0)! |
| 29 | assert decompressed_0 == uncompressed.bytes() |
| 30 | |
| 31 | compressed_1 := compress(uncompressed.bytes(), compression_level: 1)! |
| 32 | decompressed_1 := decompress(compressed_1)! |
| 33 | assert decompressed_1 == uncompressed.bytes() |
| 34 | |
| 35 | compressed_15 := compress(uncompressed.bytes(), compression_level: 15)! |
| 36 | decompressed_15 := decompress(compressed_15)! |
| 37 | assert decompressed_15 == uncompressed.bytes() |
| 38 | } |
| 39 | |
| 40 | fn test_zstd_nb_threads() { |
| 41 | uncompressed := 'Hello world!'.repeat(10000) |
| 42 | |
| 43 | compressed_0 := compress(uncompressed.bytes(), nb_threads: 0)! |
| 44 | decompressed_0 := decompress(compressed_0)! |
| 45 | assert decompressed_0 == uncompressed.bytes() |
| 46 | |
| 47 | compressed_1 := compress(uncompressed.bytes(), nb_threads: 1)! |
| 48 | decompressed_1 := decompress(compressed_1)! |
| 49 | assert decompressed_1 == uncompressed.bytes() |
| 50 | |
| 51 | compressed_15 := compress(uncompressed.bytes(), nb_threads: 15)! |
| 52 | decompressed_15 := decompress(compressed_15)! |
| 53 | assert decompressed_15 == uncompressed.bytes() |
| 54 | } |
| 55 | |
| 56 | fn test_zstd_checksum_flag() { |
| 57 | uncompressed := 'Hello world!'.repeat(10000) |
| 58 | |
| 59 | compressed_true := compress(uncompressed.bytes(), checksum_flag: true)! |
| 60 | decompressed_true := decompress(compressed_true)! |
| 61 | assert decompressed_true == uncompressed.bytes() |
| 62 | |
| 63 | compressed_false := compress(uncompressed.bytes(), checksum_flag: false)! |
| 64 | decompressed_false := decompress(compressed_false)! |
| 65 | assert decompressed_false == uncompressed.bytes() |
| 66 | } |
| 67 | |
| 68 | fn test_zstd_deferent_strategy() { |
| 69 | uncompressed := 'Hello world!'.repeat(10000) |
| 70 | |
| 71 | compressed_default := compress(uncompressed.bytes(), strategy: .default)! |
| 72 | decompressed_default := decompress(compressed_default)! |
| 73 | assert decompressed_default == uncompressed.bytes() |
| 74 | |
| 75 | compressed_fast := compress(uncompressed.bytes(), strategy: .fast)! |
| 76 | decompressed_fast := decompress(compressed_fast)! |
| 77 | assert decompressed_fast == uncompressed.bytes() |
| 78 | |
| 79 | compressed_dfast := compress(uncompressed.bytes(), strategy: .dfast)! |
| 80 | decompressed_dfast := decompress(compressed_dfast)! |
| 81 | assert decompressed_dfast == uncompressed.bytes() |
| 82 | |
| 83 | compressed_greedy := compress(uncompressed.bytes(), strategy: .greedy)! |
| 84 | decompressed_greedy := decompress(compressed_greedy)! |
| 85 | assert decompressed_greedy == uncompressed.bytes() |
| 86 | |
| 87 | compressed_lazy := compress(uncompressed.bytes(), strategy: .lazy)! |
| 88 | decompressed_lazy := decompress(compressed_lazy)! |
| 89 | assert decompressed_lazy == uncompressed.bytes() |
| 90 | |
| 91 | compressed_lazy2 := compress(uncompressed.bytes(), strategy: .lazy2)! |
| 92 | decompressed_lazy2 := decompress(compressed_lazy2)! |
| 93 | assert decompressed_lazy2 == uncompressed.bytes() |
| 94 | |
| 95 | compressed_btlazy2 := compress(uncompressed.bytes(), strategy: .btlazy2)! |
| 96 | decompressed_btlazy2 := decompress(compressed_btlazy2)! |
| 97 | assert decompressed_btlazy2 == uncompressed.bytes() |
| 98 | |
| 99 | compressed_btopt := compress(uncompressed.bytes(), strategy: .btopt)! |
| 100 | decompressed_btopt := decompress(compressed_btopt)! |
| 101 | assert decompressed_btopt == uncompressed.bytes() |
| 102 | |
| 103 | compressed_btultra := compress(uncompressed.bytes(), strategy: .btultra)! |
| 104 | decompressed_btultra := decompress(compressed_btultra)! |
| 105 | assert decompressed_btultra == uncompressed.bytes() |
| 106 | |
| 107 | compressed_btultra2 := compress(uncompressed.bytes(), strategy: .btultra2)! |
| 108 | decompressed_btultra2 := decompress(compressed_btultra2)! |
| 109 | assert decompressed_btultra2 == uncompressed.bytes() |
| 110 | } |
| 111 | |
| 112 | fn compress_file(fname string, oname string, params CompressParams) ! { |
| 113 | mut fin := os.open_file(fname, 'rb')! |
| 114 | mut fout := os.open_file(oname, 'wb')! |
| 115 | defer { |
| 116 | fin.close() |
| 117 | fout.close() |
| 118 | } |
| 119 | |
| 120 | mut buf_in := []u8{len: buf_in_size} |
| 121 | mut buf_out := []u8{len: buf_out_size} |
| 122 | |
| 123 | mut cctx := new_cctx(params)! |
| 124 | defer { |
| 125 | cctx.free_cctx() |
| 126 | } |
| 127 | |
| 128 | mut last_chunk := false |
| 129 | mut input := &InBuffer{ |
| 130 | src: buf_in.data |
| 131 | size: 0 |
| 132 | pos: 0 |
| 133 | } |
| 134 | mut output := &OutBuffer{ |
| 135 | dst: buf_out.data |
| 136 | size: 0 |
| 137 | pos: 0 |
| 138 | } |
| 139 | for !last_chunk { |
| 140 | read_len := fin.read(mut buf_in)! |
| 141 | last_chunk = read_len < buf_in_size |
| 142 | mode := if last_chunk { |
| 143 | EndDirective.end |
| 144 | } else { |
| 145 | EndDirective.continue |
| 146 | } |
| 147 | |
| 148 | mut finished := false |
| 149 | input.src = buf_in.data |
| 150 | input.size = usize(read_len) |
| 151 | input.pos = 0 |
| 152 | for !finished { |
| 153 | output.dst = buf_out.data |
| 154 | output.size = buf_out_size |
| 155 | output.pos = 0 |
| 156 | remaining := cctx.compress_stream2(output, input, mode)! |
| 157 | fout.write(buf_out[..int(output.pos)])! |
| 158 | finished = if last_chunk { remaining == 0 } else { input.pos == input.size } |
| 159 | } |
| 160 | |
| 161 | if input.pos != input.size { |
| 162 | return error('Impossible: zstd only returns 0 when the input is completely consumed!') |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | fn decompress_file(fname string, oname string, params DecompressParams) ! { |
| 168 | mut fin := os.open_file(fname, 'rb')! |
| 169 | mut fout := os.open_file(oname, 'wb')! |
| 170 | defer { |
| 171 | fin.close() |
| 172 | fout.close() |
| 173 | } |
| 174 | |
| 175 | mut buf_in := []u8{len: buf_in_size} |
| 176 | mut buf_out := []u8{len: buf_out_size} |
| 177 | |
| 178 | mut dctx := new_dctx(params)! |
| 179 | defer { |
| 180 | dctx.free_dctx() |
| 181 | } |
| 182 | |
| 183 | mut input := &InBuffer{ |
| 184 | src: buf_in.data |
| 185 | size: 0 |
| 186 | pos: 0 |
| 187 | } |
| 188 | mut output := &OutBuffer{ |
| 189 | dst: buf_out.data |
| 190 | size: 0 |
| 191 | pos: 0 |
| 192 | } |
| 193 | |
| 194 | mut last_ret := usize(0) |
| 195 | for { |
| 196 | read_len := fin.read(mut buf_in)! |
| 197 | input.src = buf_in.data |
| 198 | input.size = usize(read_len) |
| 199 | input.pos = 0 |
| 200 | for input.pos < input.size { |
| 201 | output.dst = buf_out.data |
| 202 | output.size = buf_out_size |
| 203 | output.pos = 0 |
| 204 | ret := dctx.decompress_stream(output, input)! |
| 205 | fout.write(buf_out[..int(output.pos)])! |
| 206 | last_ret = ret |
| 207 | } |
| 208 | if read_len < buf_in.len { |
| 209 | break |
| 210 | } |
| 211 | } |
| 212 | if last_ret != 0 { |
| 213 | /* The last return value from DecompressStream did not end on a |
| 214 | * frame, but we reached the end of the file! We assume this is an |
| 215 | * error, and the input was truncated. |
| 216 | */ |
| 217 | return error('EOF before end of stream: ${last_ret}') |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | // zstd stream mode test |
| 222 | fn test_zstd_stream() { |
| 223 | decompress_file(s('readme_level_19.zst'), s('tmp_file1'))! |
| 224 | compress_file(s('tmp_file1'), s('tmp_file.zstd'), |
| 225 | compression_level: 6 |
| 226 | nb_threads: 1 |
| 227 | checksum_flag: true |
| 228 | )! |
| 229 | decompress_file(s('tmp_file.zstd'), s('tmp_file2'))! |
| 230 | file1 := os.read_file(s('tmp_file1'))! |
| 231 | assert file1.contains('## Acknowledgement') |
| 232 | assert file1.contains('## Troubleshooting') |
| 233 | file2 := os.read_file(s('tmp_file2'))! |
| 234 | assert file1 == file2 |
| 235 | os.rm(s('tmp_file1'))! |
| 236 | os.rm(s('tmp_file2'))! |
| 237 | os.rm(s('tmp_file.zstd'))! |
| 238 | } |
| 239 | |
| 240 | // store_array compress an `array`'s data, and store it to file `fname`. |
| 241 | // extra compression parameters can be set by `params` |
| 242 | // WARNING: Because struct padding, some data in struct may be marked unused. |
| 243 | // So, when `store_array`, it will cause memory fsanitize fail with 'use-of-uninitialized-value'. |
| 244 | // It can be safely ignored. |
| 245 | // For example, following struct may cause memory fsanitize fail: |
| 246 | // struct MemoryTrace { |
| 247 | // operation u8 |
| 248 | // address u64 |
| 249 | // size u8 |
| 250 | // } |
| 251 | // By changing it into following , you can pass the memory fsanitize check : |
| 252 | // struct MemoryTrace { |
| 253 | // operation u64 |
| 254 | // address u64 |
| 255 | // size u64 |
| 256 | // } |
| 257 | struct MemoryTrace { |
| 258 | operation u64 |
| 259 | address u64 |
| 260 | size u64 |
| 261 | } |
| 262 | |
| 263 | fn store_array_test(fname string) ! { |
| 264 | // Create a big array |
| 265 | mut store_memory_trace := []MemoryTrace{cap: 1000} |
| 266 | for i in 0 .. 1000 { |
| 267 | store_memory_trace << MemoryTrace{ |
| 268 | operation: u64(`L`) |
| 269 | address: u64(i) |
| 270 | size: u8(i % 8) |
| 271 | } |
| 272 | } |
| 273 | store_array[MemoryTrace](fname, store_memory_trace, compression_level: 8)! |
| 274 | } |
| 275 | |
| 276 | fn load_array_test(fname string) ! { |
| 277 | load_memory_trace := load_array[MemoryTrace](fname)! |
| 278 | for i in 0 .. 1000 { |
| 279 | assert load_memory_trace[i].operation == `L` |
| 280 | assert load_memory_trace[i].address == i |
| 281 | assert load_memory_trace[i].size == u8(i % 8) |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | fn test_zstd_store_load_array() { |
| 286 | store_array_test(s('mem_trace.zstd'))! |
| 287 | load_array_test(s('mem_trace.zstd'))! |
| 288 | os.rm(s('mem_trace.zstd'))! |
| 289 | } |
| 290 | |
| 291 | fn assert_decompress_error(data []u8, reason string) ! { |
| 292 | decompress(data) or { |
| 293 | assert err.msg() == reason |
| 294 | return |
| 295 | } |
| 296 | return error('did not error') |
| 297 | } |
| 298 | |
| 299 | fn test_zstd_invalid_too_small() { |
| 300 | assert_decompress_error([]u8{}, |
| 301 | 'An error occurred (e.g. invalid magic number, srcSize too small)')! |
| 302 | } |
| 303 | |
| 304 | fn test_zstd_invalid_magic_numbers() { |
| 305 | assert_decompress_error([]u8{len: 100}, |
| 306 | 'An error occurred (e.g. invalid magic number, srcSize too small)')! |
| 307 | } |
| 308 | |
| 309 | fn test_zstd_invalid_compression() { |
| 310 | mut data := []u8{len: 100} |
| 311 | data[0] = 0x1f |
| 312 | data[1] = 0x8b |
| 313 | assert_decompress_error(data, |
| 314 | 'An error occurred (e.g. invalid magic number, srcSize too small)')! |
| 315 | } |
| 316 | |
| 317 | fn test_zstd_with_corruption1() { |
| 318 | uncompressed := 'Hello world!' |
| 319 | mut compressed := compress(uncompressed.bytes())! |
| 320 | compressed[5] = u8(0x7A) |
| 321 | assert_decompress_error(compressed, 'Data corruption detected')! |
| 322 | } |
| 323 | |
| 324 | fn test_zstd_with_corruption2() { |
| 325 | uncompressed := 'Hello world!' |
| 326 | mut compressed := compress(uncompressed.bytes())! |
| 327 | compressed[6] = u8(0x7A) |
| 328 | assert_decompress_error(compressed, 'Destination buffer is too small')! |
| 329 | } |
| 330 | |
| 331 | fn test_zstd_with_corruption3() { |
| 332 | uncompressed := 'Hello world!' |
| 333 | mut compressed := compress(uncompressed.bytes())! |
| 334 | compressed[7] = u8(0x7A) |
| 335 | assert_decompress_error(compressed, 'Src size is incorrect')! |
| 336 | } |
| 337 | |
| 338 | fn test_zstd_with_corruption4() { |
| 339 | uncompressed := 'Hello world!' |
| 340 | mut compressed := compress(uncompressed.bytes())! |
| 341 | compressed[8] = u8(0x7A) |
| 342 | assert_decompress_error(compressed, 'Src size is incorrect')! |
| 343 | } |
| 344 | |
| 345 | fn test_zstd_with_corruption5() { |
| 346 | uncompressed := 'Hello world!' |
| 347 | mut compressed := compress(uncompressed.bytes())! |
| 348 | compressed[9] = u8(0x7A) |
| 349 | assert_decompress_error(compressed, "Restored data doesn't match checksum")! |
| 350 | } |
| 351 | |
| 352 | fn test_zstd_with_corruption6() { |
| 353 | uncompressed := 'Hello world!' |
| 354 | mut compressed := compress(uncompressed.bytes())! |
| 355 | compressed[10] = u8(0x7A) |
| 356 | assert_decompress_error(compressed, "Restored data doesn't match checksum")! |
| 357 | } |
| 358 | |
| 359 | fn test_zstd_with_corruption7() { |
| 360 | uncompressed := 'Hello world!' |
| 361 | mut compressed := compress(uncompressed.bytes())! |
| 362 | compressed[compressed.len - 1] += 1 |
| 363 | assert_decompress_error(compressed, "Restored data doesn't match checksum")! |
| 364 | } |
| 365 | |