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

          Line data    Source code
       1             : // Copyright 2024 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             :         "cmp"
       9             :         "fmt"
      10             :         "math/rand/v2"
      11             :         "slices"
      12             : 
      13             :         "github.com/cockroachdb/pebble"
      14             :         "github.com/cockroachdb/pebble/internal/testkeys"
      15             :         "github.com/cockroachdb/pebble/sstable"
      16             :         "github.com/cockroachdb/pebble/sstable/colblk"
      17             : )
      18             : 
      19             : var TestkeysKeyFormat = KeyFormat{
      20             :         Comparer: testkeys.Comparer,
      21           2 :         KeySchema: func() *colblk.KeySchema {
      22           2 :                 kf := colblk.DefaultKeySchema(testkeys.Comparer, 16 /* bundle size */)
      23           2 :                 return &kf
      24           2 :         }(),
      25           2 :         FormatKey:       func(k UserKey) string { return string(k) },
      26           2 :         FormatKeySuffix: func(s UserKeySuffix) string { return string(s) },
      27           1 :         NewGenerator: func(km *keyManager, rng *rand.Rand, cfg OpConfig) KeyGenerator {
      28           1 :                 return &testkeyKeyGenerator{
      29           1 :                         keyManager: km,
      30           1 :                         rng:        rng,
      31           1 :                         cfg:        cfg,
      32           1 :                 }
      33           1 :         },
      34           2 :         NewSuffixFilterMask: func() pebble.BlockPropertyFilterMask {
      35           2 :                 return sstable.NewTestKeysMaskingFilter()
      36           2 :         },
      37           2 :         NewSuffixBlockPropertyFilter: func(filterMin, filterMax []byte) sstable.BlockPropertyFilter {
      38           2 :                 low, err := testkeys.ParseSuffix(filterMin)
      39           2 :                 if err != nil {
      40           0 :                         panic(err)
      41             :                 }
      42           2 :                 high, err := testkeys.ParseSuffix(filterMax)
      43           2 :                 if err != nil {
      44           0 :                         panic(err)
      45             :                 }
      46           2 :                 return sstable.NewTestKeysBlockPropertyFilter(uint64(low), uint64(high))
      47             :         },
      48             : }
      49             : 
      50             : type testkeyKeyGenerator struct {
      51             :         keyManager *keyManager
      52             :         rng        *rand.Rand
      53             :         cfg        OpConfig
      54             : }
      55             : 
      56             : // RecordPrecedingKey may be invoked before generating keys to inform the key
      57             : // generator of a key that was previously generated and used within a related
      58             : // test context.
      59           1 : func (kg *testkeyKeyGenerator) RecordPrecedingKey(key []byte) {
      60           1 :         // If the key has a suffix that's larger than the current max suffix,
      61           1 :         // ratchet up the maximum of the distribution of suffixes.
      62           1 :         if i := testkeys.Comparer.Split(key); i < len(key) {
      63           1 :                 s, err := testkeys.ParseSuffix(key[i:])
      64           1 :                 if err != nil {
      65           0 :                         panic(err)
      66             :                 }
      67           1 :                 if uint64(s) > kg.cfg.writeSuffixDist.Max() {
      68           1 :                         diff := uint64(s) - kg.cfg.writeSuffixDist.Max()
      69           1 :                         kg.cfg.writeSuffixDist.IncMax(diff)
      70           1 :                 }
      71             :         }
      72             : }
      73             : 
      74             : // RandKey returns a random key (either a previously known key, or a new key).
      75           1 : func (kg *testkeyKeyGenerator) RandKey(newKeyProbability float64) []byte {
      76           1 :         return kg.randKey(newKeyProbability, nil /* bounds */)
      77           1 : }
      78             : 
      79             : // RandKeyInRange returns a random key (either a previously known key, or a new
      80             : // key) in the given key range.
      81             : func (kg *testkeyKeyGenerator) RandKeyInRange(
      82             :         newKeyProbability float64, kr pebble.KeyRange,
      83           1 : ) []byte {
      84           1 :         return kg.randKey(newKeyProbability, &kr)
      85           1 : }
      86             : 
      87             : // RandPrefix returns a random prefix key (a key with no suffix).
      88           1 : func (kg *testkeyKeyGenerator) RandPrefix(newPrefix float64) []byte {
      89           1 :         prefixes := kg.keyManager.prefixes()
      90           1 :         if len(prefixes) > 0 && kg.rng.Float64() > newPrefix {
      91           1 :                 return pickOneUniform(kg.rng, prefixes)
      92           1 :         }
      93             : 
      94             :         // Use a new prefix.
      95           1 :         for {
      96           1 :                 prefix := kg.generateKeyWithSuffix(4, 12, 0)
      97           1 :                 if !kg.keyManager.prefixExists(prefix) {
      98           1 :                         if !kg.keyManager.addNewKey(prefix) {
      99           0 :                                 panic("key must not exist if prefix doesn't exist")
     100             :                         }
     101           1 :                         return prefix
     102             :                 }
     103             :         }
     104             : }
     105             : 
     106             : // UniqueKeys takes a key-generating function and uses it to generate n unique
     107             : // keys, returning them in sorted order.
     108           1 : func (kg *testkeyKeyGenerator) UniqueKeys(n int, genFn func() []byte) [][]byte {
     109           1 :         keys := make([][]byte, n)
     110           1 :         used := make(map[string]struct{}, n)
     111           1 :         for i := range keys {
     112           1 :                 for attempts := 0; ; attempts++ {
     113           1 :                         keys[i] = genFn()
     114           1 :                         if _, exists := used[string(keys[i])]; !exists {
     115           1 :                                 break
     116             :                         }
     117           1 :                         if attempts > 100000 {
     118           0 :                                 panic("could not generate unique key")
     119             :                         }
     120             :                 }
     121           1 :                 used[string(keys[i])] = struct{}{}
     122             :         }
     123           1 :         slices.SortFunc(keys, kg.cmp)
     124           1 :         return keys
     125             : }
     126             : 
     127             : // SkewedSuffix generates a random suffix according to the configuration's
     128             : // suffix distribution. It takes a probability 0 ≤ p ≤ 1.0 indicating the
     129             : // probability with which the generator should increase the max suffix generated
     130             : // by the generator.
     131             : //
     132             : // May return a nil suffix, with the probability the configuration's suffix
     133             : // distribution assigns to the zero suffix.
     134           1 : func (kg *testkeyKeyGenerator) SkewedSuffix(incMaxProb float64) []byte {
     135           1 :         if suffix := kg.skewedSuffixInt(incMaxProb); suffix != 0 {
     136           1 :                 return testkeys.Suffix(suffix)
     137           1 :         }
     138           1 :         return nil
     139             : }
     140             : 
     141             : // skewedSuffixInt is a helper of SkewedSuffix which returns the unencoded
     142             : // suffix as an integer.
     143           1 : func (kg *testkeyKeyGenerator) skewedSuffixInt(incMaxProb float64) int64 {
     144           1 :         if kg.rng.Float64() < incMaxProb {
     145           1 :                 kg.cfg.writeSuffixDist.IncMax(1)
     146           1 :         }
     147           1 :         return int64(kg.cfg.writeSuffixDist.Uint64(kg.rng))
     148             : }
     149             : 
     150             : // IncMaxSuffix increases the max suffix range and returns the new maximum
     151             : // suffix (which is guaranteed to be larger than any previously generated
     152             : // suffix).
     153           1 : func (kg *testkeyKeyGenerator) IncMaxSuffix() []byte {
     154           1 :         kg.cfg.writeSuffixDist.IncMax(1)
     155           1 :         return testkeys.Suffix(int64(kg.cfg.writeSuffixDist.Max()))
     156           1 : }
     157             : 
     158           1 : func (kg *testkeyKeyGenerator) SuffixRange() (low, high []byte) {
     159           1 :         a := kg.uniformSuffixInt()
     160           1 :         b := kg.uniformSuffixInt()
     161           1 :         if a > b {
     162           1 :                 a, b = b, a
     163           1 :         } else if a == b {
     164           1 :                 b++
     165           1 :         }
     166           1 :         return testkeys.Suffix(a), testkeys.Suffix(b)
     167             : }
     168             : 
     169             : // UniformSuffix returns a suffix in the same range as SkewedSuffix but with a
     170             : // uniform distribution. This is used during reads to better exercise reading a
     171             : // mix of older and newer keys. The suffix can be empty.
     172             : //
     173             : // May return a nil suffix.
     174           1 : func (kg *testkeyKeyGenerator) UniformSuffix() []byte {
     175           1 :         if suffix := kg.uniformSuffixInt(); suffix != 0 {
     176           1 :                 return testkeys.Suffix(suffix)
     177           1 :         }
     178           1 :         return nil
     179             : }
     180             : 
     181             : // uniformSuffixInt is a helper of UniformSuffix which returns the unencoded
     182             : // suffix as an integer.
     183           1 : func (kg *testkeyKeyGenerator) uniformSuffixInt() int64 {
     184           1 :         maxVal := kg.cfg.writeSuffixDist.Max()
     185           1 :         return kg.rng.Int64N(int64(maxVal))
     186           1 : }
     187             : 
     188             : // randKey returns a random key (either a previously known key or a new key).
     189             : //
     190             : // If bounds is not nil, the key will be inside the bounds.
     191           1 : func (kg *testkeyKeyGenerator) randKey(newKeyProbability float64, bounds *pebble.KeyRange) []byte {
     192           1 :         var knownKeys [][]byte
     193           1 :         if bounds == nil {
     194           1 :                 knownKeys = kg.keyManager.knownKeys()
     195           1 :         } else {
     196           1 :                 if kg.cmp(bounds.Start, bounds.End) >= 0 {
     197           0 :                         panic(fmt.Sprintf("invalid bounds [%q, %q)", bounds.Start, bounds.End))
     198             :                 }
     199           1 :                 knownKeys = kg.keyManager.knownKeysInRange(*bounds)
     200             :         }
     201           1 :         switch {
     202           1 :         case len(knownKeys) > 0 && kg.rng.Float64() > newKeyProbability:
     203           1 :                 // Use an existing user key.
     204           1 :                 return pickOneUniform(kg.rng, knownKeys)
     205             : 
     206           1 :         case len(knownKeys) > 0 && kg.rng.Float64() > kg.cfg.newPrefix:
     207           1 :                 // Use an existing prefix but a new suffix, producing a new user key.
     208           1 :                 prefixes := kg.keyManager.prefixes()
     209           1 : 
     210           1 :                 // If we're constrained to a key range, find which existing prefixes
     211           1 :                 // fall within that key range.
     212           1 :                 if bounds != nil {
     213           1 :                         s, _ := slices.BinarySearchFunc(prefixes, bounds.Start, kg.cmp)
     214           1 :                         e, _ := slices.BinarySearchFunc(prefixes, bounds.End, kg.cmp)
     215           1 :                         prefixes = prefixes[s:e]
     216           1 :                 }
     217             : 
     218           1 :                 if len(prefixes) > 0 {
     219           1 :                         for {
     220           1 :                                 // Pick a prefix on each iteration in case most or all suffixes are
     221           1 :                                 // already in use for any individual prefix.
     222           1 :                                 p := kg.rng.IntN(len(prefixes))
     223           1 :                                 suffix := int64(kg.cfg.writeSuffixDist.Uint64(kg.rng))
     224           1 : 
     225           1 :                                 var key []byte
     226           1 :                                 if suffix > 0 {
     227           1 :                                         key = resizeBuffer(key, len(prefixes[p]), testkeys.SuffixLen(suffix))
     228           1 :                                         n := copy(key, prefixes[p])
     229           1 :                                         testkeys.WriteSuffix(key[n:], suffix)
     230           1 :                                 } else {
     231           1 :                                         key = resizeBuffer(key, len(prefixes[p]), 0)
     232           1 :                                         copy(key, prefixes[p])
     233           1 :                                 }
     234             : 
     235           1 :                                 if bounds == nil || (kg.cmp(key, bounds.Start) >= 0 && kg.cmp(key, bounds.End) < 0) {
     236           1 :                                         if kg.keyManager.addNewKey(key) {
     237           1 :                                                 return key
     238           1 :                                         }
     239             :                                 }
     240             : 
     241             :                                 // If the generated key already existed, or the generated key
     242             :                                 // fell outside the provided bounds, increase the suffix
     243             :                                 // distribution and loop.
     244           1 :                                 kg.cfg.writeSuffixDist.IncMax(1)
     245             :                         }
     246             :                 }
     247             :                 // Otherwise fall through to generating a new prefix.
     248             :         }
     249             : 
     250           1 :         if bounds == nil {
     251           1 :                 suffix := kg.skewedSuffixInt(0.01)
     252           1 :                 for {
     253           1 :                         key := kg.generateKeyWithSuffix(4, 12, suffix)
     254           1 :                         if !kg.keyManager.prefixExists(kg.prefix(key)) {
     255           1 :                                 if !kg.keyManager.addNewKey(key) {
     256           0 :                                         panic("key must not exist if prefix doesn't exist")
     257             :                                 }
     258           1 :                                 return key
     259             :                         }
     260             :                 }
     261             :         }
     262             :         // We need to generate a key between the bounds.
     263           1 :         startPrefix, startSuffix := kg.parseKey(bounds.Start)
     264           1 :         endPrefix, endSuffix := kg.parseKey(bounds.End)
     265           1 :         var prefix []byte
     266           1 :         var suffix int64
     267           1 :         if kg.equal(startPrefix, endPrefix) {
     268           0 :                 prefix = startPrefix
     269           0 :                 // Bounds have the same prefix, generate a suffix in-between.
     270           0 :                 if cmpSuffix(startSuffix, endSuffix) >= 0 {
     271           0 :                         panic(fmt.Sprintf("invalid bounds [%q, %q)", bounds.Start, bounds.End))
     272             :                 }
     273           0 :                 suffix = kg.skewedSuffixInt(0.01)
     274           0 :                 for i := 0; !(cmpSuffix(startSuffix, suffix) <= 0 && cmpSuffix(suffix, endSuffix) < 0); i++ {
     275           0 :                         if i > 10 {
     276           0 :                                 // This value is always >= startSuffix and < endSuffix.
     277           0 :                                 suffix = (startSuffix + endSuffix) / 2
     278           0 :                                 break
     279             :                         }
     280             :                         // The suffix we want must exist in the current suffix range, we don't
     281             :                         // want to keep increasing it here.
     282           0 :                         suffix = kg.skewedSuffixInt(0)
     283             :                 }
     284           1 :         } else {
     285           1 :                 prefix = testkeys.RandomPrefixInRange(startPrefix, endPrefix, kg.rng)
     286           1 :                 suffix = kg.skewedSuffixInt(0.01)
     287           1 :                 if kg.equal(prefix, startPrefix) {
     288           1 :                         // We can't use a suffix which sorts before startSuffix.
     289           1 :                         for i := 0; cmpSuffix(suffix, startSuffix) < 0; i++ {
     290           0 :                                 if i > 10 {
     291           0 :                                         suffix = startSuffix
     292           0 :                                         break
     293             :                                 }
     294           0 :                                 suffix = kg.skewedSuffixInt(0)
     295             :                         }
     296             :                 }
     297             :         }
     298           1 :         key := slices.Clip(prefix)
     299           1 :         if suffix != 0 {
     300           1 :                 key = append(key, testkeys.Suffix(suffix)...)
     301           1 :         }
     302           1 :         if kg.cmp(key, bounds.Start) < 0 || kg.cmp(key, bounds.End) >= 0 {
     303           0 :                 panic(fmt.Sprintf("invalid randKey %q; bounds: [%q, %q) %v %v",
     304           0 :                         key, bounds.Start, bounds.End, kg.cmp(key, bounds.Start), kg.cmp(key, bounds.End)))
     305             :         }
     306             :         // We might (rarely) produce an existing key here, that's ok.
     307           1 :         kg.keyManager.addNewKey(key)
     308           1 :         return key
     309             : }
     310             : 
     311             : // generateKeyWithSuffix generates a key with a random prefix and the given
     312             : // suffix. If the given suffix is 0, the key will not have a suffix.
     313             : func (kg *testkeyKeyGenerator) generateKeyWithSuffix(
     314             :         minPrefixLen, maxPrefixLen int, suffix int64,
     315           1 : ) []byte {
     316           1 :         prefix := randBytes(kg.rng, minPrefixLen, maxPrefixLen)
     317           1 :         if suffix == 0 {
     318           1 :                 return prefix
     319           1 :         }
     320           1 :         return append(prefix, testkeys.Suffix(suffix)...)
     321             : }
     322             : 
     323             : // cmpSuffix compares two suffixes, where suffix 0 means there is no suffix.
     324           1 : func cmpSuffix(s1, s2 int64) int {
     325           1 :         switch {
     326           0 :         case s1 == s2:
     327           0 :                 return 0
     328           0 :         case s1 == 0:
     329           0 :                 return -1
     330           1 :         case s2 == 0:
     331           1 :                 return +1
     332           0 :         default:
     333           0 :                 return cmp.Compare(s2, s1)
     334             :         }
     335             : }
     336             : 
     337           1 : func (kg *testkeyKeyGenerator) cmp(a, b []byte) int {
     338           1 :         return kg.keyManager.kf.Comparer.Compare(a, b)
     339           1 : }
     340             : 
     341           1 : func (kg *testkeyKeyGenerator) equal(a, b []byte) bool {
     342           1 :         return kg.keyManager.kf.Comparer.Equal(a, b)
     343           1 : }
     344             : 
     345           1 : func (kg *testkeyKeyGenerator) split(a []byte) int {
     346           1 :         return kg.keyManager.kf.Comparer.Split(a)
     347           1 : }
     348             : 
     349           1 : func (kg *testkeyKeyGenerator) prefix(a []byte) []byte {
     350           1 :         n := kg.split(a)
     351           1 :         return a[:n:n]
     352           1 : }
     353             : 
     354           1 : func (kg *testkeyKeyGenerator) parseKey(k []byte) (prefix []byte, suffix int64) {
     355           1 :         n := kg.split(k)
     356           1 :         if n == len(k) {
     357           1 :                 return k, 0
     358           1 :         }
     359           1 :         suffix, err := testkeys.ParseSuffix(k[n:])
     360           1 :         if err != nil {
     361           0 :                 panic(fmt.Sprintf("error parsing suffix for key %q", k))
     362             :         }
     363           1 :         return k[:n:n], suffix
     364             : }
     365             : 
     366           1 : func randBytes(rng *rand.Rand, minLen, maxLen int) []byte {
     367           1 :         n := minLen + rng.IntN(maxLen-minLen+1)
     368           1 :         if n == 0 {
     369           1 :                 return nil
     370           1 :         }
     371             :         // NB: The actual random values are not particularly important. We only use
     372             :         // lowercase letters because that makes visual determination of ordering
     373             :         // easier, rather than having to remember the lexicographic ordering of
     374             :         // uppercase vs lowercase, or letters vs numbers vs punctuation.
     375           1 :         const letters = "abcdefghijklmnopqrstuvwxyz"
     376           1 :         const lettersLen = uint64(len(letters))
     377           1 :         const lettersCharsPerRand = 12 // floor(log(math.MaxUint64)/log(lettersLen))
     378           1 : 
     379           1 :         var r uint64
     380           1 :         var q int
     381           1 :         buf := make([]byte, n)
     382           1 :         for i := range buf {
     383           1 :                 if q == 0 {
     384           1 :                         r = rng.Uint64()
     385           1 :                         q = lettersCharsPerRand
     386           1 :                 }
     387           1 :                 buf[i] = letters[r%lettersLen]
     388           1 :                 r = r / lettersLen
     389           1 :                 q--
     390             :         }
     391           1 :         return buf
     392             : }
     393             : 
     394           1 : func pickOneUniform[S ~[]E, E any](rng *rand.Rand, x S) E {
     395           1 :         return x[rng.IntN(len(x))]
     396           1 : }

Generated by: LCOV version 1.14