mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-11-15 04:35:17 +00:00
433 lines
10 KiB
Go
433 lines
10 KiB
Go
// Copyright 2023 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 (
|
|
"fmt"
|
|
"go/token"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cockroachdb/datadriven"
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/cockroachdb/pebble/internal/dsl"
|
|
)
|
|
|
|
// This file contains testing facilities for Spans and FragmentIterators. It's
|
|
// defined here so that it may be used by the keyspan package to test its
|
|
// various FragmentIterator implementations.
|
|
//
|
|
// TODO(jackson): Move keyspan.{Span,Key,FragmentIterator} into internal/base,
|
|
// and then move the testing facilities to an independent package, eg
|
|
// internal/itertest.
|
|
|
|
// probe defines an interface for probes that may inspect or mutate internal
|
|
// span iterator behavior.
|
|
type probe interface {
|
|
// probe inspects, and possibly manipulates, iterator operations' results.
|
|
probe(*probeContext)
|
|
}
|
|
|
|
func parseProbes(probeDSLs ...string) []probe {
|
|
probes := make([]probe, len(probeDSLs))
|
|
var err error
|
|
for i := range probeDSLs {
|
|
probes[i], err = probeParser.Parse(probeDSLs[i])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return probes
|
|
}
|
|
|
|
func attachProbes(iter FragmentIterator, pctx probeContext, probes ...probe) FragmentIterator {
|
|
if pctx.log == nil {
|
|
pctx.log = io.Discard
|
|
}
|
|
for i := range probes {
|
|
iter = &probeIterator{
|
|
iter: iter,
|
|
probe: probes[i],
|
|
probeCtx: pctx,
|
|
}
|
|
}
|
|
return iter
|
|
}
|
|
|
|
// probeContext provides the context within which a probe is run. It includes
|
|
// information about the iterator operation in progress.
|
|
type probeContext struct {
|
|
op
|
|
log io.Writer
|
|
}
|
|
|
|
type op struct {
|
|
Kind OpKind
|
|
SeekKey []byte
|
|
Span *Span
|
|
Err error
|
|
}
|
|
|
|
// ErrInjected is an error artificially injected for testing.
|
|
var ErrInjected = &errorProbe{name: "ErrInjected", err: errors.New("injected error")}
|
|
|
|
var probeParser = func() *dsl.Parser[probe] {
|
|
valuerParser := dsl.NewParser[valuer]()
|
|
valuerParser.DefineConstant("StartKey", func() valuer { return startKey{} })
|
|
valuerParser.DefineFunc("Bytes",
|
|
func(p *dsl.Parser[valuer], s *dsl.Scanner) valuer {
|
|
v := bytesConstant{bytes: []byte(s.ConsumeString())}
|
|
s.Consume(token.RPAREN)
|
|
return v
|
|
})
|
|
|
|
predicateParser := dsl.NewPredicateParser[*probeContext]()
|
|
predicateParser.DefineFunc("Equal",
|
|
func(p *dsl.Parser[dsl.Predicate[*probeContext]], s *dsl.Scanner) dsl.Predicate[*probeContext] {
|
|
eq := equal{
|
|
valuerParser.ParseFromPos(s, s.Scan()),
|
|
valuerParser.ParseFromPos(s, s.Scan()),
|
|
}
|
|
s.Consume(token.RPAREN)
|
|
return eq
|
|
})
|
|
for i, name := range opNames {
|
|
opKind := OpKind(i)
|
|
predicateParser.DefineConstant(name, func() dsl.Predicate[*probeContext] {
|
|
// An OpKind implements dsl.Predicate[*probeContext].
|
|
return opKind
|
|
})
|
|
}
|
|
probeParser := dsl.NewParser[probe]()
|
|
probeParser.DefineConstant("ErrInjected", func() probe { return ErrInjected })
|
|
probeParser.DefineConstant("noop", func() probe { return noop{} })
|
|
probeParser.DefineFunc("If",
|
|
func(p *dsl.Parser[probe], s *dsl.Scanner) probe {
|
|
probe := ifProbe{
|
|
predicateParser.ParseFromPos(s, s.Scan()),
|
|
probeParser.ParseFromPos(s, s.Scan()),
|
|
probeParser.ParseFromPos(s, s.Scan()),
|
|
}
|
|
s.Consume(token.RPAREN)
|
|
return probe
|
|
})
|
|
probeParser.DefineFunc("Return",
|
|
func(p *dsl.Parser[probe], s *dsl.Scanner) (ret probe) {
|
|
switch tok := s.Scan(); tok.Kind {
|
|
case token.STRING:
|
|
str, err := strconv.Unquote(tok.Lit)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
span := ParseSpan(str)
|
|
ret = returnSpan{s: &span}
|
|
case token.IDENT:
|
|
switch tok.Lit {
|
|
case "nil":
|
|
ret = returnSpan{s: nil}
|
|
default:
|
|
panic(errors.Newf("unrecognized return value %q", tok.Lit))
|
|
}
|
|
}
|
|
s.Consume(token.RPAREN)
|
|
return ret
|
|
})
|
|
probeParser.DefineFunc("Log",
|
|
func(p *dsl.Parser[probe], s *dsl.Scanner) (ret probe) {
|
|
ret = loggingProbe{prefix: s.ConsumeString()}
|
|
s.Consume(token.RPAREN)
|
|
return ret
|
|
})
|
|
return probeParser
|
|
}()
|
|
|
|
// probe implementations
|
|
|
|
type errorProbe struct {
|
|
name string
|
|
err error
|
|
}
|
|
|
|
func (p *errorProbe) String() string { return p.name }
|
|
func (p *errorProbe) Error() error { return p.err }
|
|
func (p *errorProbe) probe(pctx *probeContext) {
|
|
pctx.op.Err = p.err
|
|
pctx.op.Span = nil
|
|
}
|
|
|
|
// ifProbe is a conditional probe. If its predicate evaluates to true, it probes
|
|
// using its Then probe. If its predicate evalutes to false, it probes using its
|
|
// Else probe.
|
|
type ifProbe struct {
|
|
Predicate dsl.Predicate[*probeContext]
|
|
Then probe
|
|
Else probe
|
|
}
|
|
|
|
func (p ifProbe) String() string { return fmt.Sprintf("(If %s %s %s)", p.Predicate, p.Then, p.Else) }
|
|
func (p ifProbe) probe(pctx *probeContext) {
|
|
if p.Predicate.Evaluate(pctx) {
|
|
p.Then.probe(pctx)
|
|
} else {
|
|
p.Else.probe(pctx)
|
|
}
|
|
}
|
|
|
|
type returnSpan struct {
|
|
s *Span
|
|
}
|
|
|
|
func (p returnSpan) String() string {
|
|
if p.s == nil {
|
|
return "(Return nil)"
|
|
}
|
|
return fmt.Sprintf("(Return %q)", p.s.String())
|
|
}
|
|
|
|
func (p returnSpan) probe(pctx *probeContext) {
|
|
pctx.op.Span = p.s
|
|
pctx.op.Err = nil
|
|
}
|
|
|
|
type noop struct{}
|
|
|
|
func (noop) String() string { return "Noop" }
|
|
func (noop) probe(pctx *probeContext) {}
|
|
|
|
type loggingProbe struct {
|
|
prefix string
|
|
}
|
|
|
|
func (lp loggingProbe) String() string { return fmt.Sprintf("(Log %q)", lp.prefix) }
|
|
func (lp loggingProbe) probe(pctx *probeContext) {
|
|
opStr := strings.TrimPrefix(pctx.op.Kind.String(), "Op")
|
|
fmt.Fprintf(pctx.log, "%s%s(", lp.prefix, opStr)
|
|
if pctx.op.SeekKey != nil {
|
|
fmt.Fprintf(pctx.log, "%q", pctx.op.SeekKey)
|
|
}
|
|
fmt.Fprint(pctx.log, ") = ")
|
|
if pctx.op.Span == nil {
|
|
fmt.Fprint(pctx.log, "nil")
|
|
if pctx.op.Err != nil {
|
|
fmt.Fprintf(pctx.log, " <err=%q>", pctx.op.Err)
|
|
}
|
|
} else {
|
|
fmt.Fprint(pctx.log, pctx.op.Span.String())
|
|
}
|
|
fmt.Fprintln(pctx.log)
|
|
}
|
|
|
|
// dsl.Predicate[*probeContext] implementations.
|
|
|
|
type equal struct {
|
|
a, b valuer
|
|
}
|
|
|
|
func (e equal) String() string { return fmt.Sprintf("(Equal %s %s)", e.a, e.b) }
|
|
func (e equal) Evaluate(pctx *probeContext) bool {
|
|
return reflect.DeepEqual(e.a.value(pctx), e.b.value(pctx))
|
|
}
|
|
|
|
// OpKind indicates the type of iterator operation being performed.
|
|
type OpKind int8
|
|
|
|
const (
|
|
OpSeekGE OpKind = iota
|
|
OpSeekLT
|
|
OpFirst
|
|
OpLast
|
|
OpNext
|
|
OpPrev
|
|
OpClose
|
|
numOpKinds
|
|
)
|
|
|
|
func (o OpKind) String() string { return opNames[o] }
|
|
func (o OpKind) Evaluate(pctx *probeContext) bool { return pctx.op.Kind == o }
|
|
|
|
var opNames = [numOpKinds]string{
|
|
OpSeekGE: "OpSeekGE",
|
|
OpSeekLT: "OpSeekLT",
|
|
OpFirst: "OpFirst",
|
|
OpLast: "OpLast",
|
|
OpNext: "OpNext",
|
|
OpPrev: "OpPrev",
|
|
OpClose: "OpClose",
|
|
}
|
|
|
|
// valuer implementations
|
|
|
|
type valuer interface {
|
|
fmt.Stringer
|
|
value(pctx *probeContext) any
|
|
}
|
|
|
|
type bytesConstant struct {
|
|
bytes []byte
|
|
}
|
|
|
|
func (b bytesConstant) String() string { return fmt.Sprintf("%q", string(b.bytes)) }
|
|
func (b bytesConstant) value(pctx *probeContext) any { return b.bytes }
|
|
|
|
type startKey struct{}
|
|
|
|
func (s startKey) String() string { return "StartKey" }
|
|
func (s startKey) value(pctx *probeContext) any {
|
|
if pctx.op.Span == nil {
|
|
return nil
|
|
}
|
|
return pctx.op.Span.Start
|
|
}
|
|
|
|
type probeIterator struct {
|
|
iter FragmentIterator
|
|
err error
|
|
probe probe
|
|
probeCtx probeContext
|
|
}
|
|
|
|
// Assert that probeIterator implements the fragment iterator interface.
|
|
var _ FragmentIterator = (*probeIterator)(nil)
|
|
|
|
func (p *probeIterator) handleOp(preProbeOp op) *Span {
|
|
p.probeCtx.op = preProbeOp
|
|
if preProbeOp.Span == nil && p.iter != nil {
|
|
p.probeCtx.op.Err = p.iter.Error()
|
|
}
|
|
|
|
p.probe.probe(&p.probeCtx)
|
|
p.err = p.probeCtx.op.Err
|
|
return p.probeCtx.op.Span
|
|
}
|
|
|
|
func (p *probeIterator) SeekGE(key []byte) *Span {
|
|
op := op{
|
|
Kind: OpSeekGE,
|
|
SeekKey: key,
|
|
}
|
|
if p.iter != nil {
|
|
op.Span = p.iter.SeekGE(key)
|
|
}
|
|
return p.handleOp(op)
|
|
}
|
|
|
|
func (p *probeIterator) SeekLT(key []byte) *Span {
|
|
op := op{
|
|
Kind: OpSeekLT,
|
|
SeekKey: key,
|
|
}
|
|
if p.iter != nil {
|
|
op.Span = p.iter.SeekLT(key)
|
|
}
|
|
return p.handleOp(op)
|
|
}
|
|
|
|
func (p *probeIterator) First() *Span {
|
|
op := op{Kind: OpFirst}
|
|
if p.iter != nil {
|
|
op.Span = p.iter.First()
|
|
}
|
|
return p.handleOp(op)
|
|
}
|
|
|
|
func (p *probeIterator) Last() *Span {
|
|
op := op{Kind: OpLast}
|
|
if p.iter != nil {
|
|
op.Span = p.iter.Last()
|
|
}
|
|
return p.handleOp(op)
|
|
}
|
|
|
|
func (p *probeIterator) Next() *Span {
|
|
op := op{Kind: OpNext}
|
|
if p.iter != nil {
|
|
op.Span = p.iter.Next()
|
|
}
|
|
return p.handleOp(op)
|
|
}
|
|
|
|
func (p *probeIterator) Prev() *Span {
|
|
op := op{Kind: OpPrev}
|
|
if p.iter != nil {
|
|
op.Span = p.iter.Prev()
|
|
}
|
|
return p.handleOp(op)
|
|
}
|
|
|
|
func (p *probeIterator) Error() error {
|
|
return p.err
|
|
}
|
|
|
|
func (p *probeIterator) Close() error {
|
|
op := op{Kind: OpClose}
|
|
if p.iter != nil {
|
|
op.Err = p.iter.Close()
|
|
}
|
|
|
|
p.probeCtx.op = op
|
|
p.probe.probe(&p.probeCtx)
|
|
p.err = p.probeCtx.op.Err
|
|
return p.err
|
|
}
|
|
|
|
// runIterCmd evaluates a datadriven command controlling an internal
|
|
// keyspan.FragmentIterator, writing the results of the iterator operations to
|
|
// the provided writer.
|
|
func runIterCmd(t *testing.T, td *datadriven.TestData, iter FragmentIterator, w io.Writer) {
|
|
lines := strings.Split(strings.TrimSpace(td.Input), "\n")
|
|
for i, line := range lines {
|
|
if i > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
line = strings.TrimSpace(line)
|
|
i := strings.IndexByte(line, '#')
|
|
iterCmd := line
|
|
if i > 0 {
|
|
iterCmd = string(line[:i])
|
|
}
|
|
runIterOp(w, iter, iterCmd)
|
|
}
|
|
}
|
|
|
|
var iterDelim = map[rune]bool{',': true, ' ': true, '(': true, ')': true, '"': true}
|
|
|
|
func runIterOp(w io.Writer, it FragmentIterator, op string) {
|
|
fields := strings.FieldsFunc(op, func(r rune) bool { return iterDelim[r] })
|
|
var s *Span
|
|
switch strings.ToLower(fields[0]) {
|
|
case "first":
|
|
s = it.First()
|
|
case "last":
|
|
s = it.Last()
|
|
case "seekge", "seek-ge":
|
|
if len(fields) == 1 {
|
|
panic(fmt.Sprintf("unable to parse iter op %q", op))
|
|
}
|
|
s = it.SeekGE([]byte(fields[1]))
|
|
case "seeklt", "seek-lt":
|
|
if len(fields) == 1 {
|
|
panic(fmt.Sprintf("unable to parse iter op %q", op))
|
|
}
|
|
s = it.SeekLT([]byte(fields[1]))
|
|
case "next":
|
|
s = it.Next()
|
|
case "prev":
|
|
s = it.Prev()
|
|
default:
|
|
panic(fmt.Sprintf("unrecognized iter op %q", fields[0]))
|
|
}
|
|
if s == nil {
|
|
fmt.Fprint(w, "<nil>")
|
|
if err := it.Error(); err != nil {
|
|
fmt.Fprintf(w, " err=<%s>", it.Error())
|
|
}
|
|
return
|
|
}
|
|
fmt.Fprint(w, s)
|
|
}
|