github.com/m3db/m3@v1.5.0/src/x/cache/lru_cache_prop_test.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package cache 22 23 import ( 24 "fmt" 25 "math" 26 "math/rand" 27 "reflect" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/leanovate/gopter" 33 "github.com/leanovate/gopter/gen" 34 "github.com/leanovate/gopter/prop" 35 "go.uber.org/atomic" 36 ) 37 38 func TestLRUPropertyTest(t *testing.T) { 39 testLRUPropFunc := func(input propTestMultiInput) (bool, error) { 40 maxSize := 1000 41 42 cacheOpts := &LRUOptions{ 43 MaxEntries: maxSize, 44 Now: time.Now, 45 } 46 47 lru := NewLRU(cacheOpts) 48 49 // Print stats. 50 for i, in := range input.inputs { 51 vals := make(map[string]string) 52 gets := 0 53 puts := 0 54 for _, op := range in.operations.operations { 55 vals[op.key] = op.value 56 switch op.operation { 57 case propTestOperationGet: 58 gets++ 59 case propTestOperationPut: 60 puts++ 61 } 62 } 63 64 t.Logf("concurrency[%d] = keys=%d, gets=%d, puts=%d\n", i, len(vals), gets, puts) 65 } 66 67 // Kick off concurrent tests. 68 var ( 69 wg sync.WaitGroup 70 found = atomic.NewInt64(0) 71 notFound = atomic.NewInt64(0) 72 ) 73 for _, in := range input.inputs { 74 in := in 75 wg.Add(1) 76 go func() { 77 defer wg.Done() 78 79 for _, op := range in.operations.operations { 80 switch op.operation { 81 case propTestOperationGet: 82 _, ok := lru.TryGet(op.key) 83 if !ok { 84 notFound.Inc() 85 } else { 86 found.Inc() 87 } 88 case propTestOperationPut: 89 lru.Put(op.key, op.value) 90 default: 91 panic("unknown op") 92 } 93 } 94 }() 95 } 96 97 wg.Wait() 98 99 t.Logf("found=%d, not_found=%d\n", found.Load(), notFound.Load()) 100 101 if actualSize := len(lru.entries); actualSize > maxSize { 102 return false, fmt.Errorf("cache size %d exceeded MaxSize %d", actualSize, maxSize) 103 } 104 105 return true, nil 106 } 107 108 parameters := gopter.DefaultTestParameters() 109 parameters.Rng.Seed(123456789) 110 parameters.MinSuccessfulTests = 25 111 props := gopter.NewProperties(parameters) 112 props.Property("Test LRU concurrent use", 113 prop.ForAll(testLRUPropFunc, genPropTestMultiInputs(propTestMultiInputsOptions{ 114 inputsOpts: []genPropTestInputOptions{ 115 { 116 numKeysMin: 5000, 117 numKeysMax: 7000, 118 opsPerKeyMin: 8, 119 opsPerKeyMax: 16, 120 getPutRatio: 0.25, 121 }, 122 { 123 numKeysMin: 5000, 124 numKeysMax: 7000, 125 opsPerKeyMin: 3, 126 opsPerKeyMax: 5, 127 getPutRatio: 0.75, 128 }, 129 }, 130 }))) 131 132 props.TestingRun(t) 133 } 134 135 type propTestInput struct { 136 operations *generatedPropTestOperations 137 } 138 139 type propTestMultiInput struct { 140 inputs []propTestInput 141 } 142 143 type propTestMultiInputsOptions struct { 144 inputsOpts []genPropTestInputOptions 145 } 146 147 func genPropTestMultiInputs(opts propTestMultiInputsOptions) gopter.Gen { 148 // nolint: prealloc 149 var gens []gopter.Gen 150 for _, subOpts := range opts.inputsOpts { 151 gens = append(gens, genPropTestInput(subOpts)) 152 } 153 return gopter.CombineGens(gens...).FlatMap(func(input interface{}) gopter.Gen { 154 inputs := input.([]interface{}) 155 156 var converted []propTestInput 157 for _, input := range inputs { 158 converted = append(converted, input.(propTestInput)) 159 } 160 161 return gopter.CombineGens(gen.Bool()). 162 Map(func(input interface{}) propTestMultiInput { 163 return propTestMultiInput{inputs: converted} 164 }) 165 }, reflect.TypeOf(propTestMultiInput{})) 166 } 167 168 type genPropTestInputOptions struct { 169 numKeysMin int 170 numKeysMax int 171 opsPerKeyMin int 172 opsPerKeyMax int 173 getPutRatio float64 174 } 175 176 func genPropTestInput(opts genPropTestInputOptions) gopter.Gen { 177 return gopter.CombineGens( 178 genPropTestOperations(opts), 179 ).Map(func(inputs []interface{}) propTestInput { 180 return propTestInput{ 181 operations: inputs[0].(*generatedPropTestOperations), 182 } 183 }) 184 } 185 186 func genPropTestOperations(opts genPropTestInputOptions) gopter.Gen { 187 return gopter.CombineGens( 188 gen.IntRange(opts.numKeysMin, opts.numKeysMax), 189 gen.Int64Range(math.MinInt64, math.MaxInt64), 190 ).FlatMap(func(input interface{}) gopter.Gen { 191 inputs := input.([]interface{}) 192 193 numKeys := inputs[0].(int) 194 195 randSeed := inputs[1].(int64) 196 // nolint: gosec 197 randGen := rand.New(rand.NewSource(randSeed)) 198 199 return gopter.CombineGens().Map(func(input interface{}) *generatedPropTestOperations { 200 operations := make([]propTestOperation, 0, numKeys) 201 for i := 0; i < numKeys; i++ { 202 key := fmt.Sprintf("key-%d", i) 203 value := fmt.Sprintf("value-%d", i) 204 base := opts.opsPerKeyMin 205 n := randGen.Intn(opts.opsPerKeyMax - base) 206 for j := 0; j < base+n; j++ { 207 var op propTestOperationType 208 dice := randGen.Float64() 209 if dice < opts.getPutRatio { 210 op = propTestOperationGet 211 } else { 212 op = propTestOperationPut 213 } 214 215 operations = append(operations, propTestOperation{ 216 operation: op, 217 key: key, 218 value: value, 219 }) 220 } 221 } 222 223 rand.Shuffle(len(operations), func(i, j int) { 224 operations[i], operations[j] = operations[j], operations[i] 225 }) 226 227 return &generatedPropTestOperations{ 228 operations: operations, 229 } 230 }) 231 }, reflect.TypeOf(&generatedPropTestOperations{})) 232 } 233 234 type generatedPropTestOperations struct { 235 operations []propTestOperation 236 } 237 238 type propTestOperation struct { 239 operation propTestOperationType 240 key string 241 value string 242 } 243 244 type propTestOperationType uint 245 246 const ( 247 propTestOperationGet propTestOperationType = iota 248 propTestOperationPut 249 )