github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/nbs/mem_table_test.go (about) 1 // Copyright 2019 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 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package nbs 23 24 import ( 25 "bytes" 26 "context" 27 "io/ioutil" 28 "os" 29 "testing" 30 31 "github.com/golang/snappy" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 "golang.org/x/sync/errgroup" 35 36 "github.com/dolthub/dolt/go/store/chunks" 37 "github.com/dolthub/dolt/go/store/d" 38 "github.com/dolthub/dolt/go/store/types" 39 ) 40 41 var testMDChunks = []chunks.Chunk{ 42 mustChunk(types.EncodeValue(types.String("Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, "), types.Format_7_18)), 43 mustChunk(types.EncodeValue(types.String("and nothing particular to interest me on shore, I thought I would sail about a little and see the watery "), types.Format_7_18)), 44 mustChunk(types.EncodeValue(types.String("part of the world. It is a way I have of driving off the spleen and regulating the "), types.Format_7_18)), 45 mustChunk(types.EncodeValue(types.String("circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly "), types.Format_7_18)), 46 mustChunk(types.EncodeValue(types.String("November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing "), types.Format_7_18)), 47 mustChunk(types.EncodeValue(types.String("funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires "), types.Format_7_18)), 48 mustChunk(types.EncodeValue(types.String("a strong moral principle to prevent me from deliberately stepping into the street, and methodically "), types.Format_7_18)), 49 mustChunk(types.EncodeValue(types.String("knocking people’s hats off—then, I account it high time to get to sea as soon as I can."), types.Format_7_18)), 50 } 51 52 var testMDChunksSize uint64 53 54 func init() { 55 for _, chunk := range testMDChunks { 56 testMDChunksSize += uint64(len(chunk.Data())) 57 } 58 } 59 60 func mustChunk(chunk chunks.Chunk, err error) chunks.Chunk { 61 d.PanicIfError(err) 62 return chunk 63 } 64 65 func TestWriteChunks(t *testing.T) { 66 name, data, err := WriteChunks(testMDChunks) 67 if err != nil { 68 t.Error(err) 69 } 70 71 dir, err := ioutil.TempDir("", "write_chunks_test") 72 if err != nil { 73 t.Error(err) 74 } 75 76 err = ioutil.WriteFile(dir+name, data, os.ModePerm) 77 if err != nil { 78 t.Error(err) 79 } 80 } 81 82 func TestMemTableAddHasGetChunk(t *testing.T) { 83 assert := assert.New(t) 84 mt := newMemTable(1024) 85 86 chunks := [][]byte{ 87 []byte("hello2"), 88 []byte("goodbye2"), 89 []byte("badbye2"), 90 } 91 92 for _, c := range chunks { 93 assert.True(mt.addChunk(computeAddr(c), c)) 94 } 95 96 assertChunksInReader(chunks, mt, assert) 97 98 for _, c := range chunks { 99 data, err := mt.get(context.Background(), computeAddr(c), &Stats{}) 100 require.NoError(t, err) 101 assert.Equal(bytes.Compare(c, data), 0) 102 } 103 104 notPresent := []byte("nope") 105 assert.False(mt.has(computeAddr(notPresent))) 106 assert.Nil(mt.get(context.Background(), computeAddr(notPresent), &Stats{})) 107 } 108 109 func TestMemTableAddOverflowChunk(t *testing.T) { 110 memTableSize := uint64(1024) 111 112 assert := assert.New(t) 113 big := make([]byte, memTableSize) 114 little := []byte{0x01} 115 { 116 bigAddr := computeAddr(big) 117 mt := newMemTable(memTableSize) 118 assert.True(mt.addChunk(bigAddr, big)) 119 assert.True(mt.has(bigAddr)) 120 assert.False(mt.addChunk(computeAddr(little), little)) 121 assert.False(mt.has(computeAddr(little))) 122 } 123 124 { 125 big := big[:memTableSize-1] 126 bigAddr := computeAddr(big) 127 mt := newMemTable(memTableSize) 128 assert.True(mt.addChunk(bigAddr, big)) 129 assert.True(mt.has(bigAddr)) 130 assert.True(mt.addChunk(computeAddr(little), little)) 131 assert.True(mt.has(computeAddr(little))) 132 other := []byte("o") 133 assert.False(mt.addChunk(computeAddr(other), other)) 134 assert.False(mt.has(computeAddr(other))) 135 } 136 } 137 138 func TestMemTableWrite(t *testing.T) { 139 assert := assert.New(t) 140 mt := newMemTable(1024) 141 142 chunks := [][]byte{ 143 []byte("hello2"), 144 []byte("goodbye2"), 145 []byte("badbye2"), 146 } 147 148 for _, c := range chunks { 149 assert.True(mt.addChunk(computeAddr(c), c)) 150 } 151 152 td1, _, err := buildTable(chunks[1:2]) 153 require.NoError(t, err) 154 ti1, err := parseTableIndex(td1) 155 require.NoError(t, err) 156 tr1 := newTableReader(ti1, tableReaderAtFromBytes(td1), fileBlockSize) 157 assert.True(tr1.has(computeAddr(chunks[1]))) 158 159 td2, _, err := buildTable(chunks[2:]) 160 require.NoError(t, err) 161 ti2, err := parseTableIndex(td2) 162 require.NoError(t, err) 163 tr2 := newTableReader(ti2, tableReaderAtFromBytes(td2), fileBlockSize) 164 assert.True(tr2.has(computeAddr(chunks[2]))) 165 166 _, data, count, err := mt.write(chunkReaderGroup{tr1, tr2}, &Stats{}) 167 require.NoError(t, err) 168 assert.Equal(uint32(1), count) 169 170 ti, err := parseTableIndex(data) 171 require.NoError(t, err) 172 outReader := newTableReader(ti, tableReaderAtFromBytes(data), fileBlockSize) 173 assert.True(outReader.has(computeAddr(chunks[0]))) 174 assert.False(outReader.has(computeAddr(chunks[1]))) 175 assert.False(outReader.has(computeAddr(chunks[2]))) 176 } 177 178 type tableReaderAtAdapter struct { 179 *bytes.Reader 180 } 181 182 func tableReaderAtFromBytes(b []byte) tableReaderAt { 183 return tableReaderAtAdapter{bytes.NewReader(b)} 184 } 185 186 func (adapter tableReaderAtAdapter) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error) { 187 return adapter.ReadAt(p, off) 188 } 189 190 func TestMemTableSnappyWriteOutOfLine(t *testing.T) { 191 assert := assert.New(t) 192 mt := newMemTable(1024) 193 194 chunks := [][]byte{ 195 []byte("hello2"), 196 []byte("goodbye2"), 197 []byte("badbye2"), 198 } 199 200 for _, c := range chunks { 201 assert.True(mt.addChunk(computeAddr(c), c)) 202 } 203 mt.snapper = &outOfLineSnappy{[]bool{false, true, false}} // chunks[1] should trigger a panic 204 205 assert.Panics(func() { mt.write(nil, &Stats{}) }) 206 } 207 208 type outOfLineSnappy struct { 209 policy []bool 210 } 211 212 func (o *outOfLineSnappy) Encode(dst, src []byte) []byte { 213 outOfLine := false 214 if len(o.policy) > 0 { 215 outOfLine = o.policy[0] 216 o.policy = o.policy[1:] 217 } 218 if outOfLine { 219 return snappy.Encode(nil, src) 220 } 221 return snappy.Encode(dst, src) 222 } 223 224 type chunkReaderGroup []chunkReader 225 226 func (crg chunkReaderGroup) has(h addr) (bool, error) { 227 for _, haver := range crg { 228 ok, err := haver.has(h) 229 230 if err != nil { 231 return false, err 232 } 233 if ok { 234 return true, nil 235 } 236 } 237 238 return false, nil 239 } 240 241 func (crg chunkReaderGroup) get(ctx context.Context, h addr, stats *Stats) ([]byte, error) { 242 for _, haver := range crg { 243 if data, err := haver.get(ctx, h, stats); err != nil { 244 return nil, err 245 } else if data != nil { 246 return data, nil 247 } 248 } 249 250 return nil, nil 251 } 252 253 func (crg chunkReaderGroup) hasMany(addrs []hasRecord) (bool, error) { 254 for _, haver := range crg { 255 remaining, err := haver.hasMany(addrs) 256 257 if err != nil { 258 return false, err 259 } 260 261 if !remaining { 262 return false, nil 263 } 264 } 265 return true, nil 266 } 267 268 func (crg chunkReaderGroup) getMany(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(*chunks.Chunk), stats *Stats) (bool, error) { 269 for _, haver := range crg { 270 remaining, err := haver.getMany(ctx, eg, reqs, found, stats) 271 if err != nil { 272 return true, err 273 } 274 if !remaining { 275 return false, nil 276 } 277 } 278 return true, nil 279 } 280 281 func (crg chunkReaderGroup) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(CompressedChunk), stats *Stats) (bool, error) { 282 for _, haver := range crg { 283 remaining, err := haver.getManyCompressed(ctx, eg, reqs, found, stats) 284 if err != nil { 285 return true, err 286 } 287 if !remaining { 288 return false, nil 289 } 290 } 291 return true, nil 292 } 293 294 func (crg chunkReaderGroup) count() (count uint32, err error) { 295 for _, haver := range crg { 296 count += mustUint32(haver.count()) 297 } 298 return 299 } 300 301 func (crg chunkReaderGroup) uncompressedLen() (data uint64, err error) { 302 for _, haver := range crg { 303 data += mustUint64(haver.uncompressedLen()) 304 } 305 return 306 } 307 308 func (crg chunkReaderGroup) extract(ctx context.Context, chunks chan<- extractRecord) error { 309 for _, haver := range crg { 310 err := haver.extract(ctx, chunks) 311 312 if err != nil { 313 return err 314 } 315 } 316 317 return nil 318 } 319 320 func (crg chunkReaderGroup) Close() error { 321 var firstErr error 322 for _, c := range crg { 323 err := c.Close() 324 if err != nil && firstErr == nil { 325 firstErr = err 326 } 327 } 328 return firstErr 329 }