github.com/grailbio/base@v0.0.11/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/grailbio/base/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  }