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

Generated by: LCOV version 1.14