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