github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/internal/keyspan/datadriven_test.go (about)

     1  // Copyright 2023 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 keyspan
     6  
     7  import (
     8  	"fmt"
     9  	"go/token"
    10  	"io"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/cockroachdb/datadriven"
    17  	"github.com/cockroachdb/errors"
    18  	"github.com/cockroachdb/pebble/internal/dsl"
    19  )
    20  
    21  // This file contains testing facilities for Spans and FragmentIterators. It's
    22  // defined here so that it may be used by the keyspan package to test its
    23  // various FragmentIterator implementations.
    24  //
    25  // TODO(jackson): Move keyspan.{Span,Key,FragmentIterator} into internal/base,
    26  // and then move the testing facilities to an independent package, eg
    27  // internal/itertest.
    28  
    29  // probe defines an interface for probes that may inspect or mutate internal
    30  // span iterator behavior.
    31  type probe interface {
    32  	// probe inspects, and possibly manipulates, iterator operations' results.
    33  	probe(*probeContext)
    34  }
    35  
    36  func parseProbes(probeDSLs ...string) []probe {
    37  	probes := make([]probe, len(probeDSLs))
    38  	var err error
    39  	for i := range probeDSLs {
    40  		probes[i], err = probeParser.Parse(probeDSLs[i])
    41  		if err != nil {
    42  			panic(err)
    43  		}
    44  	}
    45  	return probes
    46  }
    47  
    48  func attachProbes(iter FragmentIterator, pctx probeContext, probes ...probe) FragmentIterator {
    49  	if pctx.log == nil {
    50  		pctx.log = io.Discard
    51  	}
    52  	for i := range probes {
    53  		iter = &probeIterator{
    54  			iter:     iter,
    55  			probe:    probes[i],
    56  			probeCtx: pctx,
    57  		}
    58  	}
    59  	return iter
    60  }
    61  
    62  // probeContext provides the context within which a probe is run. It includes
    63  // information about the iterator operation in progress.
    64  type probeContext struct {
    65  	op
    66  	log io.Writer
    67  }
    68  
    69  type op struct {
    70  	Kind    OpKind
    71  	SeekKey []byte
    72  	Span    *Span
    73  	Err     error
    74  }
    75  
    76  // ErrInjected is an error artificially injected for testing.
    77  var ErrInjected = &errorProbe{name: "ErrInjected", err: errors.New("injected error")}
    78  
    79  var probeParser = func() *dsl.Parser[probe] {
    80  	valuerParser := dsl.NewParser[valuer]()
    81  	valuerParser.DefineConstant("StartKey", func() valuer { return startKey{} })
    82  	valuerParser.DefineFunc("Bytes",
    83  		func(p *dsl.Parser[valuer], s *dsl.Scanner) valuer {
    84  			v := bytesConstant{bytes: []byte(s.ConsumeString())}
    85  			s.Consume(token.RPAREN)
    86  			return v
    87  		})
    88  
    89  	predicateParser := dsl.NewPredicateParser[*probeContext]()
    90  	predicateParser.DefineFunc("Equal",
    91  		func(p *dsl.Parser[dsl.Predicate[*probeContext]], s *dsl.Scanner) dsl.Predicate[*probeContext] {
    92  			eq := equal{
    93  				valuerParser.ParseFromPos(s, s.Scan()),
    94  				valuerParser.ParseFromPos(s, s.Scan()),
    95  			}
    96  			s.Consume(token.RPAREN)
    97  			return eq
    98  		})
    99  	for i, name := range opNames {
   100  		opKind := OpKind(i)
   101  		predicateParser.DefineConstant(name, func() dsl.Predicate[*probeContext] {
   102  			// An OpKind implements dsl.Predicate[*probeContext].
   103  			return opKind
   104  		})
   105  	}
   106  	probeParser := dsl.NewParser[probe]()
   107  	probeParser.DefineConstant("ErrInjected", func() probe { return ErrInjected })
   108  	probeParser.DefineConstant("noop", func() probe { return noop{} })
   109  	probeParser.DefineFunc("If",
   110  		func(p *dsl.Parser[probe], s *dsl.Scanner) probe {
   111  			probe := ifProbe{
   112  				predicateParser.ParseFromPos(s, s.Scan()),
   113  				probeParser.ParseFromPos(s, s.Scan()),
   114  				probeParser.ParseFromPos(s, s.Scan()),
   115  			}
   116  			s.Consume(token.RPAREN)
   117  			return probe
   118  		})
   119  	probeParser.DefineFunc("Return",
   120  		func(p *dsl.Parser[probe], s *dsl.Scanner) (ret probe) {
   121  			switch tok := s.Scan(); tok.Kind {
   122  			case token.STRING:
   123  				str, err := strconv.Unquote(tok.Lit)
   124  				if err != nil {
   125  					panic(err)
   126  				}
   127  				span := ParseSpan(str)
   128  				ret = returnSpan{s: &span}
   129  			case token.IDENT:
   130  				switch tok.Lit {
   131  				case "nil":
   132  					ret = returnSpan{s: nil}
   133  				default:
   134  					panic(errors.Newf("unrecognized return value %q", tok.Lit))
   135  				}
   136  			}
   137  			s.Consume(token.RPAREN)
   138  			return ret
   139  		})
   140  	probeParser.DefineFunc("Log",
   141  		func(p *dsl.Parser[probe], s *dsl.Scanner) (ret probe) {
   142  			ret = loggingProbe{prefix: s.ConsumeString()}
   143  			s.Consume(token.RPAREN)
   144  			return ret
   145  		})
   146  	return probeParser
   147  }()
   148  
   149  // probe implementations
   150  
   151  type errorProbe struct {
   152  	name string
   153  	err  error
   154  }
   155  
   156  func (p *errorProbe) String() string { return p.name }
   157  func (p *errorProbe) Error() error   { return p.err }
   158  func (p *errorProbe) probe(pctx *probeContext) {
   159  	pctx.op.Err = p.err
   160  	pctx.op.Span = nil
   161  }
   162  
   163  // ifProbe is a conditional probe. If its predicate evaluates to true, it probes
   164  // using its Then probe. If its predicate evalutes to false, it probes using its
   165  // Else probe.
   166  type ifProbe struct {
   167  	Predicate dsl.Predicate[*probeContext]
   168  	Then      probe
   169  	Else      probe
   170  }
   171  
   172  func (p ifProbe) String() string { return fmt.Sprintf("(If %s %s %s)", p.Predicate, p.Then, p.Else) }
   173  func (p ifProbe) probe(pctx *probeContext) {
   174  	if p.Predicate.Evaluate(pctx) {
   175  		p.Then.probe(pctx)
   176  	} else {
   177  		p.Else.probe(pctx)
   178  	}
   179  }
   180  
   181  type returnSpan struct {
   182  	s *Span
   183  }
   184  
   185  func (p returnSpan) String() string {
   186  	if p.s == nil {
   187  		return "(Return nil)"
   188  	}
   189  	return fmt.Sprintf("(Return %q)", p.s.String())
   190  }
   191  
   192  func (p returnSpan) probe(pctx *probeContext) {
   193  	pctx.op.Span = p.s
   194  	pctx.op.Err = nil
   195  }
   196  
   197  type noop struct{}
   198  
   199  func (noop) String() string           { return "Noop" }
   200  func (noop) probe(pctx *probeContext) {}
   201  
   202  type loggingProbe struct {
   203  	prefix string
   204  }
   205  
   206  func (lp loggingProbe) String() string { return fmt.Sprintf("(Log %q)", lp.prefix) }
   207  func (lp loggingProbe) probe(pctx *probeContext) {
   208  	opStr := strings.TrimPrefix(pctx.op.Kind.String(), "Op")
   209  	fmt.Fprintf(pctx.log, "%s%s(", lp.prefix, opStr)
   210  	if pctx.op.SeekKey != nil {
   211  		fmt.Fprintf(pctx.log, "%q", pctx.op.SeekKey)
   212  	}
   213  	fmt.Fprint(pctx.log, ") = ")
   214  	if pctx.op.Span == nil {
   215  		fmt.Fprint(pctx.log, "nil")
   216  		if pctx.op.Err != nil {
   217  			fmt.Fprintf(pctx.log, " <err=%q>", pctx.op.Err)
   218  		}
   219  	} else {
   220  		fmt.Fprint(pctx.log, pctx.op.Span.String())
   221  	}
   222  	fmt.Fprintln(pctx.log)
   223  }
   224  
   225  // dsl.Predicate[*probeContext] implementations.
   226  
   227  type equal struct {
   228  	a, b valuer
   229  }
   230  
   231  func (e equal) String() string { return fmt.Sprintf("(Equal %s %s)", e.a, e.b) }
   232  func (e equal) Evaluate(pctx *probeContext) bool {
   233  	return reflect.DeepEqual(e.a.value(pctx), e.b.value(pctx))
   234  }
   235  
   236  // OpKind indicates the type of iterator operation being performed.
   237  type OpKind int8
   238  
   239  const (
   240  	OpSeekGE OpKind = iota
   241  	OpSeekLT
   242  	OpFirst
   243  	OpLast
   244  	OpNext
   245  	OpPrev
   246  	OpClose
   247  	numOpKinds
   248  )
   249  
   250  func (o OpKind) String() string                   { return opNames[o] }
   251  func (o OpKind) Evaluate(pctx *probeContext) bool { return pctx.op.Kind == o }
   252  
   253  var opNames = [numOpKinds]string{
   254  	OpSeekGE: "OpSeekGE",
   255  	OpSeekLT: "OpSeekLT",
   256  	OpFirst:  "OpFirst",
   257  	OpLast:   "OpLast",
   258  	OpNext:   "OpNext",
   259  	OpPrev:   "OpPrev",
   260  	OpClose:  "OpClose",
   261  }
   262  
   263  // valuer implementations
   264  
   265  type valuer interface {
   266  	fmt.Stringer
   267  	value(pctx *probeContext) any
   268  }
   269  
   270  type bytesConstant struct {
   271  	bytes []byte
   272  }
   273  
   274  func (b bytesConstant) String() string               { return fmt.Sprintf("%q", string(b.bytes)) }
   275  func (b bytesConstant) value(pctx *probeContext) any { return b.bytes }
   276  
   277  type startKey struct{}
   278  
   279  func (s startKey) String() string { return "StartKey" }
   280  func (s startKey) value(pctx *probeContext) any {
   281  	if pctx.op.Span == nil {
   282  		return nil
   283  	}
   284  	return pctx.op.Span.Start
   285  }
   286  
   287  type probeIterator struct {
   288  	iter     FragmentIterator
   289  	err      error
   290  	probe    probe
   291  	probeCtx probeContext
   292  }
   293  
   294  // Assert that probeIterator implements the fragment iterator interface.
   295  var _ FragmentIterator = (*probeIterator)(nil)
   296  
   297  func (p *probeIterator) handleOp(preProbeOp op) *Span {
   298  	p.probeCtx.op = preProbeOp
   299  	if preProbeOp.Span == nil && p.iter != nil {
   300  		p.probeCtx.op.Err = p.iter.Error()
   301  	}
   302  
   303  	p.probe.probe(&p.probeCtx)
   304  	p.err = p.probeCtx.op.Err
   305  	return p.probeCtx.op.Span
   306  }
   307  
   308  func (p *probeIterator) SeekGE(key []byte) *Span {
   309  	op := op{
   310  		Kind:    OpSeekGE,
   311  		SeekKey: key,
   312  	}
   313  	if p.iter != nil {
   314  		op.Span = p.iter.SeekGE(key)
   315  	}
   316  	return p.handleOp(op)
   317  }
   318  
   319  func (p *probeIterator) SeekLT(key []byte) *Span {
   320  	op := op{
   321  		Kind:    OpSeekLT,
   322  		SeekKey: key,
   323  	}
   324  	if p.iter != nil {
   325  		op.Span = p.iter.SeekLT(key)
   326  	}
   327  	return p.handleOp(op)
   328  }
   329  
   330  func (p *probeIterator) First() *Span {
   331  	op := op{Kind: OpFirst}
   332  	if p.iter != nil {
   333  		op.Span = p.iter.First()
   334  	}
   335  	return p.handleOp(op)
   336  }
   337  
   338  func (p *probeIterator) Last() *Span {
   339  	op := op{Kind: OpLast}
   340  	if p.iter != nil {
   341  		op.Span = p.iter.Last()
   342  	}
   343  	return p.handleOp(op)
   344  }
   345  
   346  func (p *probeIterator) Next() *Span {
   347  	op := op{Kind: OpNext}
   348  	if p.iter != nil {
   349  		op.Span = p.iter.Next()
   350  	}
   351  	return p.handleOp(op)
   352  }
   353  
   354  func (p *probeIterator) Prev() *Span {
   355  	op := op{Kind: OpPrev}
   356  	if p.iter != nil {
   357  		op.Span = p.iter.Prev()
   358  	}
   359  	return p.handleOp(op)
   360  }
   361  
   362  func (p *probeIterator) Error() error {
   363  	return p.err
   364  }
   365  
   366  func (p *probeIterator) Close() error {
   367  	op := op{Kind: OpClose}
   368  	if p.iter != nil {
   369  		op.Err = p.iter.Close()
   370  	}
   371  
   372  	p.probeCtx.op = op
   373  	p.probe.probe(&p.probeCtx)
   374  	p.err = p.probeCtx.op.Err
   375  	return p.err
   376  }
   377  
   378  // runIterCmd evaluates a datadriven command controlling an internal
   379  // keyspan.FragmentIterator, writing the results of the iterator operations to
   380  // the provided writer.
   381  func runIterCmd(t *testing.T, td *datadriven.TestData, iter FragmentIterator, w io.Writer) {
   382  	lines := strings.Split(strings.TrimSpace(td.Input), "\n")
   383  	for i, line := range lines {
   384  		if i > 0 {
   385  			fmt.Fprintln(w)
   386  		}
   387  		line = strings.TrimSpace(line)
   388  		i := strings.IndexByte(line, '#')
   389  		iterCmd := line
   390  		if i > 0 {
   391  			iterCmd = string(line[:i])
   392  		}
   393  		runIterOp(w, iter, iterCmd)
   394  	}
   395  }
   396  
   397  var iterDelim = map[rune]bool{',': true, ' ': true, '(': true, ')': true, '"': true}
   398  
   399  func runIterOp(w io.Writer, it FragmentIterator, op string) {
   400  	fields := strings.FieldsFunc(op, func(r rune) bool { return iterDelim[r] })
   401  	var s *Span
   402  	switch strings.ToLower(fields[0]) {
   403  	case "first":
   404  		s = it.First()
   405  	case "last":
   406  		s = it.Last()
   407  	case "seekge", "seek-ge":
   408  		if len(fields) == 1 {
   409  			panic(fmt.Sprintf("unable to parse iter op %q", op))
   410  		}
   411  		s = it.SeekGE([]byte(fields[1]))
   412  	case "seeklt", "seek-lt":
   413  		if len(fields) == 1 {
   414  			panic(fmt.Sprintf("unable to parse iter op %q", op))
   415  		}
   416  		s = it.SeekLT([]byte(fields[1]))
   417  	case "next":
   418  		s = it.Next()
   419  	case "prev":
   420  		s = it.Prev()
   421  	default:
   422  		panic(fmt.Sprintf("unrecognized iter op %q", fields[0]))
   423  	}
   424  	if s == nil {
   425  		fmt.Fprint(w, "<nil>")
   426  		if err := it.Error(); err != nil {
   427  			fmt.Fprintf(w, " err=<%s>", it.Error())
   428  		}
   429  		return
   430  	}
   431  	fmt.Fprint(w, s)
   432  }