mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-30 18:35:18 +00:00
321 lines
8.2 KiB
Go
321 lines
8.2 KiB
Go
|
// Copyright 2018 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 keyspan
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/cockroachdb/datadriven"
|
||
|
"github.com/cockroachdb/pebble/internal/base"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
var spanRe = regexp.MustCompile(`(\d+):\s*(\w+)-*(\w+)\w*([^\n]*)`)
|
||
|
|
||
|
func parseSpanSingleKey(t *testing.T, s string, kind base.InternalKeyKind) Span {
|
||
|
m := spanRe.FindStringSubmatch(s)
|
||
|
if len(m) != 5 {
|
||
|
t.Fatalf("expected 5 components, but found %d: %s", len(m), s)
|
||
|
}
|
||
|
seqNum, err := strconv.Atoi(m[1])
|
||
|
require.NoError(t, err)
|
||
|
return Span{
|
||
|
Start: []byte(m[2]),
|
||
|
End: []byte(m[3]),
|
||
|
Keys: []Key{
|
||
|
{
|
||
|
Trailer: base.MakeTrailer(uint64(seqNum), kind),
|
||
|
Value: []byte(strings.TrimSpace(m[4])),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func buildSpans(
|
||
|
t *testing.T, cmp base.Compare, formatKey base.FormatKey, s string, kind base.InternalKeyKind,
|
||
|
) []Span {
|
||
|
var spans []Span
|
||
|
f := &Fragmenter{
|
||
|
Cmp: cmp,
|
||
|
Format: formatKey,
|
||
|
Emit: func(fragmented Span) {
|
||
|
spans = append(spans, fragmented)
|
||
|
},
|
||
|
}
|
||
|
for _, line := range strings.Split(s, "\n") {
|
||
|
if strings.HasPrefix(line, "truncate-and-flush-to ") {
|
||
|
parts := strings.Split(line, " ")
|
||
|
if len(parts) != 2 {
|
||
|
t.Fatalf("expected 2 components, but found %d: %s", len(parts), line)
|
||
|
}
|
||
|
f.TruncateAndFlushTo([]byte(parts[1]))
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
f.Add(parseSpanSingleKey(t, line, kind))
|
||
|
}
|
||
|
f.Finish()
|
||
|
return spans
|
||
|
}
|
||
|
|
||
|
func formatAlphabeticSpans(spans []Span) string {
|
||
|
isLetter := func(b []byte) bool {
|
||
|
if len(b) != 1 {
|
||
|
return false
|
||
|
}
|
||
|
return b[0] >= 'a' && b[0] <= 'z'
|
||
|
}
|
||
|
|
||
|
var buf bytes.Buffer
|
||
|
for _, v := range spans {
|
||
|
switch {
|
||
|
case !v.Valid():
|
||
|
fmt.Fprintf(&buf, "<invalid>\n")
|
||
|
case v.Empty():
|
||
|
fmt.Fprintf(&buf, "<empty>\n")
|
||
|
case !isLetter(v.Start) || !isLetter(v.End) || v.Start[0] == v.End[0]:
|
||
|
for _, k := range v.Keys {
|
||
|
fmt.Fprintf(&buf, "%d: %s-%s", k.SeqNum(), v.Start, v.End)
|
||
|
if len(k.Value) > 0 {
|
||
|
buf.WriteString(strings.Repeat(" ", int('z'-v.End[0]+1)))
|
||
|
buf.WriteString(string(k.Value))
|
||
|
}
|
||
|
fmt.Fprintln(&buf)
|
||
|
}
|
||
|
default:
|
||
|
for _, k := range v.Keys {
|
||
|
fmt.Fprintf(&buf, "%d: %s%s%s%s",
|
||
|
k.SeqNum(),
|
||
|
strings.Repeat(" ", int(v.Start[0]-'a')),
|
||
|
v.Start,
|
||
|
strings.Repeat("-", int(v.End[0]-v.Start[0]-1)),
|
||
|
v.End)
|
||
|
if len(k.Value) > 0 {
|
||
|
buf.WriteString(strings.Repeat(" ", int('z'-v.End[0]+1)))
|
||
|
buf.WriteString(string(k.Value))
|
||
|
}
|
||
|
fmt.Fprintln(&buf)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func TestFragmenter(t *testing.T) {
|
||
|
cmp := base.DefaultComparer.Compare
|
||
|
fmtKey := base.DefaultComparer.FormatKey
|
||
|
|
||
|
var getRe = regexp.MustCompile(`(\w+)#(\d+)`)
|
||
|
|
||
|
parseGet := func(t *testing.T, s string) (string, int) {
|
||
|
m := getRe.FindStringSubmatch(s)
|
||
|
if len(m) != 3 {
|
||
|
t.Fatalf("expected 3 components, but found %d", len(m))
|
||
|
}
|
||
|
seq, err := strconv.Atoi(m[2])
|
||
|
require.NoError(t, err)
|
||
|
return m[1], seq
|
||
|
}
|
||
|
|
||
|
var iter FragmentIterator
|
||
|
|
||
|
// Returns true if the specified <key,seq> pair is deleted at the specified
|
||
|
// read sequence number. Get ignores spans newer than the read sequence
|
||
|
// number. This is a simple version of what full processing of range
|
||
|
// tombstones looks like.
|
||
|
deleted := func(key []byte, seq, readSeq uint64) bool {
|
||
|
s := Get(cmp, iter, key)
|
||
|
return s != nil && s.CoversAt(readSeq, seq)
|
||
|
}
|
||
|
|
||
|
datadriven.RunTest(t, "testdata/fragmenter", func(t *testing.T, d *datadriven.TestData) string {
|
||
|
switch d.Cmd {
|
||
|
case "build":
|
||
|
return func() (result string) {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
result = fmt.Sprint(r)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeDelete)
|
||
|
iter = NewIter(cmp, spans)
|
||
|
return formatAlphabeticSpans(spans)
|
||
|
}()
|
||
|
|
||
|
case "get":
|
||
|
if len(d.CmdArgs) != 1 {
|
||
|
return fmt.Sprintf("expected 1 argument, but found %s", d.CmdArgs)
|
||
|
}
|
||
|
if d.CmdArgs[0].Key != "t" {
|
||
|
return fmt.Sprintf("expected timestamp argument, but found %s", d.CmdArgs[0])
|
||
|
}
|
||
|
readSeq, err := strconv.Atoi(d.CmdArgs[0].Vals[0])
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
var results []string
|
||
|
for _, p := range strings.Split(d.Input, " ") {
|
||
|
key, seq := parseGet(t, p)
|
||
|
if deleted([]byte(key), uint64(seq), uint64(readSeq)) {
|
||
|
results = append(results, "deleted")
|
||
|
} else {
|
||
|
results = append(results, "alive")
|
||
|
}
|
||
|
}
|
||
|
return strings.Join(results, " ")
|
||
|
|
||
|
default:
|
||
|
return fmt.Sprintf("unknown command: %s", d.Cmd)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestFragmenterCovers(t *testing.T) {
|
||
|
datadriven.RunTest(t, "testdata/fragmenter_covers", func(t *testing.T, d *datadriven.TestData) string {
|
||
|
switch d.Cmd {
|
||
|
case "build":
|
||
|
f := &Fragmenter{
|
||
|
Cmp: base.DefaultComparer.Compare,
|
||
|
Format: base.DefaultComparer.FormatKey,
|
||
|
Emit: func(fragmented Span) {
|
||
|
},
|
||
|
}
|
||
|
var buf bytes.Buffer
|
||
|
for _, line := range strings.Split(d.Input, "\n") {
|
||
|
switch {
|
||
|
case strings.HasPrefix(line, "add "):
|
||
|
t := parseSpanSingleKey(t, strings.TrimPrefix(line, "add "), base.InternalKeyKindRangeDelete)
|
||
|
f.Add(t)
|
||
|
case strings.HasPrefix(line, "deleted "):
|
||
|
fields := strings.Fields(strings.TrimPrefix(line, "deleted "))
|
||
|
key := base.ParseInternalKey(fields[0])
|
||
|
snapshot, err := strconv.ParseUint(fields[1], 10, 64)
|
||
|
if err != nil {
|
||
|
return err.Error()
|
||
|
}
|
||
|
func() {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
fmt.Fprintf(&buf, "%s: %s\n", key, r)
|
||
|
}
|
||
|
}()
|
||
|
switch f.Covers(key, snapshot) {
|
||
|
case NoCover:
|
||
|
fmt.Fprintf(&buf, "%s: none\n", key)
|
||
|
case CoversInvisibly:
|
||
|
fmt.Fprintf(&buf, "%s: invisibly\n", key)
|
||
|
case CoversVisibly:
|
||
|
fmt.Fprintf(&buf, "%s: visibly\n", key)
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
}
|
||
|
return buf.String()
|
||
|
|
||
|
default:
|
||
|
return fmt.Sprintf("unknown command: %s", d.Cmd)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestFragmenterTruncateAndFlushTo(t *testing.T) {
|
||
|
cmp := base.DefaultComparer.Compare
|
||
|
fmtKey := base.DefaultComparer.FormatKey
|
||
|
|
||
|
datadriven.RunTest(t, "testdata/fragmenter_truncate_and_flush_to", func(t *testing.T, d *datadriven.TestData) string {
|
||
|
switch d.Cmd {
|
||
|
case "build":
|
||
|
return func() (result string) {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
result = fmt.Sprint(r)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeDelete)
|
||
|
return formatAlphabeticSpans(spans)
|
||
|
}()
|
||
|
|
||
|
default:
|
||
|
return fmt.Sprintf("unknown command: %s", d.Cmd)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestFragmenter_Values(t *testing.T) {
|
||
|
cmp := base.DefaultComparer.Compare
|
||
|
fmtKey := base.DefaultComparer.FormatKey
|
||
|
|
||
|
datadriven.RunTest(t, "testdata/fragmenter_values", func(t *testing.T, d *datadriven.TestData) string {
|
||
|
switch d.Cmd {
|
||
|
case "build":
|
||
|
return func() (result string) {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
result = fmt.Sprint(r)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeKeySet)
|
||
|
return formatAlphabeticSpans(spans)
|
||
|
}()
|
||
|
|
||
|
default:
|
||
|
return fmt.Sprintf("unknown command: %s", d.Cmd)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestFragmenter_EmitOrder(t *testing.T) {
|
||
|
var buf bytes.Buffer
|
||
|
|
||
|
datadriven.RunTest(t, "testdata/fragmenter_emit_order", func(t *testing.T, d *datadriven.TestData) string {
|
||
|
switch d.Cmd {
|
||
|
case "build":
|
||
|
buf.Reset()
|
||
|
f := Fragmenter{
|
||
|
Cmp: base.DefaultComparer.Compare,
|
||
|
Format: base.DefaultComparer.FormatKey,
|
||
|
Emit: func(span Span) {
|
||
|
fmt.Fprintf(&buf, "%s %s:",
|
||
|
base.DefaultComparer.FormatKey(span.Start),
|
||
|
base.DefaultComparer.FormatKey(span.End))
|
||
|
for i, k := range span.Keys {
|
||
|
if i == 0 {
|
||
|
fmt.Fprint(&buf, " ")
|
||
|
} else {
|
||
|
fmt.Fprint(&buf, ", ")
|
||
|
}
|
||
|
fmt.Fprintf(&buf, "#%d,%s", k.SeqNum(), k.Kind())
|
||
|
}
|
||
|
fmt.Fprintln(&buf, "\n-")
|
||
|
},
|
||
|
}
|
||
|
for _, line := range strings.Split(d.Input, "\n") {
|
||
|
fields := strings.Fields(line)
|
||
|
if len(fields) != 2 {
|
||
|
panic(fmt.Sprintf("datadriven test: expect 2 fields, found %d", len(fields)))
|
||
|
}
|
||
|
k := base.ParseInternalKey(fields[0])
|
||
|
f.Add(Span{
|
||
|
Start: k.UserKey,
|
||
|
End: []byte(fields[1]),
|
||
|
Keys: []Key{{Trailer: k.Trailer}},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
f.Finish()
|
||
|
return buf.String()
|
||
|
default:
|
||
|
panic(fmt.Sprintf("unrecognized command %q", d.Cmd))
|
||
|
}
|
||
|
})
|
||
|
}
|