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