LCOV - code coverage report
Current view: top level - pebble/metamorphic - options.go (source / functions) Hit Total Coverage
Test: 2025-01-07 08:17Z 28edac9f - tests + meta.lcov Lines: 667 716 93.2 %
Date: 2025-01-07 08:18:18 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2019 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 metamorphic
       6             : 
       7             : import (
       8             :         "bytes"
       9             :         "fmt"
      10             :         "math"
      11             :         "math/rand/v2"
      12             :         "os"
      13             :         "path/filepath"
      14             :         "runtime"
      15             :         "strconv"
      16             :         "strings"
      17             :         "sync/atomic"
      18             :         "time"
      19             : 
      20             :         "github.com/cockroachdb/errors"
      21             :         "github.com/cockroachdb/pebble"
      22             :         "github.com/cockroachdb/pebble/bloom"
      23             :         "github.com/cockroachdb/pebble/internal/base"
      24             :         "github.com/cockroachdb/pebble/internal/cache"
      25             :         "github.com/cockroachdb/pebble/objstorage/remote"
      26             :         "github.com/cockroachdb/pebble/sstable"
      27             :         "github.com/cockroachdb/pebble/sstable/block"
      28             :         "github.com/cockroachdb/pebble/vfs"
      29             :         "github.com/cockroachdb/pebble/wal"
      30             : )
      31             : 
      32             : const (
      33             :         minimumFormatMajorVersion = pebble.FormatMinSupported
      34             :         // The format major version to use in the default options configurations. We
      35             :         // default to the minimum supported format so we exercise the runtime version
      36             :         // ratcheting that a cluster upgrading would experience. The randomized
      37             :         // options may still use format major versions that are less than
      38             :         // defaultFormatMajorVersion but are at least minimumFormatMajorVersion.
      39             :         defaultFormatMajorVersion = pebble.FormatMinSupported
      40             :         // newestFormatMajorVersionToTest is the most recent format major version
      41             :         // the metamorphic tests should use. This may be greater than
      42             :         // pebble.FormatNewest when some format major versions are marked as
      43             :         // experimental.
      44             :         newestFormatMajorVersionToTest = pebble.FormatNewest
      45             : )
      46             : 
      47             : func parseOptions(
      48             :         opts *TestOptions, data string, customOptionParsers map[string]func(string) (CustomOption, bool),
      49           2 : ) error {
      50           2 :         hooks := &pebble.ParseHooks{
      51           2 :                 NewCache:        pebble.NewCache,
      52           2 :                 NewFilterPolicy: filterPolicyFromName,
      53           2 :                 SkipUnknown: func(name, value string) bool {
      54           2 :                         switch name {
      55           0 :                         case "TestOptions":
      56           0 :                                 return true
      57           2 :                         case "TestOptions.strictfs":
      58           2 :                                 opts.strictFS = true
      59           2 :                                 opts.Opts.FS = vfs.NewCrashableMem()
      60           2 :                                 return true
      61           2 :                         case "TestOptions.key_format":
      62           2 :                                 opts.KeyFormat = keyFormatsByName[value]
      63           2 :                                 return true
      64           2 :                         case "TestOptions.ingest_using_apply":
      65           2 :                                 opts.ingestUsingApply = true
      66           2 :                                 return true
      67           2 :                         case "TestOptions.delete_sized":
      68           2 :                                 opts.deleteSized = true
      69           2 :                                 return true
      70           2 :                         case "TestOptions.replace_single_delete":
      71           2 :                                 opts.replaceSingleDelete = true
      72           2 :                                 return true
      73           2 :                         case "TestOptions.use_disk":
      74           2 :                                 opts.useDisk = true
      75           2 :                                 opts.Opts.FS = vfs.Default
      76           2 :                                 return true
      77           0 :                         case "TestOptions.initial_state_desc":
      78           0 :                                 opts.initialStateDesc = value
      79           0 :                                 return true
      80           0 :                         case "TestOptions.initial_state_path":
      81           0 :                                 opts.initialStatePath = value
      82           0 :                                 return true
      83           2 :                         case "TestOptions.threads":
      84           2 :                                 v, err := strconv.Atoi(value)
      85           2 :                                 if err != nil {
      86           0 :                                         panic(err)
      87             :                                 }
      88           2 :                                 opts.Threads = v
      89           2 :                                 return true
      90           2 :                         case "TestOptions.disable_block_property_collector":
      91           2 :                                 v, err := strconv.ParseBool(value)
      92           2 :                                 if err != nil {
      93           0 :                                         panic(err)
      94             :                                 }
      95           2 :                                 opts.disableBlockPropertyCollector = v
      96           2 :                                 if v {
      97           2 :                                         opts.Opts.BlockPropertyCollectors = nil
      98           2 :                                 }
      99           2 :                                 return true
     100           2 :                         case "TestOptions.enable_value_blocks":
     101           2 :                                 opts.enableValueBlocks = true
     102           2 :                                 opts.Opts.Experimental.EnableValueBlocks = func() bool { return true }
     103           2 :                                 return true
     104           2 :                         case "TestOptions.disable_value_blocks_for_ingest_sstables":
     105           2 :                                 opts.disableValueBlocksForIngestSSTables = true
     106           2 :                                 return true
     107           2 :                         case "TestOptions.async_apply_to_db":
     108           2 :                                 opts.asyncApplyToDB = true
     109           2 :                                 return true
     110           2 :                         case "TestOptions.shared_storage_enabled":
     111           2 :                                 opts.sharedStorageEnabled = true
     112           2 :                                 opts.sharedStorageFS = remote.NewInMem()
     113           2 :                                 if opts.Opts.Experimental.CreateOnShared == remote.CreateOnSharedNone {
     114           1 :                                         opts.Opts.Experimental.CreateOnShared = remote.CreateOnSharedAll
     115           1 :                                 }
     116           2 :                                 return true
     117           2 :                         case "TestOptions.external_storage_enabled":
     118           2 :                                 opts.externalStorageEnabled = true
     119           2 :                                 opts.externalStorageFS = remote.NewInMem()
     120           2 :                                 return true
     121           2 :                         case "TestOptions.secondary_cache_enabled":
     122           2 :                                 opts.secondaryCacheEnabled = true
     123           2 :                                 opts.Opts.Experimental.SecondaryCacheSizeBytes = 1024 * 1024 * 32 // 32 MBs
     124           2 :                                 return true
     125           2 :                         case "TestOptions.seed_efos":
     126           2 :                                 v, err := strconv.ParseUint(value, 10, 64)
     127           2 :                                 if err != nil {
     128           0 :                                         panic(err)
     129             :                                 }
     130           2 :                                 opts.seedEFOS = v
     131           2 :                                 return true
     132           2 :                         case "TestOptions.io_latency_mean":
     133           2 :                                 v, err := time.ParseDuration(value)
     134           2 :                                 if err != nil {
     135           0 :                                         panic(err)
     136             :                                 }
     137           2 :                                 opts.ioLatencyMean = v
     138           2 :                                 return true
     139           2 :                         case "TestOptions.io_latency_probability":
     140           2 :                                 v, err := strconv.ParseFloat(value, 64)
     141           2 :                                 if err != nil {
     142           0 :                                         panic(err)
     143             :                                 }
     144           2 :                                 opts.ioLatencyProbability = v
     145           2 :                                 return true
     146           2 :                         case "TestOptions.io_latency_seed":
     147           2 :                                 v, err := strconv.ParseInt(value, 10, 64)
     148           2 :                                 if err != nil {
     149           0 :                                         panic(err)
     150             :                                 }
     151           2 :                                 opts.ioLatencySeed = v
     152           2 :                                 return true
     153           2 :                         case "TestOptions.ingest_split":
     154           2 :                                 opts.ingestSplit = true
     155           2 :                                 opts.Opts.Experimental.IngestSplit = func() bool {
     156           2 :                                         return true
     157           2 :                                 }
     158           2 :                                 return true
     159           2 :                         case "TestOptions.use_shared_replicate":
     160           2 :                                 opts.useSharedReplicate = true
     161           2 :                                 return true
     162           0 :                         case "TestOptions.use_external_replicate":
     163           0 :                                 opts.useExternalReplicate = true
     164           0 :                                 return true
     165           2 :                         case "TestOptions.use_excise":
     166           2 :                                 opts.useExcise = true
     167           2 :                                 return true
     168           2 :                         case "TestOptions.use_delete_only_compaction_excises":
     169           2 :                                 opts.useDeleteOnlyCompactionExcises = true
     170           2 :                                 opts.Opts.Experimental.EnableDeleteOnlyCompactionExcises = func() bool {
     171           1 :                                         return opts.useDeleteOnlyCompactionExcises
     172           1 :                                 }
     173           2 :                                 return true
     174           2 :                         case "TestOptions.disable_downloads":
     175           2 :                                 opts.disableDownloads = true
     176           2 :                                 return true
     177           2 :                         case "TestOptions.use_jemalloc_size_classes":
     178           2 :                                 opts.Opts.AllocatorSizeClasses = pebble.JemallocSizeClasses
     179           2 :                                 return true
     180           1 :                         default:
     181           1 :                                 if customOptionParsers == nil {
     182           0 :                                         return false
     183           0 :                                 }
     184           1 :                                 name = strings.TrimPrefix(name, "TestOptions.")
     185           1 :                                 if p, ok := customOptionParsers[name]; ok {
     186           1 :                                         if customOpt, ok := p(value); ok {
     187           1 :                                                 opts.CustomOpts = append(opts.CustomOpts, customOpt)
     188           1 :                                                 return true
     189           1 :                                         }
     190             :                                 }
     191           0 :                                 return false
     192             :                         }
     193             :                 },
     194             :         }
     195           2 :         err := opts.Opts.Parse(data, hooks)
     196           2 :         // Ensure that the WAL failover FS agrees with the primary FS. They're
     197           2 :         // separate options, but in the metamorphic tests we keep them in sync.
     198           2 :         if opts.Opts.WALFailover != nil {
     199           2 :                 opts.Opts.WALFailover.Secondary.FS = opts.Opts.FS
     200           2 :         }
     201           2 :         opts.InitRemoteStorageFactory()
     202           2 :         opts.Opts.EnsureDefaults()
     203           2 :         return err
     204             : }
     205             : 
     206           1 : func optionsToString(opts *TestOptions) string {
     207           1 :         var buf bytes.Buffer
     208           1 :         fmt.Fprintf(&buf, "  key_format=%s\n", opts.KeyFormat.Name)
     209           1 :         if opts.strictFS {
     210           1 :                 fmt.Fprint(&buf, "  strictfs=true\n")
     211           1 :         }
     212           1 :         if opts.ingestUsingApply {
     213           1 :                 fmt.Fprint(&buf, "  ingest_using_apply=true\n")
     214           1 :         }
     215           1 :         if opts.deleteSized {
     216           1 :                 fmt.Fprint(&buf, "  delete_sized=true\n")
     217           1 :         }
     218           1 :         if opts.replaceSingleDelete {
     219           1 :                 fmt.Fprint(&buf, "  replace_single_delete=true\n")
     220           1 :         }
     221           1 :         if opts.useDisk {
     222           1 :                 fmt.Fprint(&buf, "  use_disk=true\n")
     223           1 :         }
     224           1 :         if opts.initialStatePath != "" {
     225           0 :                 fmt.Fprintf(&buf, "  initial_state_path=%s\n", opts.initialStatePath)
     226           0 :         }
     227           1 :         if opts.initialStateDesc != "" {
     228           0 :                 fmt.Fprintf(&buf, "  initial_state_desc=%s\n", opts.initialStateDesc)
     229           0 :         }
     230           1 :         if opts.Threads != 0 {
     231           1 :                 fmt.Fprintf(&buf, "  threads=%d\n", opts.Threads)
     232           1 :         }
     233           1 :         if opts.disableBlockPropertyCollector {
     234           1 :                 fmt.Fprintf(&buf, "  disable_block_property_collector=%t\n", opts.disableBlockPropertyCollector)
     235           1 :         }
     236           1 :         if opts.enableValueBlocks {
     237           1 :                 fmt.Fprintf(&buf, "  enable_value_blocks=%t\n", opts.enableValueBlocks)
     238           1 :         }
     239           1 :         if opts.disableValueBlocksForIngestSSTables {
     240           1 :                 fmt.Fprintf(&buf, "  disable_value_blocks_for_ingest_sstables=%t\n", opts.disableValueBlocksForIngestSSTables)
     241           1 :         }
     242           1 :         if opts.asyncApplyToDB {
     243           1 :                 fmt.Fprint(&buf, "  async_apply_to_db=true\n")
     244           1 :         }
     245           1 :         if opts.sharedStorageEnabled {
     246           1 :                 fmt.Fprint(&buf, "  shared_storage_enabled=true\n")
     247           1 :         }
     248           1 :         if opts.externalStorageEnabled {
     249           1 :                 fmt.Fprint(&buf, "  external_storage_enabled=true\n")
     250           1 :         }
     251           1 :         if opts.secondaryCacheEnabled {
     252           1 :                 fmt.Fprint(&buf, "  secondary_cache_enabled=true\n")
     253           1 :         }
     254           1 :         if opts.seedEFOS != 0 {
     255           1 :                 fmt.Fprintf(&buf, "  seed_efos=%d\n", opts.seedEFOS)
     256           1 :         }
     257           1 :         if opts.ingestSplit {
     258           1 :                 fmt.Fprintf(&buf, "  ingest_split=%v\n", opts.ingestSplit)
     259           1 :         }
     260           1 :         if opts.ioLatencyProbability > 0 {
     261           1 :                 fmt.Fprintf(&buf, "  io_latency_mean=%s\n", opts.ioLatencyMean)
     262           1 :                 fmt.Fprintf(&buf, "  io_latency_probability=%.10f\n", opts.ioLatencyProbability)
     263           1 :                 fmt.Fprintf(&buf, "  io_latency_seed=%d\n", opts.ioLatencySeed)
     264           1 :         }
     265           1 :         if opts.useSharedReplicate {
     266           1 :                 fmt.Fprintf(&buf, "  use_shared_replicate=%v\n", opts.useSharedReplicate)
     267           1 :         }
     268           1 :         if opts.useExternalReplicate {
     269           0 :                 fmt.Fprintf(&buf, "  use_external_replicate=%v\n", opts.useExternalReplicate)
     270           0 :         }
     271           1 :         if opts.useExcise {
     272           1 :                 fmt.Fprintf(&buf, "  use_excise=%v\n", opts.useExcise)
     273           1 :         }
     274           1 :         if opts.useDeleteOnlyCompactionExcises {
     275           1 :                 fmt.Fprintf(&buf, "  use_delete_only_compaction_excises=%v\n", opts.useDeleteOnlyCompactionExcises)
     276           1 :         }
     277           1 :         if opts.disableDownloads {
     278           1 :                 fmt.Fprintf(&buf, "  disable_downloads=%v\n", opts.disableDownloads)
     279           1 :         }
     280           1 :         if opts.Opts.AllocatorSizeClasses != nil {
     281           1 :                 if fmt.Sprint(opts.Opts.AllocatorSizeClasses) != fmt.Sprint(pebble.JemallocSizeClasses) {
     282           0 :                         panic(fmt.Sprintf("unexpected AllocatorSizeClasses %v", opts.Opts.AllocatorSizeClasses))
     283             :                 }
     284           1 :                 fmt.Fprint(&buf, "  use_jemalloc_size_classes=true\n")
     285             :         }
     286           1 :         for _, customOpt := range opts.CustomOpts {
     287           1 :                 fmt.Fprintf(&buf, "  %s=%s\n", customOpt.Name(), customOpt.Value())
     288           1 :         }
     289             : 
     290           1 :         s := opts.Opts.String()
     291           1 :         if buf.Len() == 0 {
     292           0 :                 return s
     293           0 :         }
     294           1 :         return s + "\n[TestOptions]\n" + buf.String()
     295             : }
     296             : 
     297           2 : func defaultTestOptions() *TestOptions {
     298           2 :         kf := TestkeysKeyFormat
     299           2 :         return &TestOptions{
     300           2 :                 Opts:        defaultOptions(kf),
     301           2 :                 Threads:     16,
     302           2 :                 RetryPolicy: NeverRetry,
     303           2 :                 KeyFormat:   kf,
     304           2 :         }
     305           2 : }
     306             : 
     307           2 : func defaultOptions(kf KeyFormat) *pebble.Options {
     308           2 :         opts := &pebble.Options{
     309           2 :                 // Use an archive cleaner to ease post-mortem debugging.
     310           2 :                 Cleaner: base.ArchiveCleaner{},
     311           2 :                 // Always use our custom comparer which provides a Split method,
     312           2 :                 // splitting keys at the trailing '@'.
     313           2 :                 Comparer:           kf.Comparer,
     314           2 :                 KeySchema:          kf.KeySchema.Name,
     315           2 :                 KeySchemas:         sstable.MakeKeySchemas(kf.KeySchema),
     316           2 :                 FS:                 vfs.NewMem(),
     317           2 :                 FormatMajorVersion: defaultFormatMajorVersion,
     318           2 :                 Levels: []pebble.LevelOptions{{
     319           2 :                         FilterPolicy: bloom.FilterPolicy(10),
     320           2 :                 }},
     321           2 :                 BlockPropertyCollectors: blockPropertyCollectorConstructors,
     322           2 :         }
     323           2 :         opts.Experimental.EnableColumnarBlocks = func() bool { return true }
     324             : 
     325             :         // We don't want to run the level checker every time because it can slow down
     326             :         // downloads and background compactions too much.
     327             :         //
     328             :         // We aim to run it once every 500ms (on average). To do this with some
     329             :         // randomization, each time we get a callback we see how much time passed
     330             :         // since the last call and run the check with a proportional probability.
     331           2 :         const meanTimeBetweenChecks = 500 * time.Millisecond
     332           2 :         startTime := time.Now()
     333           2 :         // lastCallTime stores the time of the last DebugCheck call, as the duration
     334           2 :         // since startTime.
     335           2 :         var lastCallTime atomic.Uint64
     336           2 :         opts.DebugCheck = func(db *pebble.DB) error {
     337           2 :                 now := time.Since(startTime)
     338           2 :                 last := time.Duration(lastCallTime.Swap(uint64(now)))
     339           2 :                 // Run the check with probability equal to the time (as a fraction of
     340           2 :                 // meanTimeBetweenChecks) passed since the last time we had a chance, as a
     341           2 :                 // fraction of meanTimeBetweenChecks.
     342           2 :                 if rand.Float64() < float64(now-last)/float64(meanTimeBetweenChecks) {
     343           2 :                         return pebble.DebugCheckLevels(db)
     344           2 :                 }
     345           2 :                 return nil
     346             :         }
     347             : 
     348           2 :         return opts
     349             : }
     350             : 
     351             : // TestOptions describes the options configuring an individual run of the
     352             : // metamorphic tests.
     353             : type TestOptions struct {
     354             :         // Opts holds the *pebble.Options for the test.
     355             :         Opts *pebble.Options
     356             :         // Threads configures the parallelism of the test. Each thread will run in
     357             :         // an independent goroutine and be responsible for executing operations
     358             :         // against an independent set of objects. The outcome of any individual
     359             :         // operation will still be deterministic, with the metamorphic test
     360             :         // inserting synchronization where necessary.
     361             :         Threads int
     362             :         // RetryPolicy configures which errors should be retried.
     363             :         RetryPolicy RetryPolicy
     364             :         // KeyFormat defines the format of keys used within the test.
     365             :         KeyFormat KeyFormat
     366             :         // CustomOptions holds custom test options that are defined outside of this
     367             :         // package.
     368             :         CustomOpts []CustomOption
     369             : 
     370             :         // internal
     371             : 
     372             :         useDisk  bool
     373             :         strictFS bool
     374             :         // Use Batch.Apply rather than DB.Ingest.
     375             :         ingestUsingApply bool
     376             :         // Use Batch.DeleteSized rather than Batch.Delete.
     377             :         deleteSized bool
     378             :         // Replace a SINGLEDEL with a DELETE.
     379             :         replaceSingleDelete bool
     380             :         // The path on the local filesystem where the initial state of the database
     381             :         // exists.  Empty if the test run begins from an empty database state.
     382             :         initialStatePath string
     383             :         // A human-readable string describing the initial state of the database.
     384             :         // Empty if the test run begins from an empty database state.
     385             :         initialStateDesc string
     386             :         // Disable the block property collector, which may be used by block property
     387             :         // filters.
     388             :         disableBlockPropertyCollector bool
     389             :         // Enable the use of value blocks.
     390             :         enableValueBlocks bool
     391             :         // Disables value blocks in the sstables written for ingest.
     392             :         disableValueBlocksForIngestSSTables bool
     393             :         // Use DB.ApplyNoSyncWait for applies that want to sync the WAL.
     394             :         asyncApplyToDB bool
     395             :         // Enable the use of shared storage.
     396             :         sharedStorageEnabled bool
     397             :         sharedStorageFS      remote.Storage
     398             :         // Enable the use of shared storage for external file ingestion.
     399             :         externalStorageEnabled bool
     400             :         externalStorageFS      remote.Storage
     401             :         // Enables the use of shared replication in TestOptions.
     402             :         useSharedReplicate bool
     403             :         // Enables the use of external replication in TestOptions.
     404             :         useExternalReplicate bool
     405             :         // Enable the secondary cache. Only effective if sharedStorageEnabled is
     406             :         // also true.
     407             :         secondaryCacheEnabled bool
     408             :         // If nonzero, enables the use of EventuallyFileOnlySnapshots for
     409             :         // newSnapshotOps that are keyspan-bounded. The set of which newSnapshotOps
     410             :         // are actually created as EventuallyFileOnlySnapshots is deterministically
     411             :         // derived from the seed and the operation index.
     412             :         seedEFOS uint64
     413             :         // If nonzero, enables the injection of random IO latency. The mechanics of
     414             :         // a Pebble operation can be very timing dependent, so artificial latency
     415             :         // can ensure a wide variety of mechanics are exercised. Additionally,
     416             :         // exercising some mechanics such as WAL failover require IO latency.
     417             :         ioLatencyProbability float64
     418             :         ioLatencySeed        int64
     419             :         ioLatencyMean        time.Duration
     420             :         // Enables ingest splits. Saved here for serialization as Options does not
     421             :         // serialize this.
     422             :         ingestSplit bool
     423             :         // Enables operations that do excises. Note that a false value for this does
     424             :         // not guarantee the lack of excises, as useSharedReplicate can also cause
     425             :         // excises. However !useExcise && !useSharedReplicate can be used to guarantee
     426             :         // lack of excises.
     427             :         useExcise bool
     428             :         // useDeleteOnlyCompactionExcises turns on the ability for delete-only compactions
     429             :         // to do excises. Note that this can be true even when useExcise is false.
     430             :         useDeleteOnlyCompactionExcises bool
     431             :         // disableDownloads, if true, makes downloadOp a no-op.
     432             :         disableDownloads bool
     433             : }
     434             : 
     435             : // InitRemoteStorageFactory initializes Opts.Experimental.RemoteStorage.
     436           2 : func (testOpts *TestOptions) InitRemoteStorageFactory() {
     437           2 :         if testOpts.sharedStorageEnabled || testOpts.externalStorageEnabled {
     438           2 :                 m := make(map[remote.Locator]remote.Storage)
     439           2 :                 if testOpts.sharedStorageEnabled {
     440           2 :                         m[""] = testOpts.sharedStorageFS
     441           2 :                 }
     442           2 :                 if testOpts.externalStorageEnabled {
     443           2 :                         m["external"] = testOpts.externalStorageFS
     444           2 :                 }
     445           2 :                 testOpts.Opts.Experimental.RemoteStorage = remote.MakeSimpleFactory(m)
     446             :         }
     447             : }
     448             : 
     449             : // CustomOption defines a custom option that configures the behavior of an
     450             : // individual test run. Like all test options, custom options are serialized to
     451             : // the OPTIONS file even if they're not options ordinarily understood by Pebble.
     452             : type CustomOption interface {
     453             :         // Name returns the name of the custom option. This is the key under which
     454             :         // the option appears in the OPTIONS file, within the [TestOptions] stanza.
     455             :         Name() string
     456             :         // Value returns the value of the custom option, serialized as it should
     457             :         // appear within the OPTIONS file.
     458             :         Value() string
     459             :         // Close is run after the test database has been closed at the end of the
     460             :         // test as well as during restart operations within the test sequence. It's
     461             :         // passed a copy of the *pebble.Options. If the custom options hold on to
     462             :         // any resources outside, Close should release them.
     463             :         Close(*pebble.Options) error
     464             :         // Open is run before the test runs and during a restart operation after the
     465             :         // test database has been closed and Close has been called. It's passed a
     466             :         // copy of the *pebble.Options. If the custom options must acquire any
     467             :         // resources before the test continues, it should reacquire them.
     468             :         Open(*pebble.Options) error
     469             : 
     470             :         // TODO(jackson): provide additional hooks for custom options changing the
     471             :         // behavior of a run.
     472             : }
     473             : 
     474           1 : func standardOptions() []*TestOptions {
     475           1 :         // The index labels are not strictly necessary, but they make it easier to
     476           1 :         // find which options correspond to a failure.
     477           1 :         stdOpts := []string{
     478           1 :                 0: "", // default options
     479           1 :                 1: `
     480           1 : [Options]
     481           1 :   cache_size=1
     482           1 : `,
     483           1 :                 2: `
     484           1 : [Options]
     485           1 :   disable_wal=true
     486           1 : [TestOptions]
     487           1 :   use_jemalloc_size_classes=true
     488           1 : `,
     489           1 :                 3: `
     490           1 : [Options]
     491           1 :   l0_compaction_threshold=1
     492           1 : `,
     493           1 :                 4: `
     494           1 : [Options]
     495           1 :   l0_compaction_threshold=1
     496           1 :   l0_stop_writes_threshold=1
     497           1 : `,
     498           1 :                 5: `
     499           1 : [Options]
     500           1 :   lbase_max_bytes=1
     501           1 : `,
     502           1 :                 6: `
     503           1 : [Options]
     504           1 :   max_manifest_file_size=1
     505           1 : `,
     506           1 :                 7: `
     507           1 : [Options]
     508           1 :   max_open_files=1
     509           1 : `,
     510           1 :                 8: `
     511           1 : [Options]
     512           1 :   mem_table_size=2000
     513           1 : `,
     514           1 :                 9: `
     515           1 : [Options]
     516           1 :   mem_table_stop_writes_threshold=2
     517           1 : `,
     518           1 :                 10: `
     519           1 : [Options]
     520           1 :   wal_dir=data/wal
     521           1 : `,
     522           1 :                 11: `
     523           1 : [Level "0"]
     524           1 :   block_restart_interval=1
     525           1 : `,
     526           1 :                 12: `
     527           1 : [Level "0"]
     528           1 :   block_size=1
     529           1 : `,
     530           1 :                 13: `
     531           1 : [Level "0"]
     532           1 :   compression=NoCompression
     533           1 : `,
     534           1 :                 14: `
     535           1 : [Level "0"]
     536           1 :   index_block_size=1
     537           1 : `,
     538           1 :                 15: `
     539           1 : [Options]
     540           1 :   l0_stop_writes_threshold=100
     541           1 : [Level "0"]
     542           1 :   target_file_size=12
     543           1 : `,
     544           1 :                 16: `
     545           1 : [Level "0"]
     546           1 :   filter_policy=none
     547           1 : `,
     548           1 :                 // 1GB
     549           1 :                 17: `
     550           1 : [Options]
     551           1 :   bytes_per_sync=1073741824
     552           1 : [TestOptions]
     553           1 :   strictfs=true
     554           1 : `,
     555           1 :                 18: `
     556           1 : [Options]
     557           1 :   max_concurrent_compactions=2
     558           1 : `,
     559           1 :                 19: `
     560           1 : [TestOptions]
     561           1 :   ingest_using_apply=true
     562           1 : `,
     563           1 :                 20: `
     564           1 : [TestOptions]
     565           1 :   replace_single_delete=true
     566           1 : `,
     567           1 :                 21: `
     568           1 : [TestOptions]
     569           1 :   use_disk=true
     570           1 :   use_jemalloc_size_classes=true
     571           1 : `,
     572           1 :                 22: `
     573           1 : [Options]
     574           1 :   max_writer_concurrency=2
     575           1 :   force_writer_parallelism=true
     576           1 : `,
     577           1 :                 23: `
     578           1 : [TestOptions]
     579           1 :   disable_block_property_collector=true
     580           1 : `,
     581           1 :                 24: `
     582           1 : [TestOptions]
     583           1 :   threads=1
     584           1 : `,
     585           1 :                 25: `
     586           1 : [TestOptions]
     587           1 :   enable_value_blocks=true
     588           1 :   use_jemalloc_size_classes=true
     589           1 : `,
     590           1 :                 26: fmt.Sprintf(`
     591           1 : [Options]
     592           1 :   format_major_version=%s
     593           1 : `, newestFormatMajorVersionToTest),
     594           1 :                 27: fmt.Sprintf(`
     595           1 : [Options]
     596           1 :   format_major_version=%s
     597           1 : [TestOptions]
     598           1 :   shared_storage_enabled=true
     599           1 :   secondary_cache_enabled=true
     600           1 : `, pebble.FormatMinForSharedObjects),
     601           1 :                 28: fmt.Sprintf(`
     602           1 : [Options]
     603           1 :   format_major_version=%s
     604           1 : [TestOptions]
     605           1 :   external_storage_enabled=true
     606           1 : `, pebble.FormatSyntheticPrefixSuffix),
     607           1 :                 29: fmt.Sprintf(`
     608           1 : [Options]
     609           1 :   format_major_version=%s
     610           1 :   max_concurrent_downloads=2
     611           1 : [TestOptions]
     612           1 :   shared_storage_enabled=true
     613           1 :   external_storage_enabled=true
     614           1 :   secondary_cache_enabled=false
     615           1 : `, pebble.FormatSyntheticPrefixSuffix),
     616           1 :         }
     617           1 : 
     618           1 :         opts := make([]*TestOptions, len(stdOpts))
     619           1 :         for i := range opts {
     620           1 :                 opts[i] = defaultTestOptions()
     621           1 :                 // NB: The standard options by definition can never include custom
     622           1 :                 // options, so no need to propagate custom option parsers.
     623           1 :                 if err := parseOptions(opts[i], stdOpts[i], nil /* custom option parsers */); err != nil {
     624           0 :                         panic(err)
     625             :                 }
     626             :         }
     627           1 :         return opts
     628             : }
     629             : 
     630             : // RandomOptions generates a random set of operations, drawing randomness from
     631             : // rng.
     632             : func RandomOptions(
     633             :         rng *rand.Rand, customOptionParsers map[string]func(string) (CustomOption, bool),
     634           1 : ) *TestOptions {
     635           1 :         testOpts := defaultTestOptions()
     636           1 :         opts := testOpts.Opts
     637           1 : 
     638           1 :         // There are some private options, which we don't want users to fiddle with.
     639           1 :         // There's no way to set it through the public interface. The only method is
     640           1 :         // through Parse.
     641           1 :         {
     642           1 :                 var privateOpts bytes.Buffer
     643           1 :                 fmt.Fprintln(&privateOpts, `[Options]`)
     644           1 :                 if rng.IntN(3) == 0 /* 33% */ {
     645           1 :                         fmt.Fprintln(&privateOpts, `  disable_delete_only_compactions=true`)
     646           1 :                 }
     647           1 :                 if rng.IntN(3) == 0 /* 33% */ {
     648           1 :                         fmt.Fprintln(&privateOpts, `  disable_elision_only_compactions=true`)
     649           1 :                 }
     650           1 :                 if rng.IntN(5) == 0 /* 20% */ {
     651           1 :                         fmt.Fprintln(&privateOpts, `  disable_lazy_combined_iteration=true`)
     652           1 :                 }
     653           1 :                 if privateOptsStr := privateOpts.String(); privateOptsStr != `[Options]\n` {
     654           1 :                         parseOptions(testOpts, privateOptsStr, customOptionParsers)
     655           1 :                 }
     656             :         }
     657             : 
     658           1 :         opts.BytesPerSync = 1 << uint(rng.IntN(28))     // 1B - 256MB
     659           1 :         opts.Cache = cache.New(1 << uint(rng.IntN(30))) // 1B - 1GB
     660           1 :         opts.DisableWAL = rng.IntN(2) == 0
     661           1 :         opts.FlushDelayDeleteRange = time.Millisecond * time.Duration(5*rng.IntN(245)) // 5-250ms
     662           1 :         opts.FlushDelayRangeKey = time.Millisecond * time.Duration(5*rng.IntN(245))    // 5-250ms
     663           1 :         opts.FlushSplitBytes = 1 << rng.IntN(20)                                       // 1B - 1MB
     664           1 :         opts.FormatMajorVersion = minimumFormatMajorVersion
     665           1 :         n := int(newestFormatMajorVersionToTest - opts.FormatMajorVersion)
     666           1 :         opts.FormatMajorVersion += pebble.FormatMajorVersion(rng.IntN(n + 1))
     667           1 :         opts.Experimental.L0CompactionConcurrency = 1 + rng.IntN(4) // 1-4
     668           1 :         opts.Experimental.LevelMultiplier = 5 << rng.IntN(7)        // 5 - 320
     669           1 :         opts.TargetByteDeletionRate = 1 << uint(20+rng.IntN(10))    // 1MB - 1GB
     670           1 :         opts.Experimental.ValidateOnIngest = rng.IntN(2) != 0
     671           1 :         opts.L0CompactionThreshold = 1 + rng.IntN(100)     // 1 - 100
     672           1 :         opts.L0CompactionFileThreshold = 1 << rng.IntN(11) // 1 - 1024
     673           1 :         opts.L0StopWritesThreshold = 50 + rng.IntN(100)    // 50 - 150
     674           1 :         if opts.L0StopWritesThreshold < 2*opts.L0CompactionThreshold {
     675           1 :                 opts.L0StopWritesThreshold = 2 * opts.L0CompactionThreshold
     676           1 :         }
     677           1 :         opts.LBaseMaxBytes = 1 << uint(rng.IntN(30)) // 1B - 1GB
     678           1 :         maxConcurrentCompactions := rng.IntN(3) + 1  // 1-3
     679           1 :         opts.MaxConcurrentCompactions = func() int {
     680           1 :                 return maxConcurrentCompactions
     681           1 :         }
     682           1 :         maxConcurrentDownloads := rng.IntN(3) + 1 // 1-3
     683           1 :         opts.MaxConcurrentDownloads = func() int {
     684           1 :                 return maxConcurrentDownloads
     685           1 :         }
     686           1 :         opts.MaxManifestFileSize = 1 << uint(rng.IntN(30)) // 1B  - 1GB
     687           1 :         opts.MemTableSize = 2 << (10 + uint(rng.IntN(16))) // 2KB - 256MB
     688           1 :         opts.MemTableStopWritesThreshold = 2 + rng.IntN(5) // 2 - 5
     689           1 :         if rng.IntN(2) == 0 {
     690           1 :                 opts.WALDir = "data/wal"
     691           1 :         }
     692             : 
     693             :         // Half the time enable WAL failover.
     694           1 :         if rng.IntN(2) == 0 {
     695           1 :                 // Use 10x longer durations when writing directly to FS; we don't want
     696           1 :                 // WAL failover to trigger excessively frequently.
     697           1 :                 referenceDur := time.Millisecond
     698           1 :                 if testOpts.useDisk {
     699           0 :                         referenceDur *= 10
     700           0 :                 }
     701             : 
     702           1 :                 scaleDuration := func(d time.Duration, minFactor, maxFactor float64) time.Duration {
     703           1 :                         return time.Duration(float64(d) * (minFactor + rng.Float64()*(maxFactor-minFactor)))
     704           1 :                 }
     705           1 :                 probeInterval := scaleDuration(referenceDur, 0.10, 0.50) // Between 10-50% of the reference duration.
     706           1 :                 unhealthyThreshold := expRandDuration(rng, 3*referenceDur, time.Second)
     707           1 :                 healthyThreshold := expRandDuration(rng, 3*referenceDur, time.Second)
     708           1 :                 // Use a healthy interval between 1-119x the probe interval. The probe
     709           1 :                 // maintains a maximum history 120 entries, so the healthy interval
     710           1 :                 // must not exceed 119x the probe interval.
     711           1 :                 healthyInterval := scaleDuration(probeInterval, 1.0, 119.0)
     712           1 :                 opts.WALFailover = &pebble.WALFailoverOptions{
     713           1 :                         Secondary: wal.Dir{FS: vfs.Default, Dirname: "data/wal_secondary"},
     714           1 :                         FailoverOptions: wal.FailoverOptions{
     715           1 :                                 PrimaryDirProbeInterval:      probeInterval,
     716           1 :                                 HealthyProbeLatencyThreshold: healthyThreshold,
     717           1 :                                 HealthyInterval:              healthyInterval,
     718           1 :                                 UnhealthySamplingInterval:    scaleDuration(unhealthyThreshold, 0.10, 0.50), // Between 10-50% of the unhealthy threshold
     719           1 :                                 UnhealthyOperationLatencyThreshold: func() (time.Duration, bool) {
     720           1 :                                         return unhealthyThreshold, true
     721           1 :                                 },
     722             :                                 ElevatedWriteStallThresholdLag: expRandDuration(rng, 5*referenceDur, 2*time.Second),
     723             :                         },
     724             :                 }
     725             :         }
     726           1 :         if rng.IntN(4) == 0 {
     727           1 :                 // Enable Writer parallelism for 25% of the random options. Setting
     728           1 :                 // MaxWriterConcurrency to any value greater than or equal to 1 has the
     729           1 :                 // same effect currently.
     730           1 :                 opts.Experimental.MaxWriterConcurrency = 2
     731           1 :                 opts.Experimental.ForceWriterParallelism = true
     732           1 :         }
     733           1 :         if rng.IntN(2) == 0 {
     734           1 :                 opts.Experimental.DisableIngestAsFlushable = func() bool { return true }
     735             :         }
     736             : 
     737             :         // We either use no multilevel compactions, multilevel compactions with the
     738             :         // default (zero) additional propensity, or multilevel compactions with an
     739             :         // additional propensity to encourage more multilevel compactions than we
     740             :         // ohterwise would.
     741           1 :         switch rng.IntN(3) {
     742           1 :         case 0:
     743           1 :                 opts.Experimental.MultiLevelCompactionHeuristic = pebble.NoMultiLevel{}
     744           1 :         case 1:
     745           1 :                 opts.Experimental.MultiLevelCompactionHeuristic = pebble.WriteAmpHeuristic{}
     746           1 :         default:
     747           1 :                 opts.Experimental.MultiLevelCompactionHeuristic = pebble.WriteAmpHeuristic{
     748           1 :                         AddPropensity: rng.Float64() * float64(rng.IntN(3)), // [0,3.0)
     749           1 :                         AllowL0:       rng.IntN(4) == 1,                     // 25% of the time
     750           1 :                 }
     751             :         }
     752           1 :         if rng.IntN(2) == 0 {
     753           1 :                 opts.AllocatorSizeClasses = pebble.JemallocSizeClasses
     754           1 :         }
     755             : 
     756           1 :         var lopts pebble.LevelOptions
     757           1 :         lopts.BlockRestartInterval = 1 + rng.IntN(64)  // 1 - 64
     758           1 :         lopts.BlockSize = 1 << uint(rng.IntN(24))      // 1 - 16MB
     759           1 :         lopts.BlockSizeThreshold = 50 + rng.IntN(50)   // 50 - 100
     760           1 :         lopts.IndexBlockSize = 1 << uint(rng.IntN(24)) // 1 - 16MB
     761           1 :         lopts.TargetFileSize = 1 << uint(rng.IntN(28)) // 1 - 256MB
     762           1 :         if lopts.TargetFileSize < 1<<12 {
     763           1 :                 // We will generate a lot of files, which will slow down compactions.
     764           1 :                 // Increase L0StopWritesThreshold to reduce the number of write stalls
     765           1 :                 // that could cause operation timeouts.
     766           1 :                 opts.L0StopWritesThreshold = 100
     767           1 :         }
     768             :         // The EstimatedSize of an empty table writer is 8 bytes. We want something a
     769             :         // little bigger than that as the minimum target.
     770           1 :         lopts.TargetFileSize = max(lopts.TargetFileSize, 12)
     771           1 : 
     772           1 :         // We either use no bloom filter, the default filter, or a filter with
     773           1 :         // randomized bits-per-key setting. We zero out the Filters map. It'll get
     774           1 :         // repopulated on EnsureDefaults accordingly.
     775           1 :         opts.Filters = nil
     776           1 :         switch rng.IntN(3) {
     777           1 :         case 0:
     778           1 :                 lopts.FilterPolicy = nil
     779           1 :         case 1:
     780           1 :                 lopts.FilterPolicy = bloom.FilterPolicy(10)
     781           1 :         default:
     782           1 :                 lopts.FilterPolicy = newTestingFilterPolicy(1 << rng.IntN(5))
     783             :         }
     784             : 
     785             :         // We use either no compression, snappy compression or zstd compression.
     786           1 :         switch rng.IntN(3) {
     787           1 :         case 0:
     788           1 :                 lopts.Compression = func() block.Compression { return pebble.NoCompression }
     789           1 :         case 1:
     790           1 :                 lopts.Compression = func() block.Compression { return pebble.ZstdCompression }
     791           1 :         default:
     792           1 :                 lopts.Compression = func() block.Compression { return pebble.SnappyCompression }
     793             :         }
     794           1 :         opts.Levels = []pebble.LevelOptions{lopts}
     795           1 : 
     796           1 :         // Explicitly disable disk-backed FS's for the random configurations. The
     797           1 :         // single standard test configuration that uses a disk-backed FS is
     798           1 :         // sufficient.
     799           1 :         testOpts.useDisk = false
     800           1 :         testOpts.strictFS = rng.IntN(2) != 0 // Only relevant for MemFS.
     801           1 :         // 50% of the time, enable IO latency injection.
     802           1 :         if rng.IntN(2) == 0 {
     803           1 :                 // Note: we want ioLatencyProbability to be at least 1e-10, otherwise it
     804           1 :                 // might print as 0 when we stringify options.
     805           1 :                 testOpts.ioLatencyProbability = 1e-10 + 0.01*rng.Float64() // 0-1%
     806           1 :                 testOpts.ioLatencyMean = expRandDuration(rng, 3*time.Millisecond, time.Second)
     807           1 :                 testOpts.ioLatencySeed = rng.Int64()
     808           1 :         }
     809           1 :         testOpts.Threads = rng.IntN(runtime.GOMAXPROCS(0)) + 1
     810           1 :         if testOpts.strictFS {
     811           1 :                 opts.DisableWAL = false
     812           1 :                 opts.FS = vfs.NewCrashableMem()
     813           1 :         } else if !testOpts.useDisk {
     814           1 :                 opts.FS = vfs.NewMem()
     815           1 :         }
     816             :         // Update the WALFailover's secondary to use the same FS. This isn't
     817             :         // strictly necessary (the WALFailover could use a separate FS), but it
     818             :         // ensures when we save a copy of the test state to disk, we include the
     819             :         // secondary's WALs.
     820           1 :         if opts.WALFailover != nil {
     821           1 :                 opts.WALFailover.Secondary.FS = opts.FS
     822           1 :         }
     823           1 :         testOpts.ingestUsingApply = rng.IntN(2) != 0
     824           1 :         testOpts.deleteSized = rng.IntN(2) != 0
     825           1 :         testOpts.replaceSingleDelete = rng.IntN(2) != 0
     826           1 :         testOpts.disableBlockPropertyCollector = rng.IntN(2) == 1
     827           1 :         if testOpts.disableBlockPropertyCollector {
     828           1 :                 testOpts.Opts.BlockPropertyCollectors = nil
     829           1 :         }
     830           1 :         testOpts.enableValueBlocks = rng.IntN(2) != 0
     831           1 :         if testOpts.enableValueBlocks {
     832           1 :                 testOpts.Opts.Experimental.EnableValueBlocks = func() bool { return true }
     833             :         }
     834           1 :         testOpts.disableValueBlocksForIngestSSTables = rng.IntN(2) == 0
     835           1 :         testOpts.asyncApplyToDB = rng.IntN(2) != 0
     836           1 :         // 20% of time, enable shared storage.
     837           1 :         if rng.IntN(5) == 0 {
     838           1 :                 testOpts.sharedStorageEnabled = true
     839           1 :                 if testOpts.Opts.FormatMajorVersion < pebble.FormatMinForSharedObjects {
     840           1 :                         testOpts.Opts.FormatMajorVersion = pebble.FormatMinForSharedObjects
     841           1 :                 }
     842           1 :                 testOpts.sharedStorageFS = remote.NewInMem()
     843           1 :                 // If shared storage is enabled, pick between writing all files on shared
     844           1 :                 // vs. lower levels only, 50% of the time.
     845           1 :                 testOpts.Opts.Experimental.CreateOnShared = remote.CreateOnSharedAll
     846           1 :                 if rng.IntN(2) == 0 {
     847           1 :                         testOpts.Opts.Experimental.CreateOnShared = remote.CreateOnSharedLower
     848           1 :                 }
     849             :                 // If shared storage is enabled, enable secondary cache 50% of time.
     850           1 :                 if rng.IntN(2) == 0 {
     851           1 :                         testOpts.secondaryCacheEnabled = true
     852           1 :                         // TODO(josh): Randomize various secondary cache settings.
     853           1 :                         testOpts.Opts.Experimental.SecondaryCacheSizeBytes = 1024 * 1024 * 32 // 32 MBs
     854           1 :                 }
     855             :                 // 50% of the time, enable shared replication.
     856           1 :                 testOpts.useSharedReplicate = rng.IntN(2) == 0
     857             :         }
     858             : 
     859             :         // 50% of time, enable external storage.
     860           1 :         if rng.IntN(2) == 0 {
     861           1 :                 testOpts.externalStorageEnabled = true
     862           1 :                 if testOpts.Opts.FormatMajorVersion < pebble.FormatSyntheticPrefixSuffix {
     863           1 :                         testOpts.Opts.FormatMajorVersion = pebble.FormatSyntheticPrefixSuffix
     864           1 :                 }
     865           1 :                 testOpts.externalStorageFS = remote.NewInMem()
     866             :         }
     867             : 
     868           1 :         testOpts.seedEFOS = rng.Uint64()
     869           1 :         testOpts.ingestSplit = rng.IntN(2) == 0
     870           1 :         opts.Experimental.IngestSplit = func() bool { return testOpts.ingestSplit }
     871           1 :         testOpts.useExcise = rng.IntN(2) == 0
     872           1 :         if testOpts.useExcise {
     873           1 :                 if testOpts.Opts.FormatMajorVersion < pebble.FormatVirtualSSTables {
     874           1 :                         testOpts.Opts.FormatMajorVersion = pebble.FormatVirtualSSTables
     875           1 :                 }
     876             :         }
     877           1 :         testOpts.useDeleteOnlyCompactionExcises = rng.IntN(2) == 0
     878           1 :         opts.Experimental.EnableDeleteOnlyCompactionExcises = func() bool {
     879           1 :                 return testOpts.useDeleteOnlyCompactionExcises
     880           1 :         }
     881           1 :         testOpts.disableDownloads = rng.IntN(2) == 0
     882           1 :         testOpts.InitRemoteStorageFactory()
     883           1 :         testOpts.Opts.EnsureDefaults()
     884           1 :         return testOpts
     885             : }
     886             : 
     887           1 : func expRandDuration(rng *rand.Rand, meanDur, maxDur time.Duration) time.Duration {
     888           1 :         return min(maxDur, time.Duration(math.Round(rng.ExpFloat64()*float64(meanDur))))
     889           1 : }
     890             : 
     891           1 : func setupInitialState(dataDir string, testOpts *TestOptions) error {
     892           1 :         // Copy (vfs.Default,<initialStatePath>/data) to (testOpts.opts.FS,<dataDir>).
     893           1 :         ok, err := vfs.Clone(
     894           1 :                 vfs.Default,
     895           1 :                 testOpts.Opts.FS,
     896           1 :                 vfs.Default.PathJoin(testOpts.initialStatePath, "data"),
     897           1 :                 dataDir,
     898           1 :                 vfs.CloneSync,
     899           1 :                 vfs.CloneSkip(func(filename string) bool {
     900           1 :                         // Skip the archive of historical files, any checkpoints created by
     901           1 :                         // operations and files staged for ingest in tmp.
     902           1 :                         b := filepath.Base(filename)
     903           1 :                         return b == "archive" || b == "checkpoints" || b == "tmp"
     904           1 :                 }))
     905           1 :         if err != nil {
     906           0 :                 return err
     907           1 :         } else if !ok {
     908           0 :                 return os.ErrNotExist
     909           0 :         }
     910             : 
     911             :         // Tests with wal_dir set store their WALs in a `wal` directory. The source
     912             :         // database (initialStatePath) could've had wal_dir set, or the current test
     913             :         // options (testOpts) could have wal_dir set, or both.
     914             :         //
     915             :         // If the test opts are not configured to use a WAL dir, we add the WAL dir
     916             :         // as a 'WAL recovery dir' so that we'll read any WALs in the directory in
     917             :         // Open.
     918           1 :         walRecoveryPath := testOpts.Opts.FS.PathJoin(dataDir, "wal")
     919           1 :         if testOpts.Opts.WALDir != "" {
     920           0 :                 // If the test opts are configured to use a WAL dir, we add the data
     921           0 :                 // directory itself as a 'WAL recovery dir' so that we'll read any WALs if
     922           0 :                 // the previous test was writing them to the data directory.
     923           0 :                 walRecoveryPath = dataDir
     924           0 :         }
     925           1 :         testOpts.Opts.WALRecoveryDirs = append(testOpts.Opts.WALRecoveryDirs, wal.Dir{
     926           1 :                 FS:      testOpts.Opts.FS,
     927           1 :                 Dirname: walRecoveryPath,
     928           1 :         })
     929           1 : 
     930           1 :         // If the failover dir exists and the test opts are not configured to use
     931           1 :         // WAL failover, add the failover directory as a 'WAL recovery dir' in case
     932           1 :         // the previous test was configured to use failover.
     933           1 :         failoverDir := testOpts.Opts.FS.PathJoin(dataDir, "wal_secondary")
     934           1 :         if _, err := testOpts.Opts.FS.Stat(failoverDir); err == nil && testOpts.Opts.WALFailover == nil {
     935           0 :                 testOpts.Opts.WALRecoveryDirs = append(testOpts.Opts.WALRecoveryDirs, wal.Dir{
     936           0 :                         FS:      testOpts.Opts.FS,
     937           0 :                         Dirname: failoverDir,
     938           0 :                 })
     939           0 :         }
     940           1 :         return nil
     941             : }
     942             : 
     943             : var blockPropertyCollectorConstructors = []func() pebble.BlockPropertyCollector{
     944             :         sstable.NewTestKeysBlockPropertyCollector,
     945             : }
     946             : 
     947             : // testingFilterPolicy is used to allow bloom filter policies with non-default
     948             : // bits-per-key setting. It is necessary because the name of the production
     949             : // filter policy is fixed (see bloom.FilterPolicy.Name()); we need to output a
     950             : // custom policy name to the OPTIONS file that the test can then parse.
     951             : type testingFilterPolicy struct {
     952             :         bloom.FilterPolicy
     953             : }
     954             : 
     955             : var _ pebble.FilterPolicy = (*testingFilterPolicy)(nil)
     956             : 
     957           2 : func newTestingFilterPolicy(bitsPerKey int) *testingFilterPolicy {
     958           2 :         return &testingFilterPolicy{
     959           2 :                 FilterPolicy: bloom.FilterPolicy(bitsPerKey),
     960           2 :         }
     961           2 : }
     962             : 
     963             : const testingFilterPolicyFmt = "testing_bloom_filter/bits_per_key=%d"
     964             : 
     965             : // Name implements the pebble.FilterPolicy interface.
     966           2 : func (t *testingFilterPolicy) Name() string {
     967           2 :         if t.FilterPolicy == 10 {
     968           0 :                 return "rocksdb.BuiltinBloomFilter"
     969           0 :         }
     970           2 :         return fmt.Sprintf(testingFilterPolicyFmt, t.FilterPolicy)
     971             : }
     972             : 
     973           2 : func filterPolicyFromName(name string) (pebble.FilterPolicy, error) {
     974           2 :         switch name {
     975           2 :         case "none":
     976           2 :                 return nil, nil
     977           2 :         case "rocksdb.BuiltinBloomFilter":
     978           2 :                 return bloom.FilterPolicy(10), nil
     979             :         }
     980           2 :         var bitsPerKey int
     981           2 :         if _, err := fmt.Sscanf(name, testingFilterPolicyFmt, &bitsPerKey); err != nil {
     982           0 :                 return nil, errors.Errorf("Invalid filter policy name '%s'", name)
     983           0 :         }
     984           2 :         return newTestingFilterPolicy(bitsPerKey), nil
     985             : }

Generated by: LCOV version 1.14