github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/internal/itertest/datadriven.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 itertest provides facilities for testing internal iterators.
     6  package itertest
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/cockroachdb/datadriven"
    17  	"github.com/cockroachdb/pebble/internal/base"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  type iterCmdOpts struct {
    22  	fmtKV func(io.Writer, *base.InternalKey, []byte, base.InternalIterator)
    23  	stats *base.InternalIteratorStats
    24  }
    25  
    26  // An IterOpt configures the behavior of RunInternalIterCmd.
    27  type IterOpt func(*iterCmdOpts)
    28  
    29  // Verbose configures RunInternalIterCmd to output verbose results.
    30  func Verbose(opts *iterCmdOpts) { opts.fmtKV = verboseFmt }
    31  
    32  // Condensed configures RunInternalIterCmd to output condensed results without
    33  // values.
    34  func Condensed(opts *iterCmdOpts) { opts.fmtKV = condensedFmt }
    35  
    36  // WithStats configures RunInternalIterCmd to collect iterator stats in the
    37  // struct pointed to by stats.
    38  func WithStats(stats *base.InternalIteratorStats) IterOpt {
    39  	return func(opts *iterCmdOpts) {
    40  		opts.stats = stats
    41  	}
    42  }
    43  
    44  func defaultFmt(w io.Writer, key *base.InternalKey, v []byte, iter base.InternalIterator) {
    45  	if key != nil {
    46  		fmt.Fprintf(w, "%s:%s\n", key.UserKey, v)
    47  	} else if err := iter.Error(); err != nil {
    48  		fmt.Fprintf(w, "err=%v\n", err)
    49  	} else {
    50  		fmt.Fprintf(w, ".\n")
    51  	}
    52  }
    53  
    54  func condensedFmt(w io.Writer, key *base.InternalKey, v []byte, iter base.InternalIterator) {
    55  	if key != nil {
    56  		fmt.Fprintf(w, "<%s:%d>", key.UserKey, key.SeqNum())
    57  	} else if err := iter.Error(); err != nil {
    58  		fmt.Fprintf(w, "err=%v", err)
    59  	} else {
    60  		fmt.Fprint(w, ".")
    61  	}
    62  }
    63  
    64  func verboseFmt(w io.Writer, key *base.InternalKey, v []byte, iter base.InternalIterator) {
    65  	if key != nil {
    66  		fmt.Fprintf(w, "%s:%s\n", key, v)
    67  		return
    68  	}
    69  	defaultFmt(w, key, v, iter)
    70  }
    71  
    72  // RunInternalIterCmd evaluates a datadriven command controlling an internal
    73  // iterator, returning a string with the results of the iterator operations.
    74  func RunInternalIterCmd(
    75  	t *testing.T, d *datadriven.TestData, iter base.InternalIterator, opts ...IterOpt,
    76  ) string {
    77  	var buf bytes.Buffer
    78  	RunInternalIterCmdWriter(t, &buf, d, iter, opts...)
    79  	return buf.String()
    80  }
    81  
    82  // RunInternalIterCmdWriter evaluates a datadriven command controlling an
    83  // internal iterator, writing the results of the iterator operations to the
    84  // provided Writer.
    85  func RunInternalIterCmdWriter(
    86  	t *testing.T, w io.Writer, d *datadriven.TestData, iter base.InternalIterator, opts ...IterOpt,
    87  ) {
    88  	o := iterCmdOpts{fmtKV: defaultFmt}
    89  	for _, opt := range opts {
    90  		opt(&o)
    91  	}
    92  
    93  	getKV := func(key *base.InternalKey, val base.LazyValue) (*base.InternalKey, []byte) {
    94  		v, _, err := val.Value(nil)
    95  		require.NoError(t, err)
    96  		return key, v
    97  	}
    98  	var prefix []byte
    99  	for _, line := range strings.Split(d.Input, "\n") {
   100  		parts := strings.Fields(line)
   101  		if len(parts) == 0 {
   102  			continue
   103  		}
   104  		var key *base.InternalKey
   105  		var value []byte
   106  		switch parts[0] {
   107  		case "seek-ge":
   108  			if len(parts) < 2 || len(parts) > 3 {
   109  				fmt.Fprint(w, "seek-ge <key> [<try-seek-using-next>]\n")
   110  				return
   111  			}
   112  			prefix = nil
   113  			var flags base.SeekGEFlags
   114  			if len(parts) == 3 {
   115  				if trySeekUsingNext, err := strconv.ParseBool(parts[2]); err != nil {
   116  					fmt.Fprintf(w, "%s", err.Error())
   117  					return
   118  				} else if trySeekUsingNext {
   119  					flags = flags.EnableTrySeekUsingNext()
   120  				}
   121  			}
   122  			key, value = getKV(iter.SeekGE([]byte(strings.TrimSpace(parts[1])), flags))
   123  		case "seek-prefix-ge":
   124  			if len(parts) != 2 && len(parts) != 3 {
   125  				fmt.Fprint(w, "seek-prefix-ge <key> [<try-seek-using-next>]\n")
   126  				return
   127  			}
   128  			prefix = []byte(strings.TrimSpace(parts[1]))
   129  			var flags base.SeekGEFlags
   130  			if len(parts) == 3 {
   131  				if trySeekUsingNext, err := strconv.ParseBool(parts[2]); err != nil {
   132  					fmt.Fprintf(w, "%s", err.Error())
   133  					return
   134  				} else if trySeekUsingNext {
   135  					flags = flags.EnableTrySeekUsingNext()
   136  				}
   137  			}
   138  			key, value = getKV(iter.SeekPrefixGE(prefix, prefix /* key */, flags))
   139  		case "seek-lt":
   140  			if len(parts) != 2 {
   141  				fmt.Fprint(w, "seek-lt <key>\n")
   142  				return
   143  			}
   144  			prefix = nil
   145  			key, value = getKV(iter.SeekLT([]byte(strings.TrimSpace(parts[1])), base.SeekLTFlagsNone))
   146  		case "first":
   147  			prefix = nil
   148  			key, value = getKV(iter.First())
   149  		case "last":
   150  			prefix = nil
   151  			key, value = getKV(iter.Last())
   152  		case "next":
   153  			key, value = getKV(iter.Next())
   154  		case "prev":
   155  			key, value = getKV(iter.Prev())
   156  		case "set-bounds":
   157  			if len(parts) <= 1 || len(parts) > 3 {
   158  				fmt.Fprint(w, "set-bounds lower=<lower> upper=<upper>\n")
   159  				return
   160  			}
   161  			var lower []byte
   162  			var upper []byte
   163  			for _, part := range parts[1:] {
   164  				arg := strings.Split(strings.TrimSpace(part), "=")
   165  				switch arg[0] {
   166  				case "lower":
   167  					lower = []byte(arg[1])
   168  				case "upper":
   169  					upper = []byte(arg[1])
   170  				default:
   171  					fmt.Fprintf(w, "set-bounds: unknown arg: %s", arg)
   172  					return
   173  				}
   174  			}
   175  			iter.SetBounds(lower, upper)
   176  			continue
   177  		case "stats":
   178  			if o.stats != nil {
   179  				// The timing is non-deterministic, so set to 0.
   180  				o.stats.BlockReadDuration = 0
   181  				fmt.Fprintf(w, "%+v\n", *o.stats)
   182  			}
   183  			continue
   184  		case "reset-stats":
   185  			if o.stats != nil {
   186  				*o.stats = base.InternalIteratorStats{}
   187  			}
   188  			continue
   189  		default:
   190  			fmt.Fprintf(w, "unknown op: %s", parts[0])
   191  			return
   192  		}
   193  		o.fmtKV(w, key, value, iter)
   194  
   195  	}
   196  }