github.com/Schaudge/hts@v0.0.0-20240223063651-737b4d69d68c/bgzf/bgzf_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the Go LICENSE file. 4 5 // Changes copyright ©2012 The bíogo Authors. All rights reserved. 6 // Use of this source code is governed by a BSD-style 7 // license that can be found in the LICENSE file. 8 9 package bgzf_test 10 11 import ( 12 "bytes" 13 "compress/gzip" 14 "errors" 15 "flag" 16 "fmt" 17 "io" 18 "io/ioutil" 19 "os" 20 "runtime" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 . "github.com/Schaudge/hts/bgzf" 27 "github.com/Schaudge/hts/bgzf/cache" 28 ) 29 30 var ( 31 go1_8 bool 32 33 conc = flag.Int("conc", 1, "sets the level of concurrency for compression") 34 file = flag.String("bench.file", "", "bgzf file to read for benchmarking decompression") 35 ) 36 37 type countWriter struct { 38 bytes int64 39 w io.Writer 40 } 41 42 func (cw *countWriter) Write(p []byte) (n int, err error) { 43 n, err = cw.w.Write(p) 44 cw.bytes += int64(n) 45 return 46 } 47 48 // TestEmpty tests that an empty payload still forms a valid GZIP stream. 49 func TestEmpty(t *testing.T) { 50 buf := new(bytes.Buffer) 51 52 if err := NewWriter(buf, *conc).Close(); err != nil { 53 t.Fatalf("Writer.Close: %v", err) 54 } 55 56 r, err := NewReader(buf, *conc) 57 if err != nil { 58 t.Fatalf("NewReader: %v", err) 59 } 60 b, err := ioutil.ReadAll(r) 61 if err != nil { 62 t.Fatalf("ReadAll: %v", err) 63 } 64 if len(b) != 0 { 65 t.Fatalf("got %d bytes, want 0", len(b)) 66 } 67 if err := r.Close(); err != nil { 68 t.Fatalf("Reader.Close: %v", err) 69 } 70 } 71 72 type crippledReaderAt struct { 73 r *bytes.Reader 74 } 75 76 func (r crippledReaderAt) ReadAt(b []byte, off int64) (int, error) { 77 return r.r.ReadAt(b, off) 78 } 79 80 // TestEOF tests HasEOF can find the EOF magic block. 81 func TestEOF(t *testing.T) { 82 // os.File cases 83 f, err := ioutil.TempFile(os.TempDir(), "bgzf_EOF_test_") 84 if err != nil { 85 t.Fatalf("Create temp file: %v", err) 86 } 87 fname := f.Name() 88 89 if err := NewWriter(f, *conc).Close(); err != nil { 90 t.Fatalf("Writer.Close: %v", err) 91 } 92 93 f, err = os.Open(fname) 94 if err != nil { 95 t.Fatalf("Open temp file: %v", err) 96 } 97 ok, err := HasEOF(f) 98 if err != nil { 99 t.Errorf("HasEOF: %v", err) 100 } 101 if !ok { 102 t.Error("Expected EOF in os.File: not found.") 103 } 104 105 os.Remove(fname) 106 107 f, err = os.Open(os.TempDir()) 108 if err != nil { 109 t.Fatalf("Open temp dir: %v", err) 110 } 111 ok, err = HasEOF(f) 112 if want := "read " + os.TempDir() + ": is a directory"; err.Error() != want { 113 t.Errorf("Expected error:%s got:%v", want, err) 114 } 115 if ok { 116 t.Error("Unexpected EOF in os.File IsDir: found.") 117 } 118 119 // {bytes,strings}.Reader cases 120 var buf bytes.Buffer 121 if err := NewWriter(&buf, *conc).Close(); err != nil { 122 t.Fatalf("Writer.Close: %v", err) 123 } 124 125 ok, err = HasEOF(bytes.NewReader(buf.Bytes())) 126 if err != nil { 127 t.Errorf("HasEOF: %v", err) 128 } 129 if !ok { 130 t.Error("Expected EOF in []byte: not found.") 131 } 132 133 ok, err = HasEOF(strings.NewReader(buf.String())) 134 if err != nil { 135 t.Errorf("HasEOF: %v", err) 136 } 137 if !ok { 138 t.Error("Expected EOF in string: not found.") 139 } 140 141 ok, err = HasEOF(crippledReaderAt{bytes.NewReader(buf.Bytes())}) 142 if err != ErrNoEnd { 143 t.Errorf("Expected error:%s got:%v", ErrNoEnd, err) 144 } 145 if ok { 146 t.Error("Unexpected EOF in crippled ReaderAt: found.") 147 } 148 } 149 150 // TestRoundTrip tests that bgzipping and then bgunzipping is the identity 151 // function. 152 func TestRoundTrip(t *testing.T) { 153 buf := new(bytes.Buffer) 154 155 w := NewWriter(buf, *conc) 156 w.Comment = "comment" 157 w.Extra = []byte("extra") 158 w.ModTime = time.Unix(1e8, 0) 159 w.Name = "name" 160 if _, err := w.Write([]byte("payload")); err != nil { 161 t.Fatalf("Write: %v", err) 162 } 163 if err := w.Close(); err != nil { 164 t.Fatalf("Writer.Close: %v", err) 165 } 166 // FIXME(kortschak) The magic block is written on close, 167 // so we need to discount that until we have the capacity 168 // to see every header again. 169 wbl := buf.Len() - len(MagicBlock) 170 171 r, err := NewReader(buf, *conc) 172 if err != nil { 173 t.Fatalf("NewReader: %v", err) 174 } 175 176 if bl := ExpectedMemberSize(r.Header); bl != wbl { 177 t.Errorf("expectedMemberSize is %d, want %d", bl, wbl) 178 } 179 blEnc := string([]byte{byte(wbl - 1), byte((wbl - 1) >> 8)}) 180 if string(r.Extra) != "BC\x02\x00"+blEnc+"extra" { 181 t.Errorf("extra is %q, want %q", r.Extra, "BC\x02\x00"+blEnc+"extra") 182 } 183 b, err := ioutil.ReadAll(r) 184 if err != nil { 185 t.Fatalf("ReadAll: %v", err) 186 } 187 if string(b) != "payload" { 188 t.Fatalf("payload is %q, want %q", string(b), "payload") 189 } 190 if r.Comment != "comment" { 191 t.Errorf("comment is %q, want %q", r.Comment, "comment") 192 } 193 if bl := ExpectedMemberSize(r.Header); bl != len(MagicBlock) { 194 t.Errorf("expectedMemberSize is %d, want %d", bl, len(MagicBlock)) 195 } 196 if string(r.Extra) != "BC\x02\x00\x1b\x00" { 197 t.Errorf("extra is %q, want %q", r.Extra, "BC\x02\x00\x1b\x00") 198 } 199 if r.ModTime.Unix() != 1e8 { 200 t.Errorf("mtime is %d, want %d", r.ModTime.Unix(), uint32(1e8)) 201 } 202 if r.Name != "name" { 203 t.Errorf("name is %q, want %q", r.Name, "name") 204 } 205 if r.OS != 0xff { 206 t.Errorf("os is %x, want %x", r.OS, 0xff) 207 } 208 if err := r.Close(); err != nil { 209 t.Errorf("Reader.Close: %v", err) 210 } 211 } 212 213 // TestRoundTripMulti tests that bgzipping and then bgunzipping is the identity 214 // function for a multiple member bgzf. 215 func TestRoundTripMulti(t *testing.T) { 216 var wbl [2]int 217 buf := new(bytes.Buffer) 218 219 w := NewWriter(buf, *conc) 220 w.Comment = "comment" 221 w.Extra = []byte("extra") 222 w.ModTime = time.Unix(1e8, 0) 223 w.Name = "name" 224 if _, err := w.Write([]byte("payload1")); err != nil { 225 t.Fatalf("Write: %v", err) 226 } 227 if err := w.Flush(); err != nil { 228 t.Fatalf("Flush: %v", err) 229 } 230 if err := w.Wait(); err != nil { 231 t.Fatalf("Wait: %v", err) 232 } 233 wbl[0] = buf.Len() 234 if _, err := w.Write([]byte("payloadTwo")); err != nil { 235 t.Fatalf("Write: %v", err) 236 } 237 if err := w.Close(); err != nil { 238 t.Fatalf("Writer.Close: %v", err) 239 } 240 wbl[1] = buf.Len() - wbl[0] - len(MagicBlock) 241 242 var ( 243 b []byte 244 bl, n int 245 err error 246 ) 247 r, err := NewReader(buf, *conc) 248 if err != nil { 249 t.Fatalf("NewReader: %v", err) 250 } 251 252 if r.Comment != "comment" { 253 t.Errorf("comment is %q, want %q", r.Comment, "comment") 254 } 255 blEnc := string([]byte{byte(wbl[0] - 1), byte((wbl[0] - 1) >> 8)}) 256 if string(r.Extra) != "BC\x02\x00"+blEnc+"extra" { 257 t.Errorf("extra is %q, want %q", r.Extra, "BC\x02\x00"+blEnc+"extra") 258 } 259 if r.ModTime.Unix() != 1e8 { 260 t.Errorf("mtime is %d, want %d", r.ModTime.Unix(), uint32(1e8)) 261 } 262 if r.Name != "name" { 263 t.Errorf("name is %q, want %q", r.Name, "name") 264 } 265 266 bl = ExpectedMemberSize(r.Header) 267 if bl != wbl[0] { 268 t.Errorf("expectedMemberSize is %d, want %d", bl, wbl[0]) 269 } 270 b = make([]byte, len("payload1payloadTwo")) 271 n, err = r.Read(b) 272 if string(b[:n]) != "payload1payloadTwo" { 273 t.Errorf("payload is %q, want %q", string(b[:n]), "payload1payloadTwo") 274 } 275 if err != nil { 276 t.Errorf("Read: %v", err) 277 } 278 279 bl = ExpectedMemberSize(r.Header) 280 if bl != wbl[1] { 281 t.Errorf("expectedMemberSize is %d, want %d", bl, wbl[1]) 282 } 283 b = make([]byte, 1) 284 n, err = r.Read(b) 285 if string(b[:n]) != "" { 286 t.Errorf("payload is %q, want %q", string(b[:n]), "") 287 } 288 if err != io.EOF { 289 t.Errorf("Read: %v", err) 290 } 291 r.Close() 292 } 293 294 // See https://github.com/biogo/hts/issues/57 295 func TestHeaderIssue57(t *testing.T) { 296 var stamp time.Time 297 if !go1_8 { 298 unixEpoch := time.Unix(0, 0) 299 stamp = unixEpoch 300 } 301 302 var buf bytes.Buffer 303 bg := NewWriter(&buf, *conc) 304 bg.ModTime = stamp 305 bg.OS = 0xff 306 err := bg.Close() 307 if err != nil { 308 t.Fatal("error closing Writer") 309 } 310 got := buf.Bytes()[:16] 311 want := []byte(MagicBlock[:16]) 312 if !bytes.Equal(got, want) { 313 t.Fatalf("unexpected header:\ngot: %0#2v\nwant:%0#2v", got, want) 314 } 315 } 316 317 // TestRoundTripMultiSeek tests that bgzipping and then bgunzipping is the identity 318 // function for a multiple member bgzf with an underlying Seeker. 319 func TestRoundTripMultiSeek(t *testing.T) { 320 f, err := ioutil.TempFile(os.TempDir(), "bgzf_test_") 321 if err != nil { 322 t.Fatalf("Create temp file: %v", err) 323 } 324 fname := f.Name() 325 326 var wbl [2]int 327 cw := &countWriter{w: f} 328 w := NewWriter(cw, *conc) 329 w.Comment = "comment" 330 w.Extra = []byte("extra") 331 w.ModTime = time.Unix(1e8, 0) 332 w.Name = "name" 333 if _, err := w.Write([]byte("payload1")); err != nil { 334 t.Fatalf("Write: %v", err) 335 } 336 if err := w.Flush(); err != nil { 337 t.Fatalf("Flush: %v", err) 338 } 339 if err := w.Wait(); err != nil { 340 t.Fatalf("Wait: %v", err) 341 } 342 offset := cw.bytes 343 wbl[0] = int(offset) 344 if _, err := w.Write([]byte("payloadTwo")); err != nil { 345 t.Fatalf("Write: %v", err) 346 } 347 if err := w.Close(); err != nil { 348 t.Fatalf("Writer.Close: %v", err) 349 } 350 if err := f.Close(); err != nil { 351 t.Fatalf("os.File.Close: %v", err) 352 } 353 wbl[1] = int(cw.bytes-offset) - len(MagicBlock) 354 355 var ( 356 b []byte 357 bl, n int 358 ) 359 360 f, err = os.Open(fname) 361 if err != nil { 362 t.Fatalf("Reopen temp file: %v", err) 363 } 364 r, err := NewReader(f, *conc) 365 if err != nil { 366 t.Fatalf("NewReader: %v", err) 367 } 368 369 // Insert a HasEOF to ensure it does not corrupt subsequent reads. 370 HasEOF(f) 371 372 if r.Comment != "comment" { 373 t.Errorf("comment is %q, want %q", r.Comment, "comment") 374 } 375 blEnc := string([]byte{byte(wbl[0] - 1), byte((wbl[0] - 1) >> 8)}) 376 if string(r.Extra) != "BC\x02\x00"+blEnc+"extra" { 377 t.Errorf("extra is %q, want %q", r.Extra, "BC\x02\x00"+blEnc+"extra") 378 } 379 if r.ModTime.Unix() != 1e8 { 380 t.Errorf("mtime is %d, want %d", r.ModTime.Unix(), uint32(1e8)) 381 } 382 if r.Name != "name" { 383 t.Errorf("name is %q, want %q", r.Name, "name") 384 } 385 bl = ExpectedMemberSize(r.Header) 386 if bl != wbl[0] { 387 t.Errorf("expectedMemberSize is %d, want %d", bl, wbl[0]) 388 } 389 b = make([]byte, len("payload1payloadTwo")+1) 390 n, err = r.Read(b) 391 if err != io.EOF { 392 t.Errorf("Read: %v", err) 393 } 394 if bl := ExpectedMemberSize(r.Header); bl != len(MagicBlock) { 395 t.Errorf("expectedMemberSize is %d, want %d", bl, len(MagicBlock)) 396 } 397 if string(r.Extra) != "BC\x02\x00\x1b\x00" { 398 t.Errorf("extra is %q, want %q", r.Extra, "BC\x02\x00\x1b\x00") 399 } 400 if string(b[:n]) != "payload1payloadTwo" { 401 t.Errorf("payload is %q, want %q", string(b[:n]), "payload1payloadTwo") 402 } 403 if err := r.Seek(Offset{}); err != nil { 404 t.Errorf("Seek: %v", err) 405 } 406 n, err = r.Read(b) 407 if err != io.EOF { 408 t.Errorf("Read: %v", err) 409 } 410 if string(b[:n]) != "payload1payloadTwo" { 411 t.Errorf("payload is %q, want %q", string(b[:n]), "payload1payloadTwo") 412 } 413 if err := r.Seek(Offset{File: offset}); err != nil { 414 t.Fatalf("Seek: %v", err) 415 } 416 bl = ExpectedMemberSize(r.Header) 417 if bl != wbl[1] { 418 t.Errorf("expectedMemberSize is %d, want %d", bl, wbl[1]) 419 } 420 b = make([]byte, bl+1) 421 n, err = r.Read(b) 422 if err != io.EOF { 423 t.Errorf("Read: %v", err) 424 } 425 r.Close() 426 if string(b[:n]) != "payloadTwo" { 427 t.Errorf("payload is %q, want %q", string(b[:n]), "payloadTwo") 428 } 429 os.Remove(fname) 430 } 431 432 type errorReadSeeker struct { 433 r io.ReadSeeker 434 err error 435 } 436 437 func (r errorReadSeeker) Read(p []byte) (int, error) { 438 n, err := r.r.Read(p) 439 if err == nil && r.err != nil { 440 err = r.err 441 } 442 return n, err 443 } 444 445 func (r errorReadSeeker) Seek(offset int64, whence int) (int64, error) { 446 n, err := r.r.Seek(offset, whence) 447 if r.err != nil { 448 err = r.err 449 } 450 return n, err 451 } 452 453 func TestSeekErrorDeadlock(t *testing.T) { 454 buf := new(bytes.Buffer) 455 456 w := NewWriter(buf, *conc) 457 w.Comment = "comment" 458 w.Extra = []byte("extra") 459 w.ModTime = time.Unix(1e8, 0) 460 w.Name = "name" 461 if _, err := w.Write([]byte("payload")); err != nil { 462 t.Fatalf("Write: %v", err) 463 } 464 if err := w.Close(); err != nil { 465 t.Fatalf("Writer.Close: %v", err) 466 } 467 e := &errorReadSeeker{r: bytes.NewReader(buf.Bytes())} 468 r, err := NewReader(e, *conc) 469 if err != nil { 470 t.Fatalf("NewReader: %v", err) 471 } 472 r.Seek(Offset{File: 0}) 473 e.err = errors.New("bad seek error") 474 err = r.Seek(Offset{File: 1}) 475 if err == nil { 476 t.Error("Expected error.", err) 477 } 478 r.Close() 479 } 480 481 type countReadSeeker struct { 482 mu sync.Mutex 483 r io.ReadSeeker 484 _didSeek bool 485 n int64 486 } 487 488 func (r *countReadSeeker) offset() int64 { 489 r.mu.Lock() 490 defer r.mu.Unlock() 491 492 return r.n 493 } 494 495 func (r *countReadSeeker) didSeek() bool { 496 r.mu.Lock() 497 defer r.mu.Unlock() 498 499 return r._didSeek 500 } 501 502 func (r *countReadSeeker) Read(p []byte) (int, error) { 503 r.mu.Lock() 504 defer r.mu.Unlock() 505 506 r._didSeek = false 507 n, err := r.r.Read(p) 508 r.n += int64(n) 509 return n, err 510 } 511 512 func (r *countReadSeeker) Seek(offset int64, whence int) (int64, error) { 513 r.mu.Lock() 514 defer r.mu.Unlock() 515 516 r._didSeek = true 517 return r.r.Seek(offset, whence) 518 } 519 520 func TestSeekFast(t *testing.T) { 521 // Under these conditions we cannot guarantee that a worker 522 // will not read bytes after a Seek call has been made. 523 if *conc != 1 && runtime.GOMAXPROCS(0) > 1 { 524 return 525 } 526 const ( 527 infix = "payload" 528 blocks = 10 529 ) 530 531 // Use different caches. 532 for _, cache := range []Cache{ 533 nil, // Explicitly nil. 534 535 cache.NewLRU(0), // Functionally nil. 536 cache.NewLRU(1), 537 cache.NewLRU(blocks / 2), 538 cache.NewLRU(blocks), 539 cache.NewLRU(blocks + 1), 540 541 cache.NewRandom(0), // Functionally nil. 542 cache.NewRandom(1), 543 cache.NewRandom(blocks / 2), 544 cache.NewRandom(blocks), 545 cache.NewRandom(blocks + 1), 546 } { 547 var ( 548 buf bytes.Buffer 549 offsets = []int{0} 550 ) 551 w := NewWriter(&buf, 1) 552 for i := 0; i < blocks; i++ { 553 if _, err := fmt.Fprintf(w, "%d%[2]s%[1]d", i, infix); err != nil { 554 t.Fatalf("Write: %v", err) 555 } 556 if err := w.Flush(); err != nil { 557 t.Fatalf("Flush: %v", err) 558 } 559 if err := w.Wait(); err != nil { 560 t.Fatalf("Wait: %v", err) 561 } 562 offsets = append(offsets, buf.Len()) 563 } 564 w.Close() 565 offsets = offsets[:len(offsets)-1] 566 567 c := &countReadSeeker{r: bytes.NewReader(buf.Bytes())} 568 569 // Insert a HasEOF to ensure it does not corrupt subsequent reads. 570 HasEOF(bytes.NewReader(buf.Bytes())) 571 572 r, err := NewReader(c, *conc) 573 if err != nil { 574 t.Fatalf("NewReader: %v", err) 575 } 576 577 r.SetCache(cache) 578 p := make([]byte, len(infix)+2) 579 580 func() { 581 defer func() { 582 r := recover() 583 if r != nil { 584 t.Fatalf("Seek on unread reader panicked: %v", r) 585 } 586 }() 587 err := r.Seek(Offset{}) 588 if err != nil { 589 t.Fatalf("Seek: %v", err) 590 } 591 }() 592 593 // Standard read through of the data. 594 for i := range offsets { 595 n, err := r.Read(p) 596 if n != len(p) { 597 t.Fatalf("Unexpected read length: got:%d want:%d", n, len(p)) 598 } 599 if err != nil { 600 t.Fatalf("Read: %v", err) 601 } 602 got := string(p) 603 want := fmt.Sprintf("%d%[2]s%[1]d", i, infix) 604 if got != want { 605 t.Errorf("Unexpected result: got:%q want:%q", got, want) 606 } 607 } 608 609 // Seek to each block in turn 610 for i, o := range offsets { 611 err := r.Seek(Offset{File: int64(o)}) 612 if err != nil { 613 t.Fatalf("Seek: %v", err) 614 } 615 n, err := r.Read(p) 616 if n != len(p) { 617 t.Errorf("Unexpected read length: got:%d want:%d", n, len(p)) 618 } 619 if err != nil { 620 t.Fatalf("Read: %v", err) 621 } 622 got := string(p) 623 want := fmt.Sprintf("%d%[2]s%[1]d", i, infix) 624 if got != want { 625 t.Errorf("Unexpected result: got:%q want:%q", got, want) 626 } 627 } 628 629 // Seek to each block in turn, but read the infix and then the first 2 bytes. 630 for i, o := range offsets { 631 if err := r.Seek(Offset{File: int64(o), Block: 1}); err != nil { 632 t.Fatalf("Seek: %v", err) 633 } 634 p = p[:len(infix)] 635 n, err := r.Read(p) 636 if n != len(p) { 637 t.Fatalf("Unexpected read length: got:%d want:%d", n, len(p)) 638 } 639 if err != nil { 640 t.Fatalf("Read: %v", err) 641 } 642 got := string(p) 643 want := infix 644 if got != want { 645 t.Fatalf("Unexpected result: got:%q want:%q", got, want) 646 } 647 648 // Check whether the underlying reader was seeked or read. 649 hasRead := c.offset() 650 if err = r.Seek(Offset{File: int64(o), Block: 0}); err != nil { 651 t.Fatalf("Seek: %v", err) 652 } 653 if b := c.offset() - hasRead; b != 0 { 654 t.Errorf("Seek performed unexpected read: %d bytes", b) 655 } 656 if c.didSeek() { 657 t.Error("Seek caused underlying Seek.") 658 } 659 660 p = p[:2] 661 n, err = r.Read(p) 662 if n != len(p) { 663 t.Fatalf("Unexpected read length: got:%d want:%d", n, len(p)) 664 } 665 if err != nil { 666 t.Fatalf("Read: %v", err) 667 } 668 got = string(p) 669 want = fmt.Sprintf("%dp", i) 670 if got != want { 671 t.Fatalf("Unexpected result: got:%q want:%q", got, want) 672 } 673 } 674 r.Close() 675 } 676 } 677 678 func TestCache(t *testing.T) { 679 // Under these conditions we cannot guarantee that the order of 680 // blocks returned by nextBlock work will not result in additional 681 // cache puts. 682 if *conc != 1 { 683 return 684 } 685 const ( 686 infix = "payload" 687 blocks = 10 688 ) 689 690 // Each pattern is a series of seek-and-read (when the element >= 0) 691 // or read (when the element < 0). Each read is exactly one block 692 // worth of data. 693 type opPair struct{ seekBlock, blockID int } 694 patterns := []struct { 695 ops []opPair 696 697 // One for each cache case below. If new caches are added to the 698 // test list, stats must be added here. 699 expectedStats []cache.Stats 700 }{ 701 { 702 ops: []opPair{ 703 {seekBlock: -1, blockID: 0}, 704 {seekBlock: -1, blockID: 1}, 705 {seekBlock: -1, blockID: 2}, 706 {seekBlock: +0, blockID: 0}, 707 {seekBlock: -1, blockID: 1}, 708 {seekBlock: -1, blockID: 2}, 709 {seekBlock: -1, blockID: 3}, 710 {seekBlock: -1, blockID: 4}, 711 }, 712 expectedStats: []cache.Stats{ 713 {}, // nil cache. 714 {}, // nil cache: LRU(0) 715 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // LRU(1) 716 {Gets: 7, Misses: 4, Puts: 7, Retains: 7, Evictions: 0}, // LRU(5) 717 {Gets: 7, Misses: 4, Puts: 7, Retains: 7, Evictions: 0}, // LRU(10) 718 {Gets: 7, Misses: 4, Puts: 7, Retains: 7, Evictions: 0}, // LRU(11) 719 {}, // nil cache: FIFO(0) 720 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // FIFO(1) 721 {Gets: 7, Misses: 4, Puts: 7, Retains: 4, Evictions: 0}, // FIFO(5) 722 {Gets: 7, Misses: 4, Puts: 7, Retains: 4, Evictions: 0}, // FIFO(10) 723 {Gets: 7, Misses: 4, Puts: 7, Retains: 4, Evictions: 0}, // FIFO(11) 724 {}, // nil cache: Random(0) 725 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // Random(1) 726 {Gets: 7, Misses: 4, Puts: 7, Retains: 7, Evictions: 0}, // Random(5) 727 {Gets: 7, Misses: 4, Puts: 7, Retains: 7, Evictions: 0}, // Random(10) 728 {Gets: 7, Misses: 4, Puts: 7, Retains: 7, Evictions: 0}, // Random(11) 729 }, 730 }, 731 { 732 ops: []opPair{ 733 {seekBlock: -1, blockID: 0}, 734 {seekBlock: -1, blockID: 1}, 735 {seekBlock: -1, blockID: 2}, 736 {seekBlock: +1, blockID: 1}, 737 {seekBlock: -1, blockID: 2}, 738 {seekBlock: -1, blockID: 3}, 739 {seekBlock: -1, blockID: 4}, 740 {seekBlock: -1, blockID: 5}, 741 }, 742 expectedStats: []cache.Stats{ 743 {}, // nil cache. 744 {}, // nil cache. 745 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 4}, // LRU(1) 746 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 0}, // LRU(5) 747 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 0}, // LRU(10) 748 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 0}, // LRU(11) 749 {}, // nil cache: FIFO(0) 750 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 6}, // FIFO(1) 751 {Gets: 7, Misses: 5, Puts: 7, Retains: 5, Evictions: 0}, // FIFO(5) 752 {Gets: 7, Misses: 5, Puts: 7, Retains: 5, Evictions: 0}, // FIFO(10) 753 {Gets: 7, Misses: 5, Puts: 7, Retains: 5, Evictions: 0}, // FIFO(11) 754 {}, // nil cache: Random(0) 755 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 4}, // Random(1) 756 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 0}, // Random(5) 757 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 0}, // Random(10) 758 {Gets: 7, Misses: 5, Puts: 7, Retains: 7, Evictions: 0}, // Random(11) 759 }, 760 }, 761 { 762 ops: []opPair{ 763 {seekBlock: -1, blockID: 0}, 764 {seekBlock: -1, blockID: 1}, 765 {seekBlock: -1, blockID: 2}, 766 {seekBlock: +2, blockID: 2}, 767 {seekBlock: -1, blockID: 3}, 768 {seekBlock: -1, blockID: 4}, 769 {seekBlock: -1, blockID: 5}, 770 {seekBlock: -1, blockID: 6}, 771 }, 772 // Re-reading the same block avoids a cache look-up. 773 expectedStats: []cache.Stats{ 774 {}, // nil cache. 775 {}, // nil cache. 776 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 5}, // LRU(1) 777 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 1}, // LRU(5) 778 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 0}, // LRU(10) 779 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 0}, // LRU(11) 780 {}, // nil cache: FIFO(0) 781 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 5}, // FIFO(1) 782 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 1}, // FIFO(5) 783 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 0}, // FIFO(10) 784 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 0}, // FIFO(11) 785 {}, // nil cache: Random(0) 786 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 5}, // Random(1) 787 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 1}, // Random(5) 788 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 0}, // Random(10) 789 {Gets: 6, Misses: 6, Puts: 6, Retains: 6, Evictions: 0}, // Random(11) 790 }, 791 }, 792 { 793 ops: []opPair{ 794 {seekBlock: -1, blockID: 0}, 795 {seekBlock: -1, blockID: 1}, 796 {seekBlock: -1, blockID: 2}, 797 {seekBlock: +3, blockID: 3}, 798 {seekBlock: -1, blockID: 4}, 799 {seekBlock: -1, blockID: 5}, 800 {seekBlock: -1, blockID: 6}, 801 {seekBlock: -1, blockID: 7}, 802 }, 803 expectedStats: []cache.Stats{ 804 {}, // nil cache. 805 {}, // nil cache. 806 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // LRU(1) 807 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 2}, // LRU(5) 808 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // LRU(10) 809 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // LRU(11) 810 {}, // nil cache: FIFO(0) 811 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // FIFO(1) 812 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 2}, // FIFO(5) 813 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // FIFO(10) 814 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // FIFO(11) 815 {}, // nil cache: Random(0) 816 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // Random(1) 817 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 2}, // Random(5) 818 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // Random(10) 819 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // Random(11) 820 }, 821 }, 822 { 823 ops: []opPair{ 824 {seekBlock: -1, blockID: 0}, 825 {seekBlock: -1, blockID: 1}, 826 {seekBlock: -1, blockID: 2}, 827 {seekBlock: +4, blockID: 4}, 828 {seekBlock: -1, blockID: 5}, 829 {seekBlock: -1, blockID: 6}, 830 {seekBlock: -1, blockID: 7}, 831 {seekBlock: -1, blockID: 8}, 832 }, 833 expectedStats: []cache.Stats{ 834 {}, // nil cache. 835 {}, // nil cache. 836 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // LRU(1) 837 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 2}, // LRU(5) 838 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // LRU(10) 839 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // LRU(11) 840 {}, // nil cache: FIFO(0) 841 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // FIFO(1) 842 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 2}, // FIFO(5) 843 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // FIFO(10) 844 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // FIFO(11) 845 {}, // nil cache: Random(0) 846 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 6}, // Random(1) 847 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 2}, // Random(5) 848 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // Random(10) 849 {Gets: 7, Misses: 7, Puts: 7, Retains: 7, Evictions: 0}, // Random(11) 850 }, 851 }, 852 { 853 ops: []opPair{ 854 {seekBlock: -1, blockID: 0}, 855 {seekBlock: -1, blockID: 1}, 856 {seekBlock: -1, blockID: 2}, 857 {seekBlock: +1, blockID: 1}, 858 {seekBlock: +2, blockID: 2}, 859 {seekBlock: +1, blockID: 1}, 860 {seekBlock: +1, blockID: 1}, 861 {seekBlock: -1, blockID: 2}, 862 {seekBlock: +7, blockID: 7}, 863 {seekBlock: -1, blockID: 8}, 864 {seekBlock: -1, blockID: 9}, 865 }, 866 expectedStats: []cache.Stats{ 867 {}, // nil cache. 868 {}, // nil cache. 869 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 4}, // LRU(1) 870 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 0}, // LRU(5) 871 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 0}, // LRU(10) 872 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 0}, // LRU(11) 873 {}, // nil cache: FIFO(0) 874 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 8}, // FIFO(1) 875 {Gets: 9, Misses: 5, Puts: 9, Retains: 5, Evictions: 0}, // FIFO(5) 876 {Gets: 9, Misses: 5, Puts: 9, Retains: 5, Evictions: 0}, // FIFO(10) 877 {Gets: 9, Misses: 5, Puts: 9, Retains: 5, Evictions: 0}, // FIFO(11) 878 {}, // nil cache: Random(0) 879 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 4}, // Random(1) 880 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 0}, // Random(5) 881 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 0}, // Random(10) 882 {Gets: 9, Misses: 5, Puts: 9, Retains: 9, Evictions: 0}, // Random(11) 883 }, 884 }, 885 } 886 887 for k, pat := range patterns { 888 // Use different caches. 889 for j, s := range []Cache{ 890 nil, // Explicitly nil. 891 892 cache.NewLRU(0), // Functionally nil. 893 cache.NewLRU(1), 894 cache.NewLRU(blocks / 2), 895 cache.NewLRU(blocks), 896 cache.NewLRU(blocks + 1), 897 898 cache.NewFIFO(0), // Functionally nil. 899 cache.NewFIFO(1), 900 cache.NewFIFO(blocks / 2), 901 cache.NewFIFO(blocks), 902 cache.NewFIFO(blocks + 1), 903 904 cache.NewRandom(0), // Functionally nil. 905 cache.NewRandom(1), 906 cache.NewRandom(blocks / 2), 907 cache.NewRandom(blocks), 908 cache.NewRandom(blocks + 1), 909 } { 910 var ( 911 buf bytes.Buffer 912 offsets = []int{0} 913 ) 914 w := NewWriter(&buf, 1) 915 for i := 0; i < blocks; i++ { 916 if _, err := fmt.Fprintf(w, "%d%[2]s%[1]d", i, infix); err != nil { 917 t.Fatalf("Write: %v", err) 918 } 919 if err := w.Flush(); err != nil { 920 t.Fatalf("Flush: %v", err) 921 } 922 if err := w.Wait(); err != nil { 923 t.Fatalf("Wait: %v", err) 924 } 925 offsets = append(offsets, buf.Len()) 926 } 927 w.Close() 928 offsets = offsets[:len(offsets)-1] 929 930 br := bytes.NewReader(buf.Bytes()) 931 // Insert a HasEOF to ensure it does not corrupt subsequent reads. 932 HasEOF(br) 933 934 r, err := NewReader(br, *conc) 935 if err != nil { 936 t.Fatalf("NewReader: %v", err) 937 } 938 var stats *cache.StatsRecorder 939 if s != nil { 940 stats = &cache.StatsRecorder{Cache: s} 941 s = stats 942 } 943 r.SetCache(s) 944 p := make([]byte, len(infix)+2) 945 946 for _, op := range pat.ops { 947 if op.seekBlock >= 0 { 948 err := r.Seek(Offset{File: int64(offsets[op.seekBlock])}) 949 if err != nil { 950 t.Fatalf("Seek: %v", err) 951 } 952 } 953 n, err := r.Read(p) 954 if n != len(p) { 955 t.Errorf("Unexpected read length: got:%d want:%d", n, len(p)) 956 } 957 if err != nil { 958 t.Fatalf("Read: %v", err) 959 } 960 got := string(p) 961 want := fmt.Sprintf("%d%[2]s%[1]d", op.blockID, infix) 962 if got != want { 963 t.Errorf("Unexpected result: got:%q want:%q", got, want) 964 } 965 } 966 if stats != nil && stats.Stats() != pat.expectedStats[j] { 967 t.Errorf("Unexpected result for cache %d pattern %d: got:%+v want:%+v", j, k, stats.Stats(), pat.expectedStats[j]) 968 } 969 r.Close() 970 } 971 } 972 } 973 974 func TestBlocked(t *testing.T) { 975 const ( 976 infix = "payload" 977 blocks = 10 978 ) 979 980 for _, blocked := range []bool{false, true} { 981 var ( 982 buf bytes.Buffer 983 want bytes.Buffer 984 ) 985 w := NewWriter(&buf, 1) 986 for i := 0; i < blocks; i++ { 987 if _, err := fmt.Fprintf(w, "%d%[2]s%[1]d\n", i, infix); err != nil { 988 t.Fatalf("Write: %v", err) 989 } 990 if err := w.Flush(); err != nil { 991 t.Fatalf("Flush: %v", err) 992 } 993 if _, err := fmt.Fprintf(&want, "%d%[2]s%[1]d\n", i, infix); err != nil { 994 t.Fatalf("Write: %v", err) 995 } 996 } 997 err := w.Close() 998 if err != nil { 999 t.Fatalf("unexpected error on Close: %v", err) 1000 } 1001 1002 r, err := NewReader(bytes.NewReader(buf.Bytes()), *conc) 1003 if err != nil { 1004 t.Fatalf("NewReader: %v", err) 1005 } 1006 r.Blocked = blocked 1007 1008 p := make([]byte, len(infix)) 1009 var ( 1010 got []byte 1011 gotBlocks int 1012 ) 1013 for { 1014 n, err := r.Read(p) 1015 got = append(got, p[:n]...) 1016 if err != nil { 1017 if err == io.EOF && n != 0 { 1018 gotBlocks++ 1019 continue 1020 } 1021 break 1022 } 1023 } 1024 if !blocked && gotBlocks != 1 { 1025 t.Errorf("unexpected number of blocks:\n\tgot:%d\n\twant:%d", gotBlocks, 1) 1026 } 1027 if blocked && gotBlocks != blocks { 1028 t.Errorf("unexpected number of blocks:\n\tgot:%d\n\twant:%d", gotBlocks, blocks) 1029 } 1030 if !bytes.Equal(got, want.Bytes()) { 1031 t.Errorf("unexpected result:\n\tgot:%q\n\twant:%q", got, want.Bytes()) 1032 } 1033 r.Close() 1034 } 1035 } 1036 1037 var fuzzCrashers = []string{ 1038 // Invalid block size. 1039 "\x1f\x8b\bu0000000\x0000000000" + 1040 "000000000BC\x02\x000\x0000000" + 1041 "00000000000000000000" + 1042 "00000000000000000000" + 1043 "000000000000000000\x00", 1044 "\x1f\x8b\b\xc4000000V\x0000000000" + 1045 "00000000000000000000" + 1046 "00000000000000000000" + 1047 "00000000000000000000" + 1048 "000000000000BC\x02\x00w\x00\x030" + 1049 "\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x8b\bu000000\x00\x00" + 1050 "\x1f\x8b\bu000000\b\x00BC\x02\x00\x00\x0000" + 1051 "\x00", 1052 1053 // Zero block size. 1054 "\x1f\x8b\bu000000V\x0000000000" + 1055 "00000000000000000000" + 1056 "00000000000000000000" + 1057 "00000000000000000000" + 1058 "000000000000BC\x02\x00k\x0000" + 1059 "0000000\x00", 1060 "\x1f\x8b\bu\xe8k\x15k\x00sV\x00bcdefghi" + 1061 "jklmnxpq\xc49\xbf\x1f\x8b\x0f\a/\x85\xba\xb0Q" + 1062 "\xef (\x01\xbd\xbf\xefrde\a/\x85fghmjt\x00" + 1063 "\xff\x00\x00v\x97x\x92zB\x80\x00142261869" + 1064 "48039093abxdBC\x02\x00i\x00sV" + 1065 "\xbbghmj\x00\x00", 1066 } 1067 1068 func TestFuzzCrashers(t *testing.T) { 1069 for i, test := range fuzzCrashers { 1070 func() { 1071 i := i 1072 defer func() { 1073 r := recover() 1074 if r != nil { 1075 t.Errorf("unexpected panic for crasher %d: %v", i, r) 1076 } 1077 }() 1078 r, err := NewReader(strings.NewReader(test), 0) 1079 switch err { 1080 case nil: 1081 // Pass through. 1082 case io.EOF, ErrCorrupt: 1083 return 1084 default: 1085 t.Fatalf("unexpected error creating reader: %v", err) 1086 } 1087 tmp := make([]byte, 1024) 1088 for { 1089 _, err := r.Read(tmp) 1090 if err != nil { 1091 break 1092 } 1093 } 1094 }() 1095 } 1096 } 1097 1098 func TestZeroNonZero(t *testing.T) { 1099 const wrote = "second block" 1100 buf := bytes.NewBuffer([]byte(MagicBlock)) 1101 w := NewWriter(buf, 1) 1102 _, err := w.Write([]byte(wrote)) 1103 if err != nil { 1104 w.Close() 1105 t.Fatalf("unexpected error writing second block: %v", err) 1106 } 1107 err = w.Close() 1108 if err != nil { 1109 t.Fatalf("unexpected error closing writer: %v", err) 1110 } 1111 r, err := NewReader(buf, 1) 1112 if err != nil { 1113 t.Fatalf("unexpected error opening reader: %v", err) 1114 } 1115 defer r.Close() 1116 var b [1024]byte 1117 var got []byte 1118 for { 1119 n, err := r.Read(b[:]) 1120 got = append(got, b[:n]...) 1121 if err != nil { 1122 break 1123 } 1124 } 1125 if string(got) != wrote { 1126 t.Errorf("unexpected round trip: got:%q want:%q", got, wrote) 1127 } 1128 } 1129 1130 type zero struct{} 1131 1132 func (z zero) Read(p []byte) (int, error) { 1133 for i := range p { 1134 p[i] = 0 1135 } 1136 return len(p), nil 1137 } 1138 1139 func TestWriteByteCount(t *testing.T) { 1140 cw, _ := NewWriterLevel(ioutil.Discard, gzip.BestCompression, 4) 1141 defer cw.Close() 1142 n, err := io.Copy(cw, &io.LimitedReader{R: new(zero), N: 100000}) 1143 if n != 100000 { 1144 t.Errorf("Unexpected number of bytes, got:%d, want:%d", n, 100000) 1145 } 1146 if err != nil { 1147 t.Errorf("Unexpected error got:%v", err) 1148 } 1149 } 1150 1151 func BenchmarkWrite(b *testing.B) { 1152 bg := NewWriter(ioutil.Discard, *conc) 1153 block := bytes.Repeat([]byte("repeated"), 50) 1154 for i := 0; i < b.N; i++ { 1155 for j := 0; j < 1000000; j++ { 1156 bg.Write(block) 1157 } 1158 bg.Wait() 1159 } 1160 } 1161 1162 func BenchmarkRead(b *testing.B) { 1163 if *file == "" { 1164 b.Skip("no bgzf file specified") 1165 } 1166 f, err := os.Open(*file) 1167 if err != nil { 1168 b.Fatalf("file open failed: %v", err) 1169 } 1170 defer f.Close() 1171 1172 buf := make([]byte, 16384) 1173 b.ResetTimer() 1174 1175 for i := 0; i < b.N; i++ { 1176 f.Seek(0, os.SEEK_SET) 1177 bg, err := NewReader(f, *conc) 1178 if err != nil { 1179 b.Fatalf("bgzf open failed: %v", err) 1180 } 1181 for { 1182 _, err = bg.Read(buf) 1183 if err == io.EOF { 1184 break 1185 } 1186 if err != nil { 1187 b.Fatalf("bgzf read failed: %v", err) 1188 } 1189 } 1190 bg.Close() 1191 } 1192 }