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 }