github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/nbs/index_transformer_test.go (about)

     1  // Copyright 2022 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nbs
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"fmt"
    21  	"io"
    22  	"math/rand"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"github.com/dolthub/dolt/go/libraries/utils/test"
    28  	"github.com/dolthub/dolt/go/store/hash"
    29  )
    30  
    31  // minByteReader is a copy of smallerByteReader from testing/iotest
    32  // but with a minimum read size of min bytes.
    33  
    34  type minByteReader struct {
    35  	r   io.Reader
    36  	min int
    37  
    38  	n   int
    39  	off int
    40  }
    41  
    42  func (r *minByteReader) Read(p []byte) (int, error) {
    43  	if len(p) == 0 {
    44  		return 0, nil
    45  	}
    46  
    47  	r.n = r.min + rand.Intn(r.min*100)
    48  
    49  	n := r.n
    50  	if n > len(p) {
    51  		n = len(p)
    52  	}
    53  	n, err := r.r.Read(p[0:n])
    54  	if err != nil && err != io.EOF {
    55  		err = fmt.Errorf("Read(%d bytes at offset %d): %v", n, r.off, err)
    56  	}
    57  	r.off += n
    58  	return n, err
    59  }
    60  
    61  // Altered from testing/iotest.TestReader to use minByteReader
    62  func testReader(r io.Reader, content []byte) error {
    63  	if len(content) > 0 {
    64  		n, err := r.Read(nil)
    65  		if n != 0 || err != nil {
    66  			return fmt.Errorf("Read(0) = %d, %v, want 0, nil", n, err)
    67  		}
    68  	}
    69  
    70  	data, err := io.ReadAll(&minByteReader{r: r, min: offsetSize})
    71  	if err != nil {
    72  		return err
    73  	}
    74  	if !bytes.Equal(data, content) {
    75  		return fmt.Errorf("ReadAll(varied amounts) = %q\n\twant %q", data, content)
    76  	}
    77  
    78  	n, err := r.Read(make([]byte, offsetSize))
    79  	if n != 0 || err != io.EOF {
    80  		return fmt.Errorf("Read(offsetSize) at EOF = %v, %v, want 0, EOF", n, err)
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  func get32Bytes(src []uint32) []byte {
    87  	dst := make([]byte, len(src)*uint32Size)
    88  	for i, start, end := 0, 0, lengthSize; i < len(src); i, start, end = i+1, end, end+lengthSize {
    89  		p := dst[start:end]
    90  		binary.BigEndian.PutUint32(p, src[i])
    91  	}
    92  	return dst
    93  }
    94  
    95  func get64Bytes(src []uint64) []byte {
    96  	dst := make([]byte, len(src)*uint64Size)
    97  	for i, start, end := 0, 0, offsetSize; i < len(src); i, start, end = i+1, end, end+offsetSize {
    98  		p := dst[start:end]
    99  		binary.BigEndian.PutUint64(p, src[i])
   100  	}
   101  	return dst
   102  }
   103  
   104  func randomUInt32s(n int) []uint32 {
   105  	out := make([]uint32, n)
   106  	for i := 0; i < n; i++ {
   107  		out[i] = uint32(rand.Intn(1000))
   108  	}
   109  	return out
   110  }
   111  
   112  func calcOffsets(arr []uint32) []uint64 {
   113  	out := make([]uint64, len(arr))
   114  	out[0] = uint64(arr[0])
   115  	for i := 1; i < len(arr); i++ {
   116  		out[i] = out[i-1] + uint64(arr[i])
   117  	}
   118  	return out
   119  }
   120  
   121  func TestOffsetReader(t *testing.T) {
   122  	testSize := rand.Intn(10) + 1
   123  	lengths := randomUInt32s(testSize)
   124  	offsets := calcOffsets(lengths)
   125  
   126  	lengthBytes := get32Bytes(lengths)
   127  	offsetBytes := get64Bytes(offsets)
   128  
   129  	t.Run("converts lengths into offsets", func(t *testing.T) {
   130  		lengthsReader := bytes.NewReader(lengthBytes)
   131  		offsetReader := NewOffsetsReader(lengthsReader)
   132  
   133  		err := testReader(offsetReader, offsetBytes)
   134  		require.NoError(t, err)
   135  	})
   136  
   137  	t.Run("err not enough bytes when expected", func(t *testing.T) {
   138  		lengthsReader := bytes.NewReader(lengthBytes[:len(lengthBytes)-1])
   139  		offsetReader := NewOffsetsReader(lengthsReader)
   140  		_, err := io.ReadAll(offsetReader)
   141  		require.ErrorAsf(t, err, &ErrNotEnoughBytes, "should return ErrNotEnoughBytes")
   142  	})
   143  
   144  	t.Run("fills provided buffer correctly", func(t *testing.T) {
   145  		lengthsReader := bytes.NewReader(lengthBytes)
   146  		offsetReader := NewOffsetsReader(lengthsReader)
   147  		p := make([]byte, offsetSize)
   148  		n, err := offsetReader.Read(p)
   149  		require.NoError(t, err)
   150  		require.Equal(t, offsetSize, n)
   151  	})
   152  
   153  	t.Run("works with io.ReadAll", func(t *testing.T) {
   154  		lengthsReader := bytes.NewReader(lengthBytes[:lengthSize])
   155  		offsetReader := NewOffsetsReader(lengthsReader)
   156  		data, err := io.ReadAll(offsetReader)
   157  		require.NoError(t, err)
   158  		require.True(t, bytes.Equal(data, offsetBytes[:offsetSize]))
   159  	})
   160  }
   161  
   162  func TestIndexTransformer(t *testing.T) {
   163  	chunkCount := rand.Intn(10) + 1
   164  	lengths := randomUInt32s(chunkCount)
   165  	offsets := calcOffsets(lengths)
   166  	lengthBytes := get32Bytes(lengths)
   167  	offsetBytes := get64Bytes(offsets)
   168  
   169  	tupleBytes := test.RandomData(chunkCount * prefixTupleSize)
   170  	suffixBytes := test.RandomData(chunkCount * hash.SuffixLen)
   171  
   172  	var inBytes []byte
   173  	inBytes = append(inBytes, tupleBytes...)
   174  	inBytes = append(inBytes, lengthBytes...)
   175  	inBytes = append(inBytes, suffixBytes...)
   176  
   177  	var outBytes []byte
   178  	outBytes = append(outBytes, tupleBytes...)
   179  	outBytes = append(outBytes, offsetBytes...)
   180  	outBytes = append(outBytes, suffixBytes...)
   181  
   182  	t.Run("only converts lengths into offsets", func(t *testing.T) {
   183  		inReader := bytes.NewBuffer(inBytes)
   184  		outReader := NewIndexTransformer(inReader, chunkCount)
   185  
   186  		err := testReader(outReader, outBytes)
   187  		require.NoError(t, err)
   188  	})
   189  
   190  }