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 }