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