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  }