github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/nbs/file_table_persister_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 "context" 26 "crypto/rand" 27 "fmt" 28 "io/ioutil" 29 "os" 30 "path/filepath" 31 "testing" 32 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 ) 36 37 func TestFSTableCacheOnOpen(t *testing.T) { 38 assert := assert.New(t) 39 dir := makeTempDir(t) 40 defer os.RemoveAll(dir) 41 42 names := []addr{} 43 cacheSize := 2 44 fc := newFDCache(cacheSize) 45 defer fc.Drop() 46 fts := newFSTablePersister(dir, fc, nil) 47 48 // Create some tables manually, load them into the cache 49 func() { 50 for i := 0; i < cacheSize; i++ { 51 name, err := writeTableData(dir, []byte{byte(i)}) 52 require.NoError(t, err) 53 names = append(names, name) 54 } 55 for _, name := range names { 56 _, err := fts.Open(context.Background(), name, 1, nil) 57 require.NoError(t, err) 58 } 59 }() 60 61 // Tables should still be cached and on disk 62 for i, name := range names { 63 src, err := fts.Open(context.Background(), name, 1, nil) 64 require.NoError(t, err) 65 h := computeAddr([]byte{byte(i)}) 66 assert.True(src.has(h)) 67 } 68 69 // Kick a table out of the cache 70 name, err := writeTableData(dir, []byte{0xff}) 71 require.NoError(t, err) 72 _, err = fts.Open(context.Background(), name, 1, nil) 73 require.NoError(t, err) 74 75 present := fc.reportEntries() 76 // Since 0 refcount entries are evicted randomly, the only thing we can validate is that fc remains at its target size 77 assert.Len(present, cacheSize) 78 79 err = fc.ShrinkCache() 80 require.NoError(t, err) 81 err = removeTables(dir, names...) 82 require.NoError(t, err) 83 } 84 85 func makeTempDir(t *testing.T) string { 86 dir, err := ioutil.TempDir("", "") 87 require.NoError(t, err) 88 return dir 89 } 90 91 func writeTableData(dir string, chunx ...[]byte) (addr, error) { 92 tableData, name, err := buildTable(chunx) 93 94 if err != nil { 95 return addr{}, err 96 } 97 98 err = ioutil.WriteFile(filepath.Join(dir, name.String()), tableData, 0666) 99 100 if err != nil { 101 return addr{}, err 102 } 103 104 return name, nil 105 } 106 107 func removeTables(dir string, names ...addr) error { 108 for _, name := range names { 109 if err := os.Remove(filepath.Join(dir, name.String())); err != nil { 110 return err 111 } 112 } 113 return nil 114 } 115 116 func TestFSTablePersisterPersist(t *testing.T) { 117 assert := assert.New(t) 118 dir := makeTempDir(t) 119 defer os.RemoveAll(dir) 120 fc := newFDCache(defaultMaxTables) 121 defer fc.Drop() 122 fts := newFSTablePersister(dir, fc, nil) 123 124 src, err := persistTableData(fts, testChunks...) 125 require.NoError(t, err) 126 if assert.True(mustUint32(src.count()) > 0) { 127 buff, err := ioutil.ReadFile(filepath.Join(dir, mustAddr(src.hash()).String())) 128 require.NoError(t, err) 129 ti, err := parseTableIndex(buff) 130 require.NoError(t, err) 131 tr := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize) 132 assertChunksInReader(testChunks, tr, assert) 133 } 134 } 135 136 func persistTableData(p tablePersister, chunx ...[]byte) (src chunkSource, err error) { 137 mt := newMemTable(testMemTableSize) 138 for _, c := range chunx { 139 if !mt.addChunk(computeAddr(c), c) { 140 return nil, fmt.Errorf("memTable too full to add %s", computeAddr(c)) 141 } 142 } 143 return p.Persist(context.Background(), mt, nil, &Stats{}) 144 } 145 146 func TestFSTablePersisterPersistNoData(t *testing.T) { 147 assert := assert.New(t) 148 mt := newMemTable(testMemTableSize) 149 existingTable := newMemTable(testMemTableSize) 150 151 for _, c := range testChunks { 152 assert.True(mt.addChunk(computeAddr(c), c)) 153 assert.True(existingTable.addChunk(computeAddr(c), c)) 154 } 155 156 dir := makeTempDir(t) 157 defer os.RemoveAll(dir) 158 fc := newFDCache(defaultMaxTables) 159 defer fc.Drop() 160 fts := newFSTablePersister(dir, fc, nil) 161 162 src, err := fts.Persist(context.Background(), mt, existingTable, &Stats{}) 163 require.NoError(t, err) 164 assert.True(mustUint32(src.count()) == 0) 165 166 _, err = os.Stat(filepath.Join(dir, mustAddr(src.hash()).String())) 167 assert.True(os.IsNotExist(err), "%v", err) 168 } 169 170 func TestFSTablePersisterCacheOnPersist(t *testing.T) { 171 assert := assert.New(t) 172 dir := makeTempDir(t) 173 fc := newFDCache(1) 174 defer fc.Drop() 175 fts := newFSTablePersister(dir, fc, nil) 176 defer os.RemoveAll(dir) 177 178 var name addr 179 func() { 180 src, err := persistTableData(fts, testChunks...) 181 require.NoError(t, err) 182 name = mustAddr(src.hash()) 183 }() 184 185 // Table should still be cached 186 src, err := fts.Open(context.Background(), name, uint32(len(testChunks)), nil) 187 require.NoError(t, err) 188 assertChunksInReader(testChunks, src, assert) 189 190 // Evict |name| from cache 191 _, err = persistTableData(fts, []byte{0xff}) 192 require.NoError(t, err) 193 194 present := fc.reportEntries() 195 // Since 0 refcount entries are evicted randomly, the only thing we can validate is that fc remains at its target size 196 assert.Len(present, 1) 197 198 err = removeTables(dir, name) 199 require.NoError(t, err) 200 } 201 202 func TestFSTablePersisterConjoinAll(t *testing.T) { 203 assert := assert.New(t) 204 assert.True(len(testChunks) > 1, "Whoops, this test isn't meaningful") 205 sources := make(chunkSources, len(testChunks)) 206 207 dir := makeTempDir(t) 208 defer os.RemoveAll(dir) 209 fc := newFDCache(len(sources)) 210 defer fc.Drop() 211 fts := newFSTablePersister(dir, fc, nil) 212 213 for i, c := range testChunks { 214 randChunk := make([]byte, (i+1)*13) 215 _, err := rand.Read(randChunk) 216 require.NoError(t, err) 217 name, err := writeTableData(dir, c, randChunk) 218 require.NoError(t, err) 219 sources[i], err = fts.Open(context.Background(), name, 2, nil) 220 require.NoError(t, err) 221 } 222 223 src, err := fts.ConjoinAll(context.Background(), sources, &Stats{}) 224 require.NoError(t, err) 225 226 if assert.True(mustUint32(src.count()) > 0) { 227 buff, err := ioutil.ReadFile(filepath.Join(dir, mustAddr(src.hash()).String())) 228 require.NoError(t, err) 229 ti, err := parseTableIndex(buff) 230 require.NoError(t, err) 231 tr := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize) 232 assertChunksInReader(testChunks, tr, assert) 233 } 234 235 present := fc.reportEntries() 236 // Since 0 refcount entries are evicted randomly, the only thing we can validate is that fc remains at its target size 237 assert.Len(present, len(sources)) 238 } 239 240 func TestFSTablePersisterConjoinAllDups(t *testing.T) { 241 assert := assert.New(t) 242 dir := makeTempDir(t) 243 defer os.RemoveAll(dir) 244 fc := newFDCache(defaultMaxTables) 245 defer fc.Drop() 246 fts := newFSTablePersister(dir, fc, nil) 247 248 reps := 3 249 sources := make(chunkSources, reps) 250 for i := 0; i < reps; i++ { 251 mt := newMemTable(1 << 10) 252 for _, c := range testChunks { 253 mt.addChunk(computeAddr(c), c) 254 } 255 256 var err error 257 sources[i], err = fts.Persist(context.Background(), mt, nil, &Stats{}) 258 require.NoError(t, err) 259 } 260 261 src, err := fts.ConjoinAll(context.Background(), sources, &Stats{}) 262 require.NoError(t, err) 263 264 if assert.True(mustUint32(src.count()) > 0) { 265 buff, err := ioutil.ReadFile(filepath.Join(dir, mustAddr(src.hash()).String())) 266 require.NoError(t, err) 267 ti, err := parseTableIndex(buff) 268 require.NoError(t, err) 269 tr := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize) 270 assertChunksInReader(testChunks, tr, assert) 271 assert.EqualValues(reps*len(testChunks), mustUint32(tr.count())) 272 } 273 }