1// Copyright 2015, Joe Tsai. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE.md file. 4 5package xflate 6 7import ( 8 "bytes" 9 "io" 10 "io/ioutil" 11 "math" 12 "math/rand" 13 "testing" 14 15 "github.com/dsnet/compress/internal/errors" 16 "github.com/dsnet/compress/internal/testutil" 17) 18 19type countReadSeeker struct { 20 io.ReadSeeker 21 N int64 22} 23 24func (rs *countReadSeeker) Read(buf []byte) (int, error) { 25 n, err := rs.ReadSeeker.Read(buf) 26 rs.N += int64(n) 27 return n, err 28} 29 30func TestReader(t *testing.T) { 31 dh := testutil.MustDecodeHex 32 33 errFuncs := map[string]func(error) bool{ 34 "IsCorrupted": errors.IsCorrupted, 35 } 36 vectors := []struct { 37 desc string // Description of the test 38 input []byte // Input test string 39 output []byte // Expected output string 40 errf string // Name of error checking callback 41 }{{ 42 desc: "empty string", 43 errf: "IsCorrupted", 44 }, { 45 desc: "empty stream", 46 input: dh("" + 47 "0d008705000048c82a51e8ff37dbf1", 48 ), 49 }, { 50 desc: "empty stream with empty chunk", 51 input: dh("" + 52 "000000ffff000000ffff34c086050020916cb2a50bd20369da192deaff3bda05" + 53 "f81dc08605002021ab44219b4aff7fd6de3bf8", 54 ), 55 }, { 56 desc: "empty stream with empty index", 57 input: dh("" + 58 "04c086050020191d53a1a508c9e8ff5bda7bf815c08605002021ab44219ba2ff" + 59 "2f6bef5df8", 60 ), 61 }, { 62 desc: "empty stream with multiple empty chunks", 63 input: dh("" + 64 "000000ffff000000ffff000000ffff148086058044655366e3817441ba205d50" + 65 "4a83348c445ddcde7b6ffc15c08605002021ab44a103aaff2f6bef5df8", 66 ), 67 }, { 68 desc: "empty stream with multiple empty chunks, with final bit", 69 input: dh("" + 70 "000000ffff010000ffff000000ffff148086058044655366e3817441ba205d50" + 71 "4a83348c445ddcde7b6ffc15c08605002021ab44a103aaff2f6bef5df8", 72 ), 73 errf: "IsCorrupted", 74 }, { 75 desc: "empty stream with multiple empty indexes", 76 input: dh("" + 77 "04c086050020191d53a1a508c9e8ff5bda7bf83cc08605002019293a24a55464" + 78 "a585faff9bf600f804c08605002019493a2494d050560afd7f4c7bfb25008705" + 79 "000048c82a51e880f4ff834df0", 80 ), 81 }, { 82 desc: "3k zeros, 1KiB chunks", 83 input: dh("" + 84 "621805a360148c5800000000ffff621805a360148c5800000000ffff621805a3" + 85 "60140c3900000000ffff1c8086058044642b3bc9aa3464540784acea809055d9" + 86 "9586dd5492446555a7b607fc0d008705000048c82a51c81ea1ff0f6cf2", 87 ), 88 output: make([]byte, 3000), 89 }, { 90 desc: "quick brown fox - spec example", 91 input: dh("" + 92 "0ac94855282ccd4cce560028a928bf3c4f212dbf4201a0acd2dc82d41485fcb2" + 93 "d42205804a80f2398955950a00000000ffff4ac94f5704000000ffff24808605" + 94 "8084b247b60629218a48486656d2b442ca489fb7f7de0bfc3cc08605002019a1" + 95 "3aa454548a122ad5fff7b403f815c08605002021ab44219ba4ff2f6bef5df8", 96 ), 97 output: []byte("The quick brown fox jumped over the lazy dog!"), 98 }, { 99 desc: "quick brown fox - manual chunking/indexing", 100 input: dh("" + 101 "2ac94855282ccd4cce06000000ffff52482aca2fcf5348cbaf00000000ffff00" + 102 "0000ffff52c82acd2d484d51c82f4b2d5228c94805000000ffff248086058044" + 103 "6553762a0ad14211d207253b234546a1528ad4d3edbd0bfc52c849acaa5448c9" + 104 "4f07000000ffff2c8086058044a281ec8611190d23b21221ca0851fdafbdf7de" + 105 "05fc1dc08605002021ab44219b52ff7fd6de3bf8", 106 ), 107 output: []byte("the quick brown fox jumped over the lazy dog"), 108 }, { 109 desc: "quick brown fox - automatic chunking/indexing", 110 input: dh("" + 111 "2ac9485500000000ffff2a2ccd4c06000000ffffca56482a02000000ffff2c80" + 112 "86058044655376c32a2b9999c9cc4c665691d04ea5a474747bef01fcca2fcf53" + 113 "00000000ffff4acbaf5000000000ffffca2acd2d00000000ffff048086058044" + 114 "45036537acb2929999cccc6466cb48112a45a193db7beffc4a4d51c807000000" + 115 "ffff2a4b2d5200000000ffff2ac9485500000000ffff04808605804445036537" + 116 "acb2929999cccc6466cb48112a45a193db7beffcca49acaa04000000ffff5248" + 117 "c94f07000000ffff148086058084a261644b665632339399d9425629a44877b7" + 118 "f7de3bfc15c08605002021ab44a103aaff2f6bef5df8", 119 ), 120 output: []byte("the quick brown fox jumped over the lazy dog"), 121 }, { 122 desc: "alphabet", 123 input: dh("" + 124 "4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff" + 125 "048086058044b2e98190b285148a844a0b95a4f7db7bef3dfc15c08605002021" + 126 "ab44219ba8ff2f6bef5df8", 127 ), 128 output: []byte("abcdefghijklmnopqrstuvwxyz"), 129 }, { 130 desc: "garbage footer", 131 input: dh("5174453181b67484bf6de23a608876f8b7f44c77"), 132 errf: "IsCorrupted", 133 }, { 134 desc: "corrupt meta footer", 135 input: dh("1d008705000048ca2c50e8ff3bdbf0"), 136 errf: "IsCorrupted", 137 }, { 138 desc: "trailing meta data in footer", 139 input: dh("0d008705000048c82a51e8ff37dbf1deadcafe"), 140 errf: "IsCorrupted", 141 }, { 142 desc: "trailing raw data in footer", 143 input: dh("25c086050020a9ac12856ec8284229d4ff0fb527f8"), 144 errf: "IsCorrupted", 145 }, { 146 desc: "footer using LastMeta", 147 input: dh("0c008705000048c82a51e8ff37dbf1"), 148 errf: "IsCorrupted", 149 }, { 150 desc: "footer without magic", 151 input: dh("1d00870500004864a644eaff3bdbf0"), 152 errf: "IsCorrupted", 153 }, { 154 desc: "footer with VLI overflow", 155 input: dh("2d80860580944a458a4abb6e6c9fdbde7bef01fc"), 156 errf: "IsCorrupted", 157 }, { 158 desc: "index using LastStream", 159 input: dh("" + 160 "05c086050020191d53a1a508c9e8ff5bda7bf815c08605002021ab44219ba2ff" + 161 "2f6bef5df8", 162 ), 163 errf: "IsCorrupted", 164 }, { 165 desc: "index with wrong CRC", 166 input: dh("" + 167 "2cc086050020191d132551320a51ff9fd2de0bf825008705000048c82a51e880" + 168 "f4ff834df0", 169 ), 170 errf: "IsCorrupted", 171 }, { 172 desc: "corrupt meta index", 173 input: dh("" + 174 "04c086050020191d53a1a518c9e8ff5bda7bf815c08605002021ab44219ba2ff" + 175 "2f6bef5df8", 176 ), 177 errf: "IsCorrupted", 178 }, { 179 desc: "index with VLI overflow", 180 input: dh("" + 181 "048086058094e8c6f6de7b531215458a840e6deffc15c08605002021ab44219b" + 182 "a4ff2f6bef5df8", 183 ), 184 errf: "IsCorrupted", 185 }, { 186 desc: "trailing meta data in index", 187 input: dh("" + 188 "34c086050020291d53a1a508c908a16414a2fe3fa205f81dc08605002021ab44" + 189 "219b4aff7fd6de3bf8", 190 ), 191 errf: "IsCorrupted", 192 }, { 193 desc: "trailing raw data in index", 194 input: dh("" + 195 "04c086050020191d53a1a508c9e8ff5bda7bf862616405c08605002021ab4421" + 196 "7b94febfacbd77f9", 197 ), 198 errf: "IsCorrupted", 199 }, { 200 desc: "index total size is wrong", 201 input: dh("" + 202 "000000ffff14c086050020916cb2d505e983840aa12592faff8c76f81dc08605" + 203 "002021ab44219b4aff7fd6de3bf8", 204 ), 205 errf: "IsCorrupted", 206 }, { 207 desc: "index with compressed chunk size of zero", 208 input: dh("" + 209 "000000ffff04c086050020916cb2e9848e8894a2a441fd7f457bf905c0860500" + 210 "2021ab44217b94febfacbd77f9", 211 ), 212 errf: "IsCorrupted", 213 }, { 214 desc: "index with numeric overflow on sizes", 215 input: dh("" + 216 "000000ffff000000ffff0c40860552a43db4a53dcf6b97b47724641589a84e69" + 217 "efbdf7de7b4ffe1dc08605002021ab44219b54ff7fd6de3bf8", 218 ), 219 errf: "IsCorrupted", 220 }, { 221 desc: "empty chunk without sync marker", 222 input: dh("" + 223 "000000ffff020820800004c086050020a1ec919d1e4817a40b421269a3a8ff1f" + 224 "68fa2d008705000048c82a51e881faffc126f0", 225 ), 226 errf: "IsCorrupted", 227 }, { 228 desc: "chunk without sync marker", 229 input: dh("" + 230 "000000ffff000200fdff486902082080000cc086050020a1ec91193232d30965" + 231 "652b2b221125f5ff1eedf805c08605002021ab44217ba4febfacbd77f9", 232 ), 233 output: []byte("Hi"), 234 errf: "IsCorrupted", 235 }, { 236 desc: "chunk with wrong sizes", 237 input: dh("" + 238 "000000ffff000200fdff4869000000ffff2c8086058084b2476608d9e98432b2" + 239 "15252a958a92eaeef6de7b07fc15c08605002021ab44a103aaff2f6bef5df8", 240 ), 241 output: []byte("Hi"), 242 errf: "IsCorrupted", 243 }, { 244 desc: "size overflow across multiple indexes", 245 input: dh("" + 246 "000000ffff0c8086058094b487b6b4ce4b5ae7150d49d124195dd29efc000000" + 247 "ffff000000ffff24808605808432cac84e4676ba2059d9914a4a29259a8fb7f7" + 248 "de0bfc15c08605002021ab44a103aaff2f6bef5df8", 249 ), 250 errf: "IsCorrupted", 251 }, { 252 desc: "index back size causes integer overflow", 253 input: dh("" + 254 "4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff" + 255 "048086058044b2e98190b285148a844a0b95a4f7db7bef3dfc4a4c4a4e494d4b" + 256 "cfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff2c8086058094e8" + 257 "bcb4a74ab4538986529284cc3e6def05fc2d008705000048c82a51e881faffc1" + 258 "26f0"), 259 errf: "IsCorrupted", 260 }, { 261 desc: "raw chunk with final bit and bad size", 262 input: dh("" + 263 "010900f6ff0000ffff248086058044b2c98e8cc8888cc828ed9d284afa7fb4f7" + 264 "de0bfc05c08605002021ab44217ba4febfacbd77f9", 265 ), 266 output: dh("0000ffff010000ffff"), 267 // TODO(dsnet): The Reader incorrectly believes that this is valid. 268 // The chunk has a final raw block with a specified size of 9, but only 269 // has 4 bytes following it (0000ffff to fool the sync check). 270 // Since the decompressor would expect an additional 5 bytes, this is 271 // satisfied by the fact that the chunkReader appends the endBlock 272 // sequence (010000ffff) to every chunk. This really difficult to fix 273 // without low-level details about the DEFLATE stream. 274 errf: "", // "IsCorrupted", 275 }} 276 277 for i, v := range vectors { 278 var xr *Reader 279 var err error 280 var buf []byte 281 282 xr, err = NewReader(bytes.NewReader(v.input), nil) 283 if err != nil { 284 goto done 285 } 286 287 buf, err = ioutil.ReadAll(xr) 288 if err != nil { 289 goto done 290 } 291 292 done: 293 if v.errf != "" && !errFuncs[v.errf](err) { 294 t.Errorf("test %d (%s), mismatching error:\ngot %v\nwant %s(err) == true", i, v.desc, err, v.errf) 295 } else if v.errf == "" && err != nil { 296 t.Errorf("test %d (%s), unexpected error: got %v", i, v.desc, err) 297 } 298 if got, want, ok := testutil.BytesCompare(buf, v.output); !ok && err == nil { 299 t.Errorf("test %d (%s), mismatching output:\ngot %s\nwant %s", i, v.desc, got, want) 300 } 301 } 302} 303 304func TestReaderReset(t *testing.T) { 305 var ( 306 empty = testutil.MustDecodeHex("0d008705000048c82a51e8ff37dbf1") 307 badSize = testutil.MustDecodeHex("" + 308 "4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff" + 309 "3c8086058084b2e981acd0203b2b34884a834a2a91d2ededbd7701fc15c08605" + 310 "002021ab44a103aaff2f6bef5df8", 311 ) 312 badData = testutil.MustDecodeHex("" + 313 "4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2baf000002000000ffff" + 314 "048086058044b2e98190b285148a844a0b95a4f7db7bef3dfc15c08605002021" + 315 "ab44219ba8ff2f6bef5df8", 316 ) 317 ) 318 319 // Test Reader for idempotent Close. 320 xr := new(Reader) 321 if err := xr.Reset(bytes.NewReader(empty)); err != nil { 322 t.Fatalf("unexpected error: Reset() = %v", err) 323 } 324 buf, err := ioutil.ReadAll(xr) 325 if err != nil { 326 t.Fatalf("unexpected error: ReadAll() = %v", err) 327 } 328 if len(buf) > 0 { 329 t.Fatalf("unexpected output data: ReadAll() = %q, want nil", buf) 330 } 331 if err := xr.Close(); err != nil { 332 t.Fatalf("unexpected error: Close() = %v", err) 333 } 334 if err := xr.Close(); err != nil { 335 t.Fatalf("unexpected error: Close() = %v", err) 336 } 337 if _, err := ioutil.ReadAll(xr); err != errClosed { 338 t.Fatalf("mismatching error: ReadAll() = %v, want %v", err, errClosed) 339 } 340 341 // Test Reset on garbage data. 342 rd := bytes.NewReader(append([]byte("garbage"), empty...)) 343 if err := xr.Reset(rd); !errors.IsCorrupted(err) { 344 t.Fatalf("mismatching error: Reset() = %v, want IsCorrupted(err) == true", err) 345 } 346 if _, err := xr.Seek(0, io.SeekStart); !errors.IsCorrupted(err) { 347 t.Fatalf("mismatching error: Seek() = %v, want IsCorrupted(err) == true", err) 348 } 349 if err := xr.Close(); !errors.IsCorrupted(err) { 350 t.Fatalf("mismatching error: Close() = %v, want IsCorrupted(err) == true", err) 351 } 352 353 // Test Reset on corrupt data in discard section. 354 for i, v := range [][]byte{badData, badSize} { 355 if err := xr.Reset(bytes.NewReader(v)); err != nil { 356 t.Fatalf("test %d, unexpected error: Reset() = %v", i, err) 357 } 358 if _, err := xr.Seek(-1, io.SeekEnd); err != nil { 359 t.Fatalf("test %d, unexpected error: Seek() = %v", i, err) 360 } 361 if _, err = ioutil.ReadAll(xr); !errors.IsCorrupted(err) { 362 t.Fatalf("test %d, mismatching error: ReadAll() = %v, want IsCorrupted(err) == true", i, err) 363 } 364 } 365} 366 367func TestReaderSeek(t *testing.T) { 368 rand := rand.New(rand.NewSource(0)) 369 twain := testutil.MustLoadFile("../testdata/twain.txt") 370 371 // Generate compressed version of input file. 372 var buf bytes.Buffer 373 xw, err := NewWriter(&buf, &WriterConfig{ChunkSize: 1 << 10}) 374 if err != nil { 375 t.Fatalf("unexpected error: NewWriter() = %v", err) 376 } 377 if _, err := xw.Write(twain); err != nil { 378 t.Fatalf("unexpected error: Write() = %v", err) 379 } 380 if err := xw.Close(); err != nil { 381 t.Fatalf("unexpected error: Close() = %v", err) 382 } 383 384 // Read the compressed file. 385 rs := &countReadSeeker{ReadSeeker: bytes.NewReader(buf.Bytes())} 386 xr, err := NewReader(rs, nil) 387 if err != nil { 388 t.Fatalf("unexpected error: NewReader() = %v", err) 389 } 390 391 // As a heuristic, make sure we are not reading too much data. 392 if thres := int64(len(twain) / 100); rs.N > thres { 393 t.Fatalf("read more data than expected: %d > %d", rs.N, thres) 394 } 395 rs.N = 0 // Reset the read count 396 397 // Generate list of seek commands to try. 398 type seekCommand struct { 399 length int // Number of bytes to read 400 offset int64 // Seek to this offset 401 whence int // Whence value to use 402 } 403 vectors := []seekCommand{ 404 {length: 40, offset: int64(len(twain)) - 1, whence: io.SeekStart}, 405 {length: 40, offset: int64(len(twain)), whence: io.SeekStart}, 406 {length: 40, offset: int64(len(twain)) + 1, whence: io.SeekStart}, 407 {length: 40, offset: math.MaxInt64, whence: io.SeekStart}, 408 {length: 0, offset: 0, whence: io.SeekCurrent}, 409 {length: 13, offset: 15, whence: io.SeekStart}, 410 {length: 32, offset: 23, whence: io.SeekCurrent}, 411 {length: 32, offset: -23, whence: io.SeekCurrent}, 412 {length: 13, offset: -15, whence: io.SeekStart}, 413 {length: 100, offset: -15, whence: io.SeekEnd}, 414 {length: 0, offset: 0, whence: io.SeekCurrent}, 415 {length: 0, offset: 0, whence: io.SeekCurrent}, 416 {length: 32, offset: -34, whence: io.SeekCurrent}, 417 {length: 32, offset: -34, whence: io.SeekCurrent}, 418 {length: 2000, offset: 53, whence: io.SeekStart}, 419 {length: 2000, offset: int64(len(twain)) - 1000, whence: io.SeekStart}, 420 {length: 0, offset: 0, whence: io.SeekCurrent}, 421 {length: 100, offset: -int64(len(twain)), whence: io.SeekEnd}, 422 {length: 100, offset: -int64(len(twain)) - 1, whence: io.SeekEnd}, 423 {length: 0, offset: 0, whence: io.SeekStart}, 424 {length: 10, offset: 10, whence: io.SeekCurrent}, 425 {length: 10, offset: 10, whence: io.SeekCurrent}, 426 {length: 10, offset: 10, whence: io.SeekCurrent}, 427 {length: 10, offset: 10, whence: io.SeekCurrent}, 428 {length: 0, offset: 0, whence: -1}, 429 } 430 431 // Add random values to seek list. 432 for i := 0; i < 100; i++ { 433 length, offset := rand.Intn(1<<11), rand.Int63n(int64(len(twain))) 434 if length+int(offset) <= len(twain) { 435 vectors = append(vectors, seekCommand{length, offset, io.SeekStart}) 436 } 437 } 438 439 // Read in reverse. 440 vectors = append(vectors, seekCommand{0, 0, io.SeekEnd}) 441 for pos := int64(len(twain)); pos > 0; { 442 n := int64(rand.Intn(1 << 11)) 443 if n > pos { 444 n = pos 445 } 446 pos -= n 447 vectors = append(vectors, seekCommand{int(n), pos, io.SeekStart}) 448 } 449 450 // Execute all seek commands. 451 var pos, totalLength int64 452 for i, v := range vectors { 453 // Emulate Seek logic. 454 var wantPos int64 455 switch v.whence { 456 case io.SeekStart: 457 wantPos = v.offset 458 case io.SeekCurrent: 459 wantPos = v.offset + pos 460 case io.SeekEnd: 461 wantPos = v.offset + int64(len(twain)) 462 default: 463 wantPos = -1 464 } 465 466 // Perform actually (short-circuit if seek fails). 467 wantFail := bool(wantPos < 0) 468 gotPos, err := xr.Seek(v.offset, v.whence) 469 if gotFail := bool(err != nil); gotFail != wantFail { 470 if gotFail { 471 t.Fatalf("test %d, unexpected failure: Seek(%d, %d) = (%d, %v)", i, v.offset, v.whence, pos, err) 472 } else { 473 t.Fatalf("test %d, unexpected success: Seek(%d, %d) = (%d, nil)", i, v.offset, v.whence, pos) 474 } 475 } 476 if wantFail { 477 continue 478 } 479 if gotPos != wantPos { 480 t.Fatalf("test %d, offset mismatch: got %d, want %d", i, gotPos, wantPos) 481 } 482 483 // Read and verify some length of bytes. 484 var want []byte 485 if wantPos < int64(len(twain)) { 486 want = twain[wantPos:] 487 } 488 if len(want) > v.length { 489 want = want[:v.length] 490 } 491 got, err := ioutil.ReadAll(io.LimitReader(xr, int64(v.length))) 492 if err != nil { 493 t.Fatalf("test %v, unexpected error: ReadAll() = %v", i, err) 494 } 495 if got, want, ok := testutil.BytesCompare(got, want); !ok { 496 t.Fatalf("test %v, mismatching output:\ngot %s\nwant %s", i, got, want) 497 } 498 499 pos = gotPos + int64(len(got)) 500 totalLength += int64(v.length) 501 } 502 503 // As a heuristic, make sure we are not reading too much data. 504 if thres := 2 * totalLength; rs.N > thres { 505 t.Fatalf("read more data than expected: %d > %d", rs.N, thres) 506 } 507} 508 509func TestRecursiveReader(t *testing.T) { 510 twain := testutil.MustLoadFile("../testdata/twain.txt") 511 512 const numIters = 5 513 var bb bytes.Buffer 514 515 // Recursively compress the same input data multiple times using XFLATE. 516 // Run as a closured function to ensure defer statements execute. 517 func() { 518 wlast := io.Writer(&bb) // Latest writer 519 for i := 0; i < numIters; i++ { 520 xw, err := NewWriter(wlast, &WriterConfig{ChunkSize: 1 << uint(10+i)}) 521 if err != nil { 522 t.Fatalf("unexpected error: NewWriter() = %v", err) 523 } 524 defer func() { 525 if err := xw.Close(); err != nil { 526 t.Fatalf("unexpected error: Close() = %v", err) 527 } 528 }() 529 wlast = xw 530 } 531 if _, err := wlast.Write(twain); err != nil { 532 t.Fatalf("unexpected error: Write() = %v", err) 533 } 534 }() 535 536 // Recursively decompress the same input stream multiple times. 537 func() { 538 rlast := io.ReadSeeker(bytes.NewReader(bb.Bytes())) 539 for i := 0; i < numIters; i++ { 540 xr, err := NewReader(rlast, nil) 541 if err != nil { 542 t.Fatalf("unexpected error: NewReader() = %v", err) 543 } 544 defer func() { 545 if err := xr.Close(); err != nil { 546 t.Fatalf("unexpected error: Close() = %v", err) 547 } 548 }() 549 rlast = xr 550 } 551 552 buf := make([]byte, 321) 553 if _, err := rlast.Seek(int64(len(twain))/2, io.SeekStart); err != nil { 554 t.Fatalf("unexpected error: Seek() = %v", err) 555 } 556 if _, err := io.ReadFull(rlast, buf); err != nil { 557 t.Fatalf("unexpected error: Read() = %v", err) 558 } 559 if got, want := string(buf), string(twain[len(twain)/2:][:321]); got != want { 560 t.Errorf("output mismatch:\ngot %q\nwant %q", got, want) 561 } 562 }() 563} 564 565// BenchmarkReader benchmarks the overhead of the XFLATE format over DEFLATE. 566// Thus, it intentionally uses a very small chunk size with no compression. 567// This benchmark reads the input file in reverse to excite poor behavior. 568func BenchmarkReader(b *testing.B) { 569 rand := rand.New(rand.NewSource(0)) 570 twain := testutil.MustLoadFile("../testdata/twain.txt") 571 bb := bytes.NewBuffer(make([]byte, 0, 2*len(twain))) 572 xr := new(Reader) 573 lr := new(io.LimitedReader) 574 575 xw, _ := NewWriter(bb, &WriterConfig{Level: NoCompression, ChunkSize: 1 << 10}) 576 xw.Write(twain) 577 xw.Close() 578 579 b.ReportAllocs() 580 b.SetBytes(int64(len(twain))) 581 b.ResetTimer() 582 583 for i := 0; i < b.N; i++ { 584 rand.Seed(0) 585 rd := bytes.NewReader(bb.Bytes()) 586 if err := xr.Reset(rd); err != nil { 587 b.Fatalf("unexpected error: Reset() = %v", err) 588 } 589 590 // Read sections of the input in reverse. 591 for pos := int64(len(twain)); pos > 0; { 592 // Random section size. 593 n := int64(rand.Intn(1 << 11)) 594 if n > pos { 595 n = pos 596 } 597 pos -= n 598 599 // Read the given section. 600 if _, err := xr.Seek(pos, io.SeekStart); err != nil { 601 b.Fatalf("unexpected error: Seek() = %v", err) 602 } 603 *lr = io.LimitedReader{R: xr, N: n} 604 if _, err := io.Copy(ioutil.Discard, lr); err != nil { 605 b.Fatalf("unexpected error: Copy() = %v", err) 606 } 607 } 608 } 609} 610