github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/manifest/version_edit_test.go (about) 1 // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package manifest 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "reflect" 14 "sort" 15 "strconv" 16 "strings" 17 "testing" 18 19 "github.com/cockroachdb/errors" 20 "github.com/kr/pretty" 21 "github.com/stretchr/testify/require" 22 "github.com/zuoyebang/bitalostable/internal/base" 23 "github.com/zuoyebang/bitalostable/internal/datadriven" 24 "github.com/zuoyebang/bitalostable/record" 25 ) 26 27 func checkRoundTrip(e0 VersionEdit) error { 28 var e1 VersionEdit 29 buf := new(bytes.Buffer) 30 if err := e0.Encode(buf); err != nil { 31 return errors.Wrap(err, "encode") 32 } 33 if err := e1.Decode(buf); err != nil { 34 return errors.Wrap(err, "decode") 35 } 36 if diff := pretty.Diff(e0, e1); diff != nil { 37 return errors.Errorf("%s", strings.Join(diff, "\n")) 38 } 39 return nil 40 } 41 42 func TestVersionEditRoundTrip(t *testing.T) { 43 cmp := base.DefaultComparer.Compare 44 m1 := (&FileMetadata{ 45 FileNum: 805, 46 Size: 8050, 47 CreationTime: 805030, 48 }).ExtendPointKeyBounds( 49 cmp, 50 base.DecodeInternalKey([]byte("abc\x00\x01\x02\x03\x04\x05\x06\x07")), 51 base.DecodeInternalKey([]byte("xyz\x01\xff\xfe\xfd\xfc\xfb\xfa\xf9")), 52 ) 53 54 m2 := (&FileMetadata{ 55 FileNum: 806, 56 Size: 8060, 57 CreationTime: 806040, 58 SmallestSeqNum: 3, 59 LargestSeqNum: 5, 60 MarkedForCompaction: true, 61 }).ExtendPointKeyBounds( 62 cmp, 63 base.DecodeInternalKey([]byte("A\x00\x01\x02\x03\x04\x05\x06\x07")), 64 base.DecodeInternalKey([]byte("Z\x01\xff\xfe\xfd\xfc\xfb\xfa\xf9")), 65 ) 66 67 m3 := (&FileMetadata{ 68 FileNum: 807, 69 Size: 8070, 70 CreationTime: 807050, 71 }).ExtendRangeKeyBounds( 72 cmp, 73 base.MakeInternalKey([]byte("aaa"), 0, base.InternalKeyKindRangeKeySet), 74 base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("zzz")), 75 ) 76 77 m4 := (&FileMetadata{ 78 FileNum: 809, 79 Size: 8090, 80 CreationTime: 809060, 81 SmallestSeqNum: 9, 82 LargestSeqNum: 11, 83 }).ExtendPointKeyBounds( 84 cmp, 85 base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet), 86 base.MakeInternalKey([]byte("m"), 0, base.InternalKeyKindSet), 87 ).ExtendRangeKeyBounds( 88 cmp, 89 base.MakeInternalKey([]byte("l"), 0, base.InternalKeyKindRangeKeySet), 90 base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("z")), 91 ) 92 93 testCases := []VersionEdit{ 94 // An empty version edit. 95 {}, 96 // A complete version edit. 97 { 98 ComparerName: "11", 99 MinUnflushedLogNum: 22, 100 ObsoletePrevLogNum: 33, 101 NextFileNum: 44, 102 LastSeqNum: 55, 103 DeletedFiles: map[DeletedFileEntry]*FileMetadata{ 104 { 105 Level: 3, 106 FileNum: 703, 107 }: nil, 108 { 109 Level: 4, 110 FileNum: 704, 111 }: nil, 112 }, 113 NewFiles: []NewFileEntry{ 114 { 115 Level: 4, 116 Meta: m1, 117 }, 118 { 119 Level: 5, 120 Meta: m2, 121 }, 122 { 123 Level: 6, 124 Meta: m3, 125 }, 126 { 127 Level: 6, 128 Meta: m4, 129 }, 130 }, 131 }, 132 } 133 for _, tc := range testCases { 134 if err := checkRoundTrip(tc); err != nil { 135 t.Error(err) 136 } 137 } 138 } 139 140 func TestVersionEditDecode(t *testing.T) { 141 cmp := base.DefaultComparer.Compare 142 m := (&FileMetadata{ 143 FileNum: 4, 144 Size: 986, 145 SmallestSeqNum: 3, 146 LargestSeqNum: 5, 147 }).ExtendPointKeyBounds( 148 cmp, 149 base.MakeInternalKey([]byte("bar"), 5, base.InternalKeyKindDelete), 150 base.MakeInternalKey([]byte("foo"), 4, base.InternalKeyKindSet), 151 ) 152 153 testCases := []struct { 154 filename string 155 encodedEdits []string 156 edits []VersionEdit 157 }{ 158 // db-stage-1 and db-stage-2 have the same manifest. 159 { 160 filename: "db-stage-1/MANIFEST-000001", 161 encodedEdits: []string{ 162 "\x02\x00\x03\x02\x04\x00", 163 }, 164 edits: []VersionEdit{ 165 { 166 NextFileNum: 2, 167 }, 168 }, 169 }, 170 // db-stage-3 and db-stage-4 have the same manifest. 171 { 172 filename: "db-stage-3/MANIFEST-000005", 173 encodedEdits: []string{ 174 "\x01\x1aleveldb.BytewiseComparator", 175 "\x02\x00", 176 "\x02\x04\t\x00\x03\x06\x04\x05d\x00\x04\xda\a\vbar" + 177 "\x00\x05\x00\x00\x00\x00\x00\x00\vfoo\x01\x04\x00" + 178 "\x00\x00\x00\x00\x00\x03\x05", 179 }, 180 edits: []VersionEdit{ 181 { 182 ComparerName: "leveldb.BytewiseComparator", 183 }, 184 {}, 185 { 186 MinUnflushedLogNum: 4, 187 ObsoletePrevLogNum: 0, 188 NextFileNum: 6, 189 LastSeqNum: 5, 190 NewFiles: []NewFileEntry{ 191 { 192 Level: 0, 193 Meta: m, 194 }, 195 }, 196 }, 197 }, 198 }, 199 } 200 201 for _, tc := range testCases { 202 t.Run("", func(t *testing.T) { 203 f, err := os.Open("../../testdata/" + tc.filename) 204 if err != nil { 205 t.Fatalf("filename=%q: open error: %v", tc.filename, err) 206 } 207 defer f.Close() 208 i, r := 0, record.NewReader(f, 0 /* logNum */) 209 for { 210 rr, err := r.Next() 211 if err == io.EOF { 212 break 213 } 214 if err != nil { 215 t.Fatalf("filename=%q i=%d: record reader error: %v", tc.filename, i, err) 216 } 217 if i >= len(tc.edits) { 218 t.Fatalf("filename=%q i=%d: too many version edits", tc.filename, i+1) 219 } 220 221 encodedEdit, err := ioutil.ReadAll(rr) 222 if err != nil { 223 t.Fatalf("filename=%q i=%d: read error: %v", tc.filename, i, err) 224 } 225 if s := string(encodedEdit); s != tc.encodedEdits[i] { 226 t.Fatalf("filename=%q i=%d: got encoded %q, want %q", tc.filename, i, s, tc.encodedEdits[i]) 227 } 228 229 var edit VersionEdit 230 err = edit.Decode(bytes.NewReader(encodedEdit)) 231 if err != nil { 232 t.Fatalf("filename=%q i=%d: decode error: %v", tc.filename, i, err) 233 } 234 if !reflect.DeepEqual(edit, tc.edits[i]) { 235 t.Fatalf("filename=%q i=%d: decode\n\tgot %#v\n\twant %#v\n%s", tc.filename, i, edit, tc.edits[i], 236 strings.Join(pretty.Diff(edit, tc.edits[i]), "\n")) 237 } 238 if err := checkRoundTrip(edit); err != nil { 239 t.Fatalf("filename=%q i=%d: round trip: %v", tc.filename, i, err) 240 } 241 242 i++ 243 } 244 if i != len(tc.edits) { 245 t.Fatalf("filename=%q: got %d edits, want %d", tc.filename, i, len(tc.edits)) 246 } 247 }) 248 } 249 } 250 251 func TestVersionEditEncodeLastSeqNum(t *testing.T) { 252 testCases := []struct { 253 edit VersionEdit 254 encoded string 255 }{ 256 // If ComparerName is unset, LastSeqNum is only encoded if non-zero. 257 {VersionEdit{LastSeqNum: 0}, ""}, 258 {VersionEdit{LastSeqNum: 1}, "\x04\x01"}, 259 // For compatibility with RocksDB, if ComparerName is set we always encode 260 // LastSeqNum. 261 {VersionEdit{ComparerName: "foo", LastSeqNum: 0}, "\x01\x03\x66\x6f\x6f\x04\x00"}, 262 {VersionEdit{ComparerName: "foo", LastSeqNum: 1}, "\x01\x03\x66\x6f\x6f\x04\x01"}, 263 } 264 for _, c := range testCases { 265 t.Run("", func(t *testing.T) { 266 var buf bytes.Buffer 267 require.NoError(t, c.edit.Encode(&buf)) 268 if result := buf.String(); c.encoded != result { 269 t.Fatalf("expected %x, but found %x", c.encoded, result) 270 } 271 272 if c.edit.ComparerName != "" { 273 // Manually decode the version edit so that we can verify the contents 274 // even if the LastSeqNum decodes to 0. 275 d := versionEditDecoder{strings.NewReader(c.encoded)} 276 277 // Decode ComparerName. 278 tag, err := d.readUvarint() 279 require.NoError(t, err) 280 if tag != tagComparator { 281 t.Fatalf("expected %d, but found %d", tagComparator, tag) 282 } 283 s, err := d.readBytes() 284 require.NoError(t, err) 285 if c.edit.ComparerName != string(s) { 286 t.Fatalf("expected %q, but found %q", c.edit.ComparerName, s) 287 } 288 289 // Decode LastSeqNum. 290 tag, err = d.readUvarint() 291 require.NoError(t, err) 292 if tag != tagLastSequence { 293 t.Fatalf("expected %d, but found %d", tagLastSequence, tag) 294 } 295 val, err := d.readUvarint() 296 require.NoError(t, err) 297 if c.edit.LastSeqNum != val { 298 t.Fatalf("expected %d, but found %d", c.edit.LastSeqNum, val) 299 } 300 } 301 }) 302 } 303 } 304 305 func TestVersionEditApply(t *testing.T) { 306 parseMeta := func(s string) (FileMetadata, error) { 307 m, err := ParseFileMetadataDebug(s) 308 if err != nil { 309 return FileMetadata{}, err 310 } 311 m.SmallestSeqNum = m.Smallest.SeqNum() 312 m.LargestSeqNum = m.Largest.SeqNum() 313 if m.SmallestSeqNum > m.LargestSeqNum { 314 m.SmallestSeqNum, m.LargestSeqNum = m.LargestSeqNum, m.SmallestSeqNum 315 } 316 return m, nil 317 } 318 319 datadriven.RunTest(t, "testdata/version_edit_apply", 320 func(d *datadriven.TestData) string { 321 switch d.Cmd { 322 case "apply": 323 // TODO(sumeer): move this Version parsing code to utils, to 324 // avoid repeating it, and make it the inverse of 325 // Version.DebugString(). 326 var v *Version 327 ve := &VersionEdit{} 328 isVersion := true 329 isDelete := true 330 var level int 331 var err error 332 versionFiles := map[base.FileNum]*FileMetadata{} 333 for _, data := range strings.Split(d.Input, "\n") { 334 data = strings.TrimSpace(data) 335 switch data { 336 case "edit": 337 isVersion = false 338 case "delete": 339 isVersion = false 340 isDelete = true 341 case "add": 342 isVersion = false 343 isDelete = false 344 case "L0", "L1", "L2", "L3", "L4", "L5", "L6": 345 level, err = strconv.Atoi(data[1:]) 346 if err != nil { 347 return err.Error() 348 } 349 default: 350 if isVersion || !isDelete { 351 meta, err := parseMeta(data) 352 if err != nil { 353 return err.Error() 354 } 355 if isVersion { 356 if v == nil { 357 v = new(Version) 358 for l := 0; l < NumLevels; l++ { 359 v.Levels[l] = makeLevelMetadata(base.DefaultComparer.Compare, l, nil /* files */) 360 } 361 } 362 versionFiles[meta.FileNum] = &meta 363 v.Levels[level].tree.insert(&meta) 364 } else { 365 ve.NewFiles = 366 append(ve.NewFiles, NewFileEntry{Level: level, Meta: &meta}) 367 } 368 } else { 369 fileNum, err := strconv.Atoi(data) 370 if err != nil { 371 return err.Error() 372 } 373 dfe := DeletedFileEntry{Level: level, FileNum: base.FileNum(fileNum)} 374 if ve.DeletedFiles == nil { 375 ve.DeletedFiles = make(map[DeletedFileEntry]*FileMetadata) 376 } 377 ve.DeletedFiles[dfe] = versionFiles[dfe.FileNum] 378 } 379 } 380 } 381 382 if v != nil { 383 if err := v.InitL0Sublevels(base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20); err != nil { 384 return err.Error() 385 } 386 } 387 388 bve := BulkVersionEdit{} 389 bve.AddedByFileNum = make(map[base.FileNum]*FileMetadata) 390 if err := bve.Accumulate(ve); err != nil { 391 return err.Error() 392 } 393 newv, zombies, err := bve.Apply(v, base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20, 32000) 394 if err != nil { 395 return err.Error() 396 } 397 398 zombieFileNums := make([]base.FileNum, 0, len(zombies)) 399 for fileNum := range zombies { 400 zombieFileNums = append(zombieFileNums, fileNum) 401 } 402 sort.Slice(zombieFileNums, func(i, j int) bool { 403 return zombieFileNums[i] < zombieFileNums[j] 404 }) 405 406 return fmt.Sprintf("%szombies %d\n", newv, zombieFileNums) 407 408 default: 409 return fmt.Sprintf("unknown command: %s", d.Cmd) 410 } 411 }) 412 }