github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/manifest/version_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 "strconv" 11 "strings" 12 "sync" 13 "testing" 14 15 "github.com/cockroachdb/errors" 16 "github.com/cockroachdb/redact" 17 "github.com/stretchr/testify/require" 18 "github.com/zuoyebang/bitalostable/internal/base" 19 "github.com/zuoyebang/bitalostable/internal/datadriven" 20 "github.com/zuoyebang/bitalostable/internal/testkeys" 21 "github.com/zuoyebang/bitalostable/vfs" 22 ) 23 24 func levelMetadata(level int, files ...*FileMetadata) LevelMetadata { 25 return makeLevelMetadata(base.DefaultComparer.Compare, level, files) 26 } 27 28 func ikey(s string) InternalKey { 29 return base.MakeInternalKey([]byte(s), 0, base.InternalKeyKindSet) 30 } 31 32 func TestIkeyRange(t *testing.T) { 33 cmp := base.DefaultComparer.Compare 34 testCases := []struct { 35 input, want string 36 }{ 37 { 38 "", 39 "-", 40 }, 41 { 42 "a-e", 43 "a-e", 44 }, 45 { 46 "a-e a-e", 47 "a-e", 48 }, 49 { 50 "c-g a-e", 51 "a-g", 52 }, 53 { 54 "a-e c-g a-e", 55 "a-g", 56 }, 57 { 58 "b-d f-g", 59 "b-g", 60 }, 61 { 62 "d-e b-d", 63 "b-e", 64 }, 65 { 66 "e-e", 67 "e-e", 68 }, 69 { 70 "f-g e-e d-e c-g b-d a-e", 71 "a-g", 72 }, 73 } 74 for _, tc := range testCases { 75 var f []*FileMetadata 76 if tc.input != "" { 77 for i, s := range strings.Split(tc.input, " ") { 78 m := (&FileMetadata{ 79 FileNum: base.FileNum(i), 80 }).ExtendPointKeyBounds(cmp, ikey(s[0:1]), ikey(s[2:3])) 81 f = append(f, m) 82 } 83 } 84 levelMetadata := makeLevelMetadata(base.DefaultComparer.Compare, 0, f) 85 86 sm, la := KeyRange(base.DefaultComparer.Compare, levelMetadata.Iter()) 87 got := string(sm.UserKey) + "-" + string(la.UserKey) 88 if got != tc.want { 89 t.Errorf("KeyRange(%q) = %q, %q", tc.input, got, tc.want) 90 } 91 } 92 } 93 94 func TestOverlaps(t *testing.T) { 95 var v *Version 96 cmp := testkeys.Comparer.Compare 97 fmtKey := testkeys.Comparer.FormatKey 98 datadriven.RunTest(t, "testdata/overlaps", func(d *datadriven.TestData) string { 99 switch d.Cmd { 100 case "define": 101 var err error 102 v, err = ParseVersionDebug(cmp, fmtKey, 64>>10 /* flush split bytes */, d.Input) 103 if err != nil { 104 return err.Error() 105 } 106 return v.String() 107 case "overlaps": 108 var level int 109 var start, end string 110 var exclusiveEnd bool 111 d.ScanArgs(t, "level", &level) 112 d.ScanArgs(t, "start", &start) 113 d.ScanArgs(t, "end", &end) 114 d.ScanArgs(t, "exclusive-end", &exclusiveEnd) 115 var buf bytes.Buffer 116 v.Overlaps(level, testkeys.Comparer.Compare, []byte(start), []byte(end), exclusiveEnd).Each(func(f *FileMetadata) { 117 fmt.Fprintf(&buf, "%s\n", f.DebugString(base.DefaultFormatter, false)) 118 }) 119 return buf.String() 120 default: 121 return fmt.Sprintf("unknown command: %s", d.Cmd) 122 } 123 }) 124 } 125 126 func TestContains(t *testing.T) { 127 cmp := base.DefaultComparer.Compare 128 newFileMeta := func(fileNum base.FileNum, size uint64, smallest, largest base.InternalKey) *FileMetadata { 129 m := (&FileMetadata{ 130 FileNum: fileNum, 131 Size: size, 132 }).ExtendPointKeyBounds(cmp, smallest, largest) 133 return m 134 } 135 m00 := newFileMeta( 136 700, 137 1, 138 base.ParseInternalKey("b.SET.7008"), 139 base.ParseInternalKey("e.SET.7009"), 140 ) 141 m01 := newFileMeta( 142 701, 143 1, 144 base.ParseInternalKey("c.SET.7018"), 145 base.ParseInternalKey("f.SET.7019"), 146 ) 147 m02 := newFileMeta( 148 702, 149 1, 150 base.ParseInternalKey("f.SET.7028"), 151 base.ParseInternalKey("g.SET.7029"), 152 ) 153 m03 := newFileMeta( 154 703, 155 1, 156 base.ParseInternalKey("x.SET.7038"), 157 base.ParseInternalKey("y.SET.7039"), 158 ) 159 m04 := newFileMeta( 160 704, 161 1, 162 base.ParseInternalKey("n.SET.7048"), 163 base.ParseInternalKey("p.SET.7049"), 164 ) 165 m05 := newFileMeta( 166 705, 167 1, 168 base.ParseInternalKey("p.SET.7058"), 169 base.ParseInternalKey("p.SET.7059"), 170 ) 171 m06 := newFileMeta( 172 706, 173 1, 174 base.ParseInternalKey("p.SET.7068"), 175 base.ParseInternalKey("u.SET.7069"), 176 ) 177 m07 := newFileMeta( 178 707, 179 1, 180 base.ParseInternalKey("r.SET.7078"), 181 base.ParseInternalKey("s.SET.7079"), 182 ) 183 184 m10 := newFileMeta( 185 710, 186 1, 187 base.ParseInternalKey("d.SET.7108"), 188 base.ParseInternalKey("g.SET.7109"), 189 ) 190 m11 := newFileMeta( 191 711, 192 1, 193 base.ParseInternalKey("g.SET.7118"), 194 base.ParseInternalKey("j.SET.7119"), 195 ) 196 m12 := newFileMeta( 197 712, 198 1, 199 base.ParseInternalKey("n.SET.7128"), 200 base.ParseInternalKey("p.SET.7129"), 201 ) 202 m13 := newFileMeta( 203 713, 204 1, 205 base.ParseInternalKey("p.SET.7148"), 206 base.ParseInternalKey("p.SET.7149"), 207 ) 208 m14 := newFileMeta( 209 714, 210 1, 211 base.ParseInternalKey("p.SET.7138"), 212 base.ParseInternalKey("u.SET.7139"), 213 ) 214 215 v := Version{ 216 Levels: [NumLevels]LevelMetadata{ 217 0: levelMetadata(0, m00, m01, m02, m03, m04, m05, m06, m07), 218 1: levelMetadata(1, m10, m11, m12, m13, m14), 219 }, 220 } 221 222 testCases := []struct { 223 level int 224 file *FileMetadata 225 want bool 226 }{ 227 // Level 0: m00=b-e, m01=c-f, m02=f-g, m03=x-y, m04=n-p, m05=p-p, m06=p-u, m07=r-s. 228 // Note that: 229 // - the slice isn't sorted (e.g. m02=f-g, m03=x-y, m04=n-p), 230 // - m00 and m01 overlap (not just touch), 231 // - m06 contains m07, 232 // - m00, m01 and m02 transitively overlap/touch each other, and 233 // - m04, m05, m06 and m07 transitively overlap/touch each other. 234 {0, m00, true}, 235 {0, m01, true}, 236 {0, m02, true}, 237 {0, m03, true}, 238 {0, m04, true}, 239 {0, m05, true}, 240 {0, m06, true}, 241 {0, m07, true}, 242 {0, m10, false}, 243 {0, m11, false}, 244 {0, m12, false}, 245 {0, m13, false}, 246 {0, m14, false}, 247 {1, m00, false}, 248 {1, m01, false}, 249 {1, m02, false}, 250 {1, m03, false}, 251 {1, m04, false}, 252 {1, m05, false}, 253 {1, m06, false}, 254 {1, m07, false}, 255 {1, m10, true}, 256 {1, m11, true}, 257 {1, m12, true}, 258 {1, m13, true}, 259 {1, m14, true}, 260 261 // Level 2: empty. 262 {2, m00, false}, 263 {2, m14, false}, 264 } 265 266 for _, tc := range testCases { 267 got := v.Contains(tc.level, cmp, tc.file) 268 if got != tc.want { 269 t.Errorf("level=%d, file=%s\ngot %t\nwant %t", tc.level, tc.file, got, tc.want) 270 } 271 } 272 } 273 274 func TestVersionUnref(t *testing.T) { 275 list := &VersionList{} 276 list.Init(&sync.Mutex{}) 277 v := &Version{Deleted: func([]*FileMetadata) {}} 278 v.Ref() 279 list.PushBack(v) 280 v.Unref() 281 if !list.Empty() { 282 t.Fatalf("expected version list to be empty") 283 } 284 } 285 286 func TestCheckOrdering(t *testing.T) { 287 cmp := base.DefaultComparer.Compare 288 fmtKey := base.DefaultComparer.FormatKey 289 datadriven.RunTest(t, "testdata/version_check_ordering", 290 func(d *datadriven.TestData) string { 291 switch d.Cmd { 292 case "check-ordering": 293 v, err := ParseVersionDebug(cmp, fmtKey, 10<<20, d.Input) 294 if err != nil { 295 return err.Error() 296 } 297 // L0 files compare on sequence numbers. Use the seqnums from the 298 // smallest / largest bounds for the table. 299 v.Levels[0].Slice().Each(func(m *FileMetadata) { 300 m.SmallestSeqNum = m.Smallest.SeqNum() 301 m.LargestSeqNum = m.Largest.SeqNum() 302 }) 303 if err = v.CheckOrdering(cmp, base.DefaultFormatter); err != nil { 304 return err.Error() 305 } 306 return "OK" 307 308 default: 309 return fmt.Sprintf("unknown command: %s", d.Cmd) 310 } 311 }) 312 } 313 314 func TestCheckConsistency(t *testing.T) { 315 const dir = "./test" 316 mem := vfs.NewMem() 317 mem.MkdirAll(dir, 0755) 318 319 cmp := base.DefaultComparer.Compare 320 fmtKey := base.DefaultComparer.FormatKey 321 parseMeta := func(s string) (*FileMetadata, error) { 322 if len(s) == 0 { 323 return nil, nil 324 } 325 parts := strings.Split(s, ":") 326 if len(parts) != 2 { 327 return nil, errors.Errorf("malformed table spec: %q", s) 328 } 329 fileNum, err := strconv.Atoi(strings.TrimSpace(parts[0])) 330 if err != nil { 331 return nil, err 332 } 333 size, err := strconv.Atoi(strings.TrimSpace(parts[1])) 334 if err != nil { 335 return nil, err 336 } 337 return &FileMetadata{ 338 FileNum: base.FileNum(fileNum), 339 Size: uint64(size), 340 }, nil 341 } 342 343 datadriven.RunTest(t, "testdata/version_check_consistency", 344 func(d *datadriven.TestData) string { 345 switch d.Cmd { 346 case "check-consistency": 347 var filesByLevel [NumLevels][]*FileMetadata 348 var files *[]*FileMetadata 349 350 for _, data := range strings.Split(d.Input, "\n") { 351 switch data { 352 case "L0", "L1", "L2", "L3", "L4", "L5", "L6": 353 level, err := strconv.Atoi(data[1:]) 354 if err != nil { 355 return err.Error() 356 } 357 files = &filesByLevel[level] 358 359 default: 360 m, err := parseMeta(data) 361 if err != nil { 362 return err.Error() 363 } 364 if m != nil { 365 *files = append(*files, m) 366 } 367 } 368 } 369 370 redactErr := false 371 for _, arg := range d.CmdArgs { 372 switch v := arg.String(); v { 373 case "redact": 374 redactErr = true 375 default: 376 return fmt.Sprintf("unknown argument: %q", v) 377 } 378 } 379 380 v := NewVersion(cmp, fmtKey, 0, filesByLevel) 381 err := v.CheckConsistency(dir, mem) 382 if err != nil { 383 if redactErr { 384 redacted := redact.Sprint(err).Redact() 385 return string(redacted) 386 } 387 return err.Error() 388 } 389 return "OK" 390 391 case "build": 392 for _, data := range strings.Split(d.Input, "\n") { 393 m, err := parseMeta(data) 394 if err != nil { 395 return err.Error() 396 } 397 path := base.MakeFilepath(mem, dir, base.FileTypeTable, m.FileNum) 398 _ = mem.Remove(path) 399 f, err := mem.Create(path) 400 if err != nil { 401 return err.Error() 402 } 403 _, err = f.Write(make([]byte, m.Size)) 404 if err != nil { 405 return err.Error() 406 } 407 f.Close() 408 } 409 return "" 410 411 default: 412 return fmt.Sprintf("unknown command: %s", d.Cmd) 413 } 414 }) 415 } 416 417 func TestExtendBounds(t *testing.T) { 418 cmp := base.DefaultComparer.Compare 419 parseBounds := func(line string) (lower, upper InternalKey) { 420 parts := strings.Split(line, "-") 421 if len(parts) == 1 { 422 parts = strings.Split(parts[0], ":") 423 start, end := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 424 lower = base.ParseInternalKey(start) 425 switch k := lower.Kind(); k { 426 case base.InternalKeyKindRangeDelete: 427 upper = base.MakeRangeDeleteSentinelKey([]byte(end)) 428 case base.InternalKeyKindRangeKeySet, base.InternalKeyKindRangeKeyUnset, base.InternalKeyKindRangeKeyDelete: 429 upper = base.MakeExclusiveSentinelKey(k, []byte(end)) 430 default: 431 panic(fmt.Sprintf("unknown kind %s with end key", k)) 432 } 433 } else { 434 l, u := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 435 lower, upper = base.ParseInternalKey(l), base.ParseInternalKey(u) 436 } 437 return 438 } 439 format := func(m *FileMetadata) string { 440 var b bytes.Buffer 441 var smallest, largest string 442 switch m.boundTypeSmallest { 443 case boundTypePointKey: 444 smallest = "point" 445 case boundTypeRangeKey: 446 smallest = "range" 447 default: 448 return fmt.Sprintf("unknown bound type %d", m.boundTypeSmallest) 449 } 450 switch m.boundTypeLargest { 451 case boundTypePointKey: 452 largest = "point" 453 case boundTypeRangeKey: 454 largest = "range" 455 default: 456 return fmt.Sprintf("unknown bound type %d", m.boundTypeLargest) 457 } 458 bounds, err := m.boundsMarker() 459 if err != nil { 460 panic(err) 461 } 462 fmt.Fprintf(&b, "%s\n", m.DebugString(base.DefaultFormatter, true)) 463 fmt.Fprintf(&b, " bounds: (smallest=%s,largest=%s) (0x%08b)\n", smallest, largest, bounds) 464 return b.String() 465 } 466 m := &FileMetadata{} 467 datadriven.RunTest(t, "testdata/file_metadata_bounds", func(d *datadriven.TestData) string { 468 switch d.Cmd { 469 case "reset": 470 m = &FileMetadata{} 471 return "" 472 case "extend-point-key-bounds": 473 u, l := parseBounds(d.Input) 474 m.ExtendPointKeyBounds(cmp, u, l) 475 return format(m) 476 case "extend-range-key-bounds": 477 u, l := parseBounds(d.Input) 478 m.ExtendRangeKeyBounds(cmp, u, l) 479 return format(m) 480 default: 481 return fmt.Sprintf("unknown command %s\n", d.Cmd) 482 } 483 }) 484 } 485 486 func TestFileMetadata_ParseRoundTrip(t *testing.T) { 487 testCases := []struct { 488 name string 489 input string 490 output string 491 }{ 492 { 493 name: "point keys only", 494 input: "000001:[a#0,SET-z#0,DEL] points:[a#0,SET-z#0,DEL]", 495 }, 496 { 497 name: "range keys only", 498 input: "000001:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL] ranges:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL]", 499 }, 500 { 501 name: "point and range keys", 502 input: "000001:[a#0,RANGEKEYSET-d#0,DEL] points:[b#0,SET-d#0,DEL] ranges:[a#0,RANGEKEYSET-c#0,RANGEKEYDEL]", 503 }, 504 { 505 name: "whitespace", 506 input: " 000001 : [ a#0,SET - z#0,DEL] points : [ a#0,SET - z#0,DEL] ", 507 output: "000001:[a#0,SET-z#0,DEL] points:[a#0,SET-z#0,DEL]", 508 }, 509 } 510 for _, tc := range testCases { 511 t.Run(tc.name, func(t *testing.T) { 512 m, err := ParseFileMetadataDebug(tc.input) 513 require.NoError(t, err) 514 err = m.Validate(base.DefaultComparer.Compare, base.DefaultFormatter) 515 require.NoError(t, err) 516 got := m.DebugString(base.DefaultFormatter, true) 517 want := tc.input 518 if tc.output != "" { 519 want = tc.output 520 } 521 require.Equal(t, want, got) 522 }) 523 } 524 }