github.com/grailbio/base@v0.0.11/logio/logio_test.go (about) 1 // Copyright 2019 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 package logio 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "math/rand" 12 "testing" 13 ) 14 15 func TestLogIO(t *testing.T) { 16 sizes := records(100, 128<<10) 17 // Make sure that there are some "tricky" sizes in here, to exercise 18 // all of the code paths. 19 sizes[10] = Blocksz // doesn't fit by a small margin 20 sizes[11] = Blocksz - headersz // exact fit 21 sizes[12] = Blocksz - headersz - 2 // underfit by less than headersz; next entry requires padding 22 23 var ( 24 buf bytes.Buffer 25 scratch []byte 26 ) 27 w := NewWriter(&buf, 0) 28 for _, sz := range sizes { 29 scratch = data(scratch, sz) 30 must(t, w.Append(scratch)) 31 } 32 33 r := NewReader(&buf, 0) 34 for i, sz := range sizes { 35 t.Logf("record %d size %d", i, sz) 36 rec, err := r.Read() 37 mustf(t, err, "record %d (size %d)", i, sz) 38 if got, want := len(rec), sz; got != want { 39 t.Errorf("got %v, want %v", got, want) 40 } 41 mustData(t, rec) 42 } 43 mustEOF(t, r) 44 } 45 46 func TestResync(t *testing.T) { 47 var ( 48 sizes = records(20, 128<<10) 49 scratch []byte 50 buf bytes.Buffer 51 ) 52 w := NewWriter(&buf, 0) 53 for _, sz := range sizes { 54 scratch = data(scratch, sz) 55 must(t, w.Append(scratch)) 56 } 57 buf.Bytes()[1]++ 58 r := NewReader(&buf, 0) 59 var i int 60 for i = range sizes { 61 rec, err := r.Read() 62 if err == ErrCorrupted { 63 break 64 } 65 must(t, err) 66 if got, want := len(rec), sizes[i]; got != want { 67 t.Errorf("got %v, want %v", got, want) 68 } 69 mustData(t, rec) 70 } 71 if i == len(sizes) { 72 t.Fatal("corrupted record not detected") 73 } 74 rec, err := r.Read() 75 mustf(t, err, "failed to recover from corrupted record") 76 mustData(t, rec) 77 j := i 78 for ; i < len(sizes); i++ { 79 if len(rec) == sizes[i] { 80 i++ 81 break 82 } 83 } 84 if i == len(sizes) { 85 t.Fatal("failed to resync") 86 } 87 t.Logf("skipped %d records", i-j) 88 for ; i < len(sizes); i++ { 89 rec, err := r.Read() 90 mustf(t, err, "record %d/%d", i, len(sizes)) 91 mustData(t, rec) 92 } 93 mustEOF(t, r) 94 } 95 96 func TestRewind(t *testing.T) { 97 sizes := records(50, 128<<10) 98 var ( 99 buf bytes.Buffer 100 scratch []byte 101 ) 102 w := NewWriter(&buf, 0) 103 for _, sz := range sizes { 104 scratch = data(scratch, sz) 105 must(t, w.Append(scratch)) 106 } 107 var ( 108 rd = bytes.NewReader(buf.Bytes()) 109 off = int64(rd.Len()) 110 ) 111 for n := 1; n <= 10; n++ { 112 var err error 113 off, err = Rewind(rd, off) 114 must(t, err) 115 // Check that Rewind also seeked rd to the correct offset. 116 seekPos, err := rd.Seek(0, io.SeekCurrent) 117 must(t, err) 118 if got, want := off, seekPos; got != want { 119 t.Fatalf("got %v, want %v", got, want) 120 } 121 r := NewReader(rd, off) 122 for i, sz := range sizes[len(sizes)-n:] { 123 rec, err := r.Read() 124 must(t, err) 125 if got, want := len(rec), sz; got != want { 126 t.Fatalf("%d,%d: got %v, want %v", n, i, got, want) 127 } 128 mustData(t, rec) 129 } 130 mustEOF(t, r) 131 } 132 } 133 134 func records(n, max int) []int { 135 if n > max { 136 panic("n > max") 137 } 138 var ( 139 recs = make([]int, n) 140 stride = max / n 141 ) 142 for i := range recs { 143 recs[i] = 1 + stride*i 144 } 145 r := rand.New(rand.NewSource(int64(n + max))) 146 r.Shuffle(n, func(i, j int) { recs[i], recs[j] = recs[j], recs[i] }) 147 return recs 148 } 149 150 func data(scratch []byte, n int) []byte { 151 if n <= cap(scratch) { 152 scratch = scratch[:n] 153 } else { 154 scratch = make([]byte, n) 155 } 156 r := rand.New(rand.NewSource(int64(n))) 157 for i := range scratch { 158 scratch[i] = byte(r.Intn(256)) 159 } 160 return scratch 161 } 162 163 func mustData(t *testing.T, b []byte) { 164 t.Helper() 165 r := rand.New(rand.NewSource(int64(len(b)))) 166 for i := range b { 167 if got, want := int(b[i]), r.Intn(256); got != want { 168 t.Fatalf("byte %d: got %v, want %v", i, got, want) 169 } 170 } 171 } 172 173 func mustEOF(t *testing.T, r *Reader) { 174 t.Helper() 175 _, err := r.Read() 176 if got, want := err, io.EOF; got != want { 177 t.Errorf("got %v, want %v", got, want) 178 } 179 } 180 181 func must(t *testing.T, err error) { 182 t.Helper() 183 if err != nil { 184 t.Fatal(err) 185 } 186 } 187 188 func mustf(t *testing.T, err error, format string, v ...interface{}) { 189 t.Helper() 190 if err != nil { 191 t.Fatalf("%s: %v", fmt.Sprintf(format, v...), err) 192 } 193 }