ceremonyclient/pebble/internal/manifest/version_test.go

430 lines
10 KiB
Go
Raw Permalink Normal View History

2024-01-03 07:31:42 +00:00
// Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package manifest
import (
"bytes"
"fmt"
"strings"
"sync"
"testing"
"github.com/cockroachdb/datadriven"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/testkeys"
"github.com/stretchr/testify/require"
)
func levelMetadata(level int, files ...*FileMetadata) LevelMetadata {
return makeLevelMetadata(base.DefaultComparer.Compare, level, files)
}
func ikey(s string) InternalKey {
return base.MakeInternalKey([]byte(s), 0, base.InternalKeyKindSet)
}
func TestIkeyRange(t *testing.T) {
cmp := base.DefaultComparer.Compare
testCases := []struct {
input, want string
}{
{
"",
"-",
},
{
"a-e",
"a-e",
},
{
"a-e a-e",
"a-e",
},
{
"c-g a-e",
"a-g",
},
{
"a-e c-g a-e",
"a-g",
},
{
"b-d f-g",
"b-g",
},
{
"d-e b-d",
"b-e",
},
{
"e-e",
"e-e",
},
{
"f-g e-e d-e c-g b-d a-e",
"a-g",
},
}
for _, tc := range testCases {
var f []*FileMetadata
if tc.input != "" {
for i, s := range strings.Split(tc.input, " ") {
m := (&FileMetadata{
FileNum: base.FileNum(i),
}).ExtendPointKeyBounds(cmp, ikey(s[0:1]), ikey(s[2:3]))
m.InitPhysicalBacking()
f = append(f, m)
}
}
levelMetadata := makeLevelMetadata(base.DefaultComparer.Compare, 0, f)
sm, la := KeyRange(base.DefaultComparer.Compare, levelMetadata.Iter())
got := string(sm.UserKey) + "-" + string(la.UserKey)
if got != tc.want {
t.Errorf("KeyRange(%q) = %q, %q", tc.input, got, tc.want)
}
}
}
func TestOverlaps(t *testing.T) {
var v *Version
cmp := testkeys.Comparer.Compare
fmtKey := testkeys.Comparer.FormatKey
datadriven.RunTest(t, "testdata/overlaps", func(t *testing.T, d *datadriven.TestData) string {
switch d.Cmd {
case "define":
var err error
v, err = ParseVersionDebug(cmp, fmtKey, 64>>10 /* flush split bytes */, d.Input)
if err != nil {
return err.Error()
}
return v.String()
case "overlaps":
var level int
var start, end string
var exclusiveEnd bool
d.ScanArgs(t, "level", &level)
d.ScanArgs(t, "start", &start)
d.ScanArgs(t, "end", &end)
d.ScanArgs(t, "exclusive-end", &exclusiveEnd)
overlaps := v.Overlaps(level, testkeys.Comparer.Compare, []byte(start), []byte(end), exclusiveEnd)
var buf bytes.Buffer
fmt.Fprintf(&buf, "%d files:\n", overlaps.Len())
overlaps.Each(func(f *FileMetadata) {
fmt.Fprintf(&buf, "%s\n", f.DebugString(base.DefaultFormatter, false))
})
return buf.String()
default:
return fmt.Sprintf("unknown command: %s", d.Cmd)
}
})
}
func TestContains(t *testing.T) {
cmp := base.DefaultComparer.Compare
newFileMeta := func(fileNum base.FileNum, size uint64, smallest, largest base.InternalKey) *FileMetadata {
m := (&FileMetadata{
FileNum: fileNum,
Size: size,
}).ExtendPointKeyBounds(cmp, smallest, largest)
m.InitPhysicalBacking()
return m
}
m00 := newFileMeta(
700,
1,
base.ParseInternalKey("b.SET.7008"),
base.ParseInternalKey("e.SET.7009"),
)
m01 := newFileMeta(
701,
1,
base.ParseInternalKey("c.SET.7018"),
base.ParseInternalKey("f.SET.7019"),
)
m02 := newFileMeta(
702,
1,
base.ParseInternalKey("f.SET.7028"),
base.ParseInternalKey("g.SET.7029"),
)
m03 := newFileMeta(
703,
1,
base.ParseInternalKey("x.SET.7038"),
base.ParseInternalKey("y.SET.7039"),
)
m04 := newFileMeta(
704,
1,
base.ParseInternalKey("n.SET.7048"),
base.ParseInternalKey("p.SET.7049"),
)
m05 := newFileMeta(
705,
1,
base.ParseInternalKey("p.SET.7058"),
base.ParseInternalKey("p.SET.7059"),
)
m06 := newFileMeta(
706,
1,
base.ParseInternalKey("p.SET.7068"),
base.ParseInternalKey("u.SET.7069"),
)
m07 := newFileMeta(
707,
1,
base.ParseInternalKey("r.SET.7078"),
base.ParseInternalKey("s.SET.7079"),
)
m10 := newFileMeta(
710,
1,
base.ParseInternalKey("d.SET.7108"),
base.ParseInternalKey("g.SET.7109"),
)
m11 := newFileMeta(
711,
1,
base.ParseInternalKey("g.SET.7118"),
base.ParseInternalKey("j.SET.7119"),
)
m12 := newFileMeta(
712,
1,
base.ParseInternalKey("n.SET.7128"),
base.ParseInternalKey("p.SET.7129"),
)
m13 := newFileMeta(
713,
1,
base.ParseInternalKey("p.SET.7148"),
base.ParseInternalKey("p.SET.7149"),
)
m14 := newFileMeta(
714,
1,
base.ParseInternalKey("p.SET.7138"),
base.ParseInternalKey("u.SET.7139"),
)
v := Version{
Levels: [NumLevels]LevelMetadata{
0: levelMetadata(0, m00, m01, m02, m03, m04, m05, m06, m07),
1: levelMetadata(1, m10, m11, m12, m13, m14),
},
}
testCases := []struct {
level int
file *FileMetadata
want bool
}{
// 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.
// Note that:
// - the slice isn't sorted (e.g. m02=f-g, m03=x-y, m04=n-p),
// - m00 and m01 overlap (not just touch),
// - m06 contains m07,
// - m00, m01 and m02 transitively overlap/touch each other, and
// - m04, m05, m06 and m07 transitively overlap/touch each other.
{0, m00, true},
{0, m01, true},
{0, m02, true},
{0, m03, true},
{0, m04, true},
{0, m05, true},
{0, m06, true},
{0, m07, true},
{0, m10, false},
{0, m11, false},
{0, m12, false},
{0, m13, false},
{0, m14, false},
{1, m00, false},
{1, m01, false},
{1, m02, false},
{1, m03, false},
{1, m04, false},
{1, m05, false},
{1, m06, false},
{1, m07, false},
{1, m10, true},
{1, m11, true},
{1, m12, true},
{1, m13, true},
{1, m14, true},
// Level 2: empty.
{2, m00, false},
{2, m14, false},
}
for _, tc := range testCases {
got := v.Contains(tc.level, cmp, tc.file)
if got != tc.want {
t.Errorf("level=%d, file=%s\ngot %t\nwant %t", tc.level, tc.file, got, tc.want)
}
}
}
func TestVersionUnref(t *testing.T) {
list := &VersionList{}
list.Init(&sync.Mutex{})
v := &Version{Deleted: func([]*FileBacking) {}}
v.Ref()
list.PushBack(v)
v.Unref()
if !list.Empty() {
t.Fatalf("expected version list to be empty")
}
}
func TestCheckOrdering(t *testing.T) {
cmp := base.DefaultComparer.Compare
fmtKey := base.DefaultComparer.FormatKey
datadriven.RunTest(t, "testdata/version_check_ordering",
func(t *testing.T, d *datadriven.TestData) string {
switch d.Cmd {
case "check-ordering":
orderingInvariants := ProhibitSplitUserKeys
if d.HasArg("allow-split-user-keys") {
orderingInvariants = AllowSplitUserKeys
}
v, err := ParseVersionDebug(cmp, fmtKey, 10<<20, d.Input)
if err != nil {
return err.Error()
}
// L0 files compare on sequence numbers. Use the seqnums from the
// smallest / largest bounds for the table.
v.Levels[0].Slice().Each(func(m *FileMetadata) {
m.SmallestSeqNum = m.Smallest.SeqNum()
m.LargestSeqNum = m.Largest.SeqNum()
})
if err = v.CheckOrdering(cmp, base.DefaultFormatter, orderingInvariants); err != nil {
return err.Error()
}
return "OK"
default:
return fmt.Sprintf("unknown command: %s", d.Cmd)
}
})
}
func TestExtendBounds(t *testing.T) {
cmp := base.DefaultComparer.Compare
parseBounds := func(line string) (lower, upper InternalKey) {
parts := strings.Split(line, "-")
if len(parts) == 1 {
parts = strings.Split(parts[0], ":")
start, end := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
lower = base.ParseInternalKey(start)
switch k := lower.Kind(); k {
case base.InternalKeyKindRangeDelete:
upper = base.MakeRangeDeleteSentinelKey([]byte(end))
case base.InternalKeyKindRangeKeySet, base.InternalKeyKindRangeKeyUnset, base.InternalKeyKindRangeKeyDelete:
upper = base.MakeExclusiveSentinelKey(k, []byte(end))
default:
panic(fmt.Sprintf("unknown kind %s with end key", k))
}
} else {
l, u := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
lower, upper = base.ParseInternalKey(l), base.ParseInternalKey(u)
}
return
}
format := func(m *FileMetadata) string {
var b bytes.Buffer
var smallest, largest string
switch m.boundTypeSmallest {
case boundTypePointKey:
smallest = "point"
case boundTypeRangeKey:
smallest = "range"
default:
return fmt.Sprintf("unknown bound type %d", m.boundTypeSmallest)
}
switch m.boundTypeLargest {
case boundTypePointKey:
largest = "point"
case boundTypeRangeKey:
largest = "range"
default:
return fmt.Sprintf("unknown bound type %d", m.boundTypeLargest)
}
bounds, err := m.boundsMarker()
if err != nil {
panic(err)
}
fmt.Fprintf(&b, "%s\n", m.DebugString(base.DefaultFormatter, true))
fmt.Fprintf(&b, " bounds: (smallest=%s,largest=%s) (0x%08b)\n", smallest, largest, bounds)
return b.String()
}
m := &FileMetadata{}
datadriven.RunTest(t, "testdata/file_metadata_bounds", func(t *testing.T, d *datadriven.TestData) string {
switch d.Cmd {
case "reset":
m = &FileMetadata{}
return ""
case "extend-point-key-bounds":
u, l := parseBounds(d.Input)
m.ExtendPointKeyBounds(cmp, u, l)
return format(m)
case "extend-range-key-bounds":
u, l := parseBounds(d.Input)
m.ExtendRangeKeyBounds(cmp, u, l)
return format(m)
default:
return fmt.Sprintf("unknown command %s\n", d.Cmd)
}
})
}
func TestFileMetadata_ParseRoundTrip(t *testing.T) {
testCases := []struct {
name string
input string
output string
}{
{
name: "point keys only",
input: "000001:[a#0,SET-z#0,DEL] seqnums:[0-0] points:[a#0,SET-z#0,DEL]",
},
{
name: "range keys only",
input: "000001:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL] seqnums:[0-0] ranges:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL]",
},
{
name: "point and range keys",
input: "000001:[a#0,RANGEKEYSET-d#0,DEL] seqnums:[0-0] points:[b#0,SET-d#0,DEL] ranges:[a#0,RANGEKEYSET-c#0,RANGEKEYDEL]",
},
{
name: "point and range keys with nonzero senums",
input: "000001:[a#3,RANGEKEYSET-d#4,DEL] seqnums:[3-7] points:[b#3,SET-d#4,DEL] ranges:[a#3,RANGEKEYSET-c#5,RANGEKEYDEL]",
},
{
name: "whitespace",
input: " 000001 : [ a#0,SET - z#0,DEL] points : [ a#0,SET - z#0,DEL] ",
output: "000001:[a#0,SET-z#0,DEL] seqnums:[0-0] points:[a#0,SET-z#0,DEL]",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
m, err := ParseFileMetadataDebug(tc.input)
require.NoError(t, err)
err = m.Validate(base.DefaultComparer.Compare, base.DefaultFormatter)
require.NoError(t, err)
got := m.DebugString(base.DefaultFormatter, true)
want := tc.input
if tc.output != "" {
want = tc.output
}
require.Equal(t, want, got)
})
}
}