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  }