Line data Source code
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
6 :
7 : import (
8 : "fmt"
9 : "go/token"
10 : "strings"
11 :
12 : "github.com/cockroachdb/pebble/internal/base"
13 : "github.com/cockroachdb/pebble/internal/dsl"
14 : "github.com/cockroachdb/pebble/vfs/errorfs"
15 : )
16 :
17 : // Predicate encodes conditional logic that yields a boolean.
18 : type Predicate = dsl.Predicate[*ProbeContext]
19 :
20 : // NewParser constructs a Probe parser.
21 1 : func NewParser() *dsl.Parser[Probe] {
22 1 : predicateParser := dsl.NewPredicateParser[*ProbeContext]()
23 1 : for i, name := range opNames {
24 1 : opKind := OpKind(i)
25 1 : predicateParser.DefineConstant(name, func() dsl.Predicate[*ProbeContext] {
26 1 : // An OpKind implements dsl.Predicate[*ProbeContext].
27 1 : return opKind
28 1 : })
29 : }
30 1 : predicateParser.DefineFunc("UserKey",
31 1 : func(p *dsl.Parser[Predicate], s *dsl.Scanner) dsl.Predicate[*ProbeContext] {
32 0 : userKey := s.ConsumeString()
33 0 : s.Consume(token.RPAREN)
34 0 : return UserKey(userKey)
35 0 : })
36 :
37 1 : probeParser := dsl.NewParser[Probe]()
38 1 : probeParser.DefineConstant("ErrInjected", func() Probe { return ErrInjected })
39 1 : probeParser.DefineConstant("noop", Noop)
40 1 : probeParser.DefineConstant("Nil", Nil)
41 1 : probeParser.DefineFunc("If",
42 1 : func(p *dsl.Parser[Probe], s *dsl.Scanner) Probe {
43 1 : pred := If(
44 1 : predicateParser.ParseFromPos(s, s.Scan()),
45 1 : probeParser.ParseFromPos(s, s.Scan()),
46 1 : probeParser.ParseFromPos(s, s.Scan()),
47 1 : )
48 1 : s.Consume(token.RPAREN)
49 1 : return pred
50 1 : })
51 1 : probeParser.DefineFunc("ReturnKV",
52 1 : func(p *dsl.Parser[Probe], s *dsl.Scanner) Probe {
53 1 : kv := base.MakeInternalKV(base.ParseInternalKey(s.ConsumeString()), []byte(s.ConsumeString()))
54 1 : s.Consume(token.RPAREN)
55 1 : return ReturnKV(&kv)
56 1 : })
57 1 : probeParser.DefineFunc("Log",
58 1 : func(p *dsl.Parser[Probe], s *dsl.Scanner) (ret Probe) {
59 1 : ret = loggingProbe{prefix: s.ConsumeString()}
60 1 : s.Consume(token.RPAREN)
61 1 : return ret
62 1 : })
63 1 : return probeParser
64 : }
65 :
66 : // ErrInjected is an error artificially injected for testing.
67 : var ErrInjected = Error("ErrInjected", errorfs.ErrInjected)
68 :
69 : // Error returns a Probe that returns the provided error. The name is Name
70 : // returned by String().
71 1 : func Error(name string, err error) *ErrorProbe {
72 1 : return &ErrorProbe{name: name, err: err}
73 1 : }
74 :
75 : // ErrorProbe is a Probe that injects an error.
76 : type ErrorProbe struct {
77 : name string
78 : err error
79 : }
80 :
81 : // String implements fmt.Stringer.
82 0 : func (p *ErrorProbe) String() string {
83 0 : return p.name
84 0 : }
85 :
86 : // Error implements error, so that injected error values may be used as probes
87 : // that inject themselves.
88 0 : func (p *ErrorProbe) Error() error {
89 0 : return p.err
90 0 : }
91 :
92 : // Probe implements the Probe interface, replacing the iterator return value
93 : // with an error.
94 1 : func (p *ErrorProbe) Probe(pctx *ProbeContext) {
95 1 : pctx.Op.Return.Err = p.err
96 1 : pctx.Op.Return.KV = nil
97 1 : }
98 :
99 : // If a conditional Probe. If its predicate evaluates to true, it probes using
100 : // its Then probe. If its predicate evalutes to false, it probes using its Else
101 : // probe.
102 1 : func If(pred Predicate, thenProbe, elseProbe Probe) Probe {
103 1 : return ifProbe{pred, thenProbe, elseProbe}
104 1 : }
105 :
106 : type ifProbe struct {
107 : Predicate Predicate
108 : Then Probe
109 : Else Probe
110 : }
111 :
112 : // String implements fmt.Stringer.
113 0 : func (p ifProbe) String() string { return fmt.Sprintf("(If %s %s %s)", p.Predicate, p.Then, p.Else) }
114 :
115 : // Probe implements Probe.
116 1 : func (p ifProbe) Probe(pctx *ProbeContext) {
117 1 : if p.Predicate.Evaluate(pctx) {
118 1 : p.Then.Probe(pctx)
119 1 : } else {
120 1 : p.Else.Probe(pctx)
121 1 : }
122 : }
123 :
124 : type loggingProbe struct {
125 : prefix string
126 : }
127 :
128 0 : func (lp loggingProbe) String() string { return fmt.Sprintf("(Log %q)", lp.prefix) }
129 1 : func (lp loggingProbe) Probe(pctx *ProbeContext) {
130 1 : opStr := strings.TrimPrefix(pctx.Kind.String(), "Op")
131 1 : fmt.Fprintf(pctx.Log, "%s%s(", lp.prefix, opStr)
132 1 : if pctx.SeekKey != nil {
133 1 : fmt.Fprintf(pctx.Log, "%q", pctx.SeekKey)
134 1 : }
135 1 : fmt.Fprint(pctx.Log, ") = ")
136 1 : if pctx.Return.KV == nil {
137 1 : fmt.Fprint(pctx.Log, "nil")
138 1 : if pctx.Return.Err != nil {
139 1 : fmt.Fprintf(pctx.Log, " <err=%q>", pctx.Return.Err)
140 1 : }
141 1 : } else {
142 1 : v, _, err := pctx.Return.KV.Value(nil)
143 1 : if err != nil {
144 0 : panic(err)
145 : }
146 1 : fmt.Fprintf(pctx.Log, "(%s,%q)", pctx.Return.KV.K, v)
147 : }
148 1 : fmt.Fprintln(pctx.Log)
149 : }
150 :
151 : // UserKey implements a predicate that evaluates to true if the returned
152 : // InternalKey holds a specific user key.
153 : type UserKey []byte
154 :
155 : // String implements fmt.Stringer.
156 0 : func (p UserKey) String() string { return fmt.Sprintf("(UserKey %q)", string(p)) }
157 :
158 : // Evaluate implements Predicate.
159 0 : func (p UserKey) Evaluate(pctx *ProbeContext) bool {
160 0 : return pctx.Op.Return.KV != nil && pctx.Comparer.Equal(pctx.Op.Return.KV.K.UserKey, p)
161 0 : }
162 :
163 : // ReturnKV returns a Probe that modifies an operation's return value to the
164 : // provided KV pair.
165 1 : func ReturnKV(kv *base.InternalKV) Probe {
166 1 : return &returnKV{kv}
167 1 : }
168 :
169 : type returnKV struct {
170 : *base.InternalKV
171 : }
172 :
173 : // Probe implements Probe.
174 1 : func (kv *returnKV) Probe(pctx *ProbeContext) {
175 1 : pctx.Op.Return.KV = kv.InternalKV
176 1 : }
177 :
178 : // Noop returns a Probe that does nothing.
179 1 : func Noop() Probe { return noop{} }
180 :
181 : type noop struct{}
182 :
183 0 : func (noop) String() string { return "noop" }
184 1 : func (noop) Probe(pctx *ProbeContext) {}
185 :
186 : // Nil returns a Probe that always returns nil.
187 1 : func Nil() Probe { return returnNil{} }
188 :
189 : type returnNil struct{}
190 :
191 0 : func (returnNil) String() string { return "Nil" }
192 1 : func (returnNil) Probe(pctx *ProbeContext) {
193 1 : pctx.Op.Return.KV = nil
194 1 : }
|