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 }