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 }