github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  )