github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/morebufio/readseeker_test.go (about) 1 package morebufio 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "math/rand" 9 "strings" 10 "testing" 11 12 "github.com/Schaudge/grailbase/ioctx" 13 "github.com/grailbio/testutil/assert" 14 ) 15 16 func TestReadSeeker(t *testing.T) { 17 ctx := context.Background() 18 const file = "0123456789" 19 t.Run("read_zero", func(t *testing.T) { 20 r := NewReadSeekerSize(ioctx.FromStdReadSeeker(bytes.NewReader([]byte(file))), 4) 21 var b []byte 22 n, err := r.Read(ctx, b) 23 assert.NoError(t, err) 24 assert.EQ(t, n, 0) 25 }) 26 t.Run("read", func(t *testing.T) { 27 r := NewReadSeekerSize(ioctx.FromStdReadSeeker(bytes.NewReader([]byte(file))), 4) 28 b := make([]byte, 4) 29 30 n, err := r.Read(ctx, b) 31 assert.NoError(t, err) 32 assert.GE(t, n, 0) 33 assert.LE(t, n, len(b)) 34 assert.EQ(t, b[:n], []byte(file[:n])) 35 remaining := file[n:] 36 37 n, err = r.Read(ctx, b) 38 assert.NoError(t, err) 39 assert.GE(t, n, 0) 40 assert.LE(t, n, len(b)) 41 assert.EQ(t, b[:n], []byte(remaining[:n])) 42 }) 43 t.Run("seek", func(t *testing.T) { 44 r := NewReadSeekerSize(ioctx.FromStdReadSeeker(bytes.NewReader([]byte(file))), 4) 45 b := make([]byte, 4) 46 47 n, err := io.ReadFull(ioctx.ToStdReadSeeker(ctx, r), b) 48 assert.NoError(t, err) 49 assert.EQ(t, n, len(b)) 50 assert.EQ(t, b, []byte(file[:4])) 51 52 n64, err := r.Seek(ctx, -2, io.SeekCurrent) 53 assert.NoError(t, err) 54 assert.EQ(t, int(n64), 2) 55 56 n, err = io.ReadFull(ioctx.ToStdReadSeeker(ctx, r), b) 57 assert.NoError(t, err) 58 assert.EQ(t, n, len(b)) 59 assert.EQ(t, b, []byte(file[2:6])) 60 }) 61 t.Run("regression_early_eof", func(t *testing.T) { 62 // Regression test for an issue discovered during NewReaderAt development. 63 // We construct a read seeker that returns EOF after filling the internal buffer. 64 // In this case we get that behavior from the string reader's ReadAt method, adapted 65 // into a seeker. This exposed a bug where readSeeker returned the EOF from filling its 66 // internal buffer even if the client hadn't read that far yet. 67 rawRS := &readerAtSeeker{r: ioctx.FromStdReaderAt(strings.NewReader(file))} 68 r := NewReadSeekerSize(rawRS, len(file)+1) 69 b := make([]byte, 4) 70 71 n, err := r.Read(ctx, b) 72 assert.NoError(t, err) 73 assert.EQ(t, n, len(b)) 74 assert.EQ(t, b, []byte(file[:4])) 75 }) 76 } 77 78 func TestReadSeekerRandom(t *testing.T) { 79 const ( 80 fileSize = 10000 81 testOps = 100000 82 ) 83 ctx := context.Background() 84 rnd := rand.New(rand.NewSource(1)) 85 file := func() string { 86 b := make([]byte, fileSize) 87 _, _ = rnd.Read(b) 88 return string(b) 89 }() 90 for _, bufSize := range []int{1, 16, 1024, fileSize * 2} { 91 t.Run(fmt.Sprint(bufSize), func(t *testing.T) { 92 var ( 93 gold, test ioctx.ReadSeeker 94 pos int 95 ) 96 reinit := func() { 97 pos = 0 98 gold = ioctx.FromStdReadSeeker(bytes.NewReader([]byte(file))) 99 testBase := bytes.NewReader([]byte(file)) 100 if rnd.Intn(2) == 1 { 101 // Exercise initializing in the middle of a file. 102 pos = rnd.Intn(fileSize) 103 nGold, err := gold.Seek(ctx, int64(pos), io.SeekStart) 104 assert.NoError(t, err) 105 assert.EQ(t, nGold, int64(pos)) 106 nTest, err := testBase.Seek(int64(pos), io.SeekStart) 107 assert.NoError(t, err) 108 assert.EQ(t, nTest, int64(pos)) 109 } 110 test = NewReadSeekerSize(ioctx.FromStdReadSeeker(testBase), bufSize) 111 } 112 reinit() 113 ops := []func(){ 114 reinit, 115 func() { // read 116 n := len(file) - pos 117 if n > 0 { 118 n = rnd.Intn(n) 119 } 120 bGold := make([]byte, n) 121 bTest := make([]byte, len(bGold)) 122 123 nGold, errGold := io.ReadFull(ioctx.ToStdReadSeeker(ctx, gold), bGold) 124 nTest, errTest := io.ReadFull(ioctx.ToStdReadSeeker(ctx, test), bTest) 125 pos += nGold 126 127 assert.EQ(t, nTest, nGold) 128 assert.NoError(t, errGold) 129 assert.NoError(t, errTest) 130 }, 131 func() { // seek current 132 off := rnd.Intn(len(file)) - pos 133 134 nGold, errGold := gold.Seek(ctx, int64(off), io.SeekCurrent) 135 nTest, errTest := test.Seek(ctx, int64(off), io.SeekCurrent) 136 pos = int(nGold) 137 138 assert.EQ(t, nTest, nGold) 139 assert.NoError(t, errGold) 140 assert.NoError(t, errTest) 141 }, 142 func() { // seek start 143 off := rnd.Intn(len(file)) 144 145 nGold, errGold := gold.Seek(ctx, int64(off), io.SeekStart) 146 nTest, errTest := test.Seek(ctx, int64(off), io.SeekStart) 147 pos = int(nGold) 148 149 assert.EQ(t, nTest, nGold) 150 assert.NoError(t, errGold) 151 assert.NoError(t, errTest) 152 }, 153 func() { // seek end 154 off := -rnd.Intn(len(file)) 155 156 nGold, errGold := gold.Seek(ctx, int64(off), io.SeekEnd) 157 nTest, errTest := test.Seek(ctx, int64(off), io.SeekEnd) 158 pos = int(nGold) 159 160 assert.EQ(t, nTest, nGold) 161 assert.NoError(t, errGold) 162 assert.NoError(t, errTest) 163 }, 164 } 165 for i := 0; i < testOps; i++ { 166 ops[rnd.Intn(len(ops))]() 167 } 168 }) 169 } 170 }