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 metaflags defines command-line flags for the metamorphic tests and
6 : // provides functionality to construct the respective
7 : // metamorphic.RunOptions/RunOnceOptions.
8 : package metaflags
9 :
10 : import (
11 : "flag"
12 : "fmt"
13 : "math"
14 : "regexp"
15 :
16 : "github.com/cockroachdb/pebble/internal/randvar"
17 : "github.com/cockroachdb/pebble/metamorphic"
18 : )
19 :
20 : // CommonFlags contains flags that apply to both metamorphic.Run and
21 : // metamorphic.RunOnce/Compare.
22 : type CommonFlags struct {
23 : // Dir is the directory storing test state. See "dir" flag below.
24 : Dir string
25 : // Seed for generation of random operations. See "seed" flag below.
26 : Seed uint64
27 : // ErrorRate is the rate of injected filesystem errors. See "error-rate" flag
28 : // below.
29 : ErrorRate float64
30 : // FailRE causes the test to fail if the output matches this regex. See "fail"
31 : // flag below.
32 : FailRE string
33 : // Keep determines if the DB directory is kept on successful runs. See "keep"
34 : // flag below.
35 : Keep bool
36 : // MaxThreads used by a single run. See "max-threads" flag below.
37 : MaxThreads int
38 : // NumInstances is the number of Pebble instances to create in one run. See
39 : // "num-instances" flag below.
40 : NumInstances int
41 : }
42 :
43 1 : func initCommonFlags() *CommonFlags {
44 1 : c := &CommonFlags{}
45 1 : flag.StringVar(&c.Dir, "dir", "_meta",
46 1 : "the directory storing test state")
47 1 :
48 1 : flag.Uint64Var(&c.Seed, "seed", 0,
49 1 : "a pseudorandom number generator seed")
50 1 :
51 1 : // TODO: default error rate to a non-zero value. Currently, retrying is
52 1 : // non-deterministic because of the Ierator.*WithLimit() methods since
53 1 : // they may say that the Iterator is not valid, but be positioned at a
54 1 : // certain key that can be returned in the future if the limit is changed.
55 1 : // Since that key is hidden from clients of Iterator, the retryableIter
56 1 : // using SeekGE will not necessarily position the Iterator that saw an
57 1 : // injected error at the same place as an Iterator that did not see that
58 1 : // error.
59 1 : flag.Float64Var(&c.ErrorRate, "error-rate", 0.0,
60 1 : "rate of errors injected into filesystem operations (0 ≤ r < 1)")
61 1 :
62 1 : flag.StringVar(&c.FailRE, "fail", "",
63 1 : "fail the test if the supplied regular expression matches the output")
64 1 :
65 1 : flag.BoolVar(&c.Keep, "keep", false,
66 1 : "keep the DB directory even on successful runs")
67 1 :
68 1 : flag.IntVar(&c.MaxThreads, "max-threads", math.MaxInt,
69 1 : "limit execution of a single run to the provided number of threads; must be ≥ 1")
70 1 :
71 1 : flag.IntVar(&c.NumInstances, "num-instances", 1, "number of pebble instances to create (default: 1)")
72 1 :
73 1 : return c
74 1 : }
75 :
76 : // RunOnceFlags contains flags that apply only to metamorphic.RunOnce/Compare.
77 : type RunOnceFlags struct {
78 : *CommonFlags
79 : // RunDir applies to metamorphic.RunOnce and contains the specific
80 : // configuration of the run. See "run-dir" flag below.
81 : RunDir string
82 : // Compare applies to metamorphic.Compare. See "compare" flag below.
83 : Compare string
84 : }
85 :
86 1 : func initRunOnceFlags(c *CommonFlags) *RunOnceFlags {
87 1 : ro := &RunOnceFlags{CommonFlags: c}
88 1 : flag.StringVar(&ro.RunDir, "run-dir", "",
89 1 : "the specific configuration to (re-)run (used for post-mortem debugging)")
90 1 :
91 1 : flag.StringVar(&ro.Compare, "compare", "",
92 1 : `comma separated list of options files to compare. The result of each run is compared with
93 1 : the result of the run from the first options file in the list. Example, -compare
94 1 : random-003,standard-000. The dir flag should have the directory containing these directories.
95 1 : Example, -dir _meta/200610-203012.077`)
96 1 : return ro
97 1 : }
98 :
99 : // RunFlags contains flags that apply only to metamorphic.Run.
100 : type RunFlags struct {
101 : *CommonFlags
102 : // FS controls the type of filesystems to use. See "fs" flag below.
103 : FS string
104 : // TraceFile for execution tracing. See "trace-file" flag below.
105 : TraceFile string
106 : // Ops describes how the total number of operations is generated. See "ops" flags below.
107 : Ops randvar.Flag
108 : // InnerBinary is the binary to invoke for a single run. See "inner-binary"
109 : // flag below.
110 : InnerBinary string
111 : // PreviousOps is the path to the ops file of a previous run. See the
112 : // "previous-ops" flag below.
113 : PreviousOps string
114 : // InitialStatePath is the path to a database data directory from a previous
115 : // run. See the "initial-state" flag below.
116 : InitialStatePath string
117 : // InitialStateDesc is a human-readable description of the initial database
118 : // state. See "initial-state-desc" flag below.
119 : InitialStateDesc string
120 : }
121 :
122 0 : func initRunFlags(c *CommonFlags) *RunFlags {
123 0 : r := &RunFlags{CommonFlags: c}
124 0 : flag.StringVar(&r.FS, "fs", "rand",
125 0 : `force the tests to use either memory or disk-backed filesystems (valid: "mem", "disk", "rand")`)
126 0 :
127 0 : flag.StringVar(&r.TraceFile, "trace-file", "",
128 0 : "write an execution trace to `<run-dir>/file`")
129 0 :
130 0 : if err := r.Ops.Set("uniform:5000-10000"); err != nil {
131 0 : panic(err)
132 : }
133 0 : flag.Var(&r.Ops, "ops", "uniform:5000-10000")
134 0 :
135 0 : flag.StringVar(&r.InnerBinary, "inner-binary", "",
136 0 : `binary to run for each instance of the test (this same binary by default); cannot be used
137 0 : with --run-dir or --compare`)
138 0 :
139 0 : // The following options may be used for split-version metamorphic testing.
140 0 : // To perform split-version testing, the client runs the metamorphic tests
141 0 : // on an earlier Pebble SHA passing the `--keep` flag. The client then
142 0 : // switches to the later Pebble SHA, setting the below options to point to
143 0 : // the `ops` file and one of the previous run's data directories.
144 0 :
145 0 : flag.StringVar(&r.PreviousOps, "previous-ops", "",
146 0 : "path to an ops file, used to prepopulate the set of keys operations draw from")
147 0 :
148 0 : flag.StringVar(&r.InitialStatePath, "initial-state", "",
149 0 : "path to a database's data directory, used to prepopulate the test run's databases")
150 0 :
151 0 : flag.StringVar(&r.InitialStateDesc, "initial-state-desc", "",
152 0 : `a human-readable description of the initial database state.
153 0 : If set this parameter is written to the OPTIONS to aid in
154 0 : debugging. It's intended to describe the lineage of a
155 0 : database's state, including sufficient information for
156 0 : reproduction (eg, SHA, prng seed, etc).`)
157 0 : return r
158 : }
159 :
160 : // InitRunOnceFlags initializes the flags that are used for a single run of the
161 : // metamorphic test.
162 1 : func InitRunOnceFlags() *RunOnceFlags {
163 1 : return initRunOnceFlags(initCommonFlags())
164 1 : }
165 :
166 : // InitAllFlags initializes all metamorphic test flags: those used for a
167 : // single run, and those used for a "top level" run.
168 0 : func InitAllFlags() (*RunOnceFlags, *RunFlags) {
169 0 : c := initCommonFlags()
170 0 : return initRunOnceFlags(c), initRunFlags(c)
171 0 : }
172 :
173 : // MakeRunOnceOptions constructs RunOnceOptions based on the flags.
174 1 : func (ro *RunOnceFlags) MakeRunOnceOptions() []metamorphic.RunOnceOption {
175 1 : onceOpts := []metamorphic.RunOnceOption{
176 1 : metamorphic.MaxThreads(ro.MaxThreads),
177 1 : }
178 1 : if ro.Keep {
179 0 : onceOpts = append(onceOpts, metamorphic.KeepData{})
180 0 : }
181 1 : if ro.FailRE != "" {
182 0 : onceOpts = append(onceOpts, metamorphic.FailOnMatch{Regexp: regexp.MustCompile(ro.FailRE)})
183 0 : }
184 1 : if ro.ErrorRate > 0 {
185 0 : onceOpts = append(onceOpts, metamorphic.InjectErrorsRate(ro.ErrorRate))
186 0 : }
187 1 : if ro.NumInstances > 1 {
188 1 : onceOpts = append(onceOpts, metamorphic.MultiInstance(ro.NumInstances))
189 1 : }
190 1 : return onceOpts
191 : }
192 :
193 : // MakeRunOptions constructs RunOptions based on the flags.
194 0 : func (r *RunFlags) MakeRunOptions() []metamorphic.RunOption {
195 0 : opts := []metamorphic.RunOption{
196 0 : metamorphic.Seed(r.Seed),
197 0 : metamorphic.OpCount(r.Ops.Static),
198 0 : metamorphic.MaxThreads(r.MaxThreads),
199 0 : }
200 0 : if r.Keep {
201 0 : opts = append(opts, metamorphic.KeepData{})
202 0 : }
203 0 : if r.FailRE != "" {
204 0 : opts = append(opts, metamorphic.FailOnMatch{Regexp: regexp.MustCompile(r.FailRE)})
205 0 : }
206 0 : if r.ErrorRate > 0 {
207 0 : opts = append(opts, metamorphic.InjectErrorsRate(r.ErrorRate))
208 0 : }
209 0 : if r.TraceFile != "" {
210 0 : opts = append(opts, metamorphic.RuntimeTrace(r.TraceFile))
211 0 : }
212 0 : if r.PreviousOps != "" {
213 0 : opts = append(opts, metamorphic.ExtendPreviousRun(r.PreviousOps, r.InitialStatePath, r.InitialStateDesc))
214 0 : }
215 0 : if r.NumInstances > 1 {
216 0 : opts = append(opts, metamorphic.MultiInstance(r.NumInstances))
217 0 : }
218 :
219 : // If the filesystem type was forced, all tests will use that value.
220 0 : switch r.FS {
221 0 : case "", "rand", "default":
222 : // No-op. Use the generated value for the filesystem.
223 0 : case "disk":
224 0 : opts = append(opts, metamorphic.UseDisk)
225 0 : case "mem":
226 0 : opts = append(opts, metamorphic.UseInMemory)
227 0 : default:
228 0 : panic(fmt.Sprintf("unknown forced filesystem type: %q", r.FS))
229 : }
230 0 : if r.InnerBinary != "" {
231 0 : opts = append(opts, metamorphic.InnerBinary(r.InnerBinary))
232 0 : }
233 0 : return opts
234 : }
|