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  }