github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvnemesis/generator_test.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package kvnemesis
    12  
    13  import (
    14  	"context"
    15  	"reflect"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    20  	"github.com/cockroachdb/cockroach/pkg/util/log"
    21  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    22  	"github.com/cockroachdb/errors"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func trueForEachIntField(c *OperationConfig, fn func(int) bool) bool {
    27  	var forEachIntField func(v reflect.Value) bool
    28  	forEachIntField = func(v reflect.Value) bool {
    29  		switch v.Type().Kind() {
    30  		case reflect.Ptr:
    31  			return forEachIntField(v.Elem())
    32  		case reflect.Int:
    33  			ok := fn(int(v.Int()))
    34  			if !ok {
    35  				if log.V(1) {
    36  					log.Infof(context.Background(), "returned false for %d: %v", v.Int(), v)
    37  				}
    38  			}
    39  			return ok
    40  		case reflect.Struct:
    41  			for fieldIdx := 0; fieldIdx < v.NumField(); fieldIdx++ {
    42  				if !forEachIntField(v.Field(fieldIdx)) {
    43  					if log.V(1) {
    44  						log.Infof(context.Background(), "returned false for %s in %s",
    45  							v.Type().Field(fieldIdx).Name, v.Type().Name())
    46  					}
    47  					return false
    48  				}
    49  			}
    50  			return true
    51  		default:
    52  			panic(errors.AssertionFailedf(`unexpected type: %s`, v.Type()))
    53  		}
    54  
    55  	}
    56  	return forEachIntField(reflect.ValueOf(c))
    57  }
    58  
    59  // TestRandStep generates random steps until we've seen each type at least N
    60  // times, validating each step along the way. This both verifies that the config
    61  // returned by `newAllOperationsConfig()` in fact contains all operations as
    62  // well as verifies that Generator actually generates all of these operation
    63  // types.
    64  func TestRandStep(t *testing.T) {
    65  	defer leaktest.AfterTest(t)()
    66  
    67  	const minEachType = 5
    68  	config := newAllOperationsConfig()
    69  	config.NumNodes, config.NumReplicas = 2, 1
    70  	rng, _ := randutil.NewPseudoRand()
    71  	getReplicasFn := func(_ roachpb.Key) []roachpb.ReplicationTarget {
    72  		return make([]roachpb.ReplicationTarget, rng.Intn(2)+1)
    73  	}
    74  	g, err := MakeGenerator(config, getReplicasFn)
    75  	require.NoError(t, err)
    76  
    77  	keys := make(map[string]struct{})
    78  	var updateKeys func(Operation)
    79  	updateKeys = func(op Operation) {
    80  		switch o := op.GetValue().(type) {
    81  		case *PutOperation:
    82  			keys[string(o.Key)] = struct{}{}
    83  		case *BatchOperation:
    84  			for _, op := range o.Ops {
    85  				updateKeys(op)
    86  			}
    87  		case *ClosureTxnOperation:
    88  			for _, op := range o.Ops {
    89  				updateKeys(op)
    90  			}
    91  		}
    92  	}
    93  
    94  	splits := make(map[string]struct{})
    95  
    96  	var countClientOps func(*ClientOperationConfig, *BatchOperationConfig, ...Operation)
    97  	countClientOps = func(client *ClientOperationConfig, batch *BatchOperationConfig, ops ...Operation) {
    98  		for _, op := range ops {
    99  			switch o := op.GetValue().(type) {
   100  			case *GetOperation:
   101  				if _, ok := keys[string(o.Key)]; ok {
   102  					client.GetExisting++
   103  				} else {
   104  					client.GetMissing++
   105  				}
   106  			case *PutOperation:
   107  				if _, ok := keys[string(o.Key)]; ok {
   108  					client.PutExisting++
   109  				} else {
   110  					client.PutMissing++
   111  				}
   112  			case *BatchOperation:
   113  				batch.Batch++
   114  				countClientOps(&batch.Ops, nil, o.Ops...)
   115  			}
   116  		}
   117  	}
   118  
   119  	counts := OperationConfig{}
   120  	for {
   121  		step := g.RandStep(rng)
   122  		switch o := step.Op.GetValue().(type) {
   123  		case *GetOperation, *PutOperation, *BatchOperation:
   124  			countClientOps(&counts.DB, &counts.Batch, step.Op)
   125  		case *ClosureTxnOperation:
   126  			countClientOps(&counts.ClosureTxn.TxnClientOps, &counts.ClosureTxn.TxnBatchOps, o.Ops...)
   127  			if o.CommitInBatch != nil {
   128  				counts.ClosureTxn.CommitInBatch++
   129  				countClientOps(&counts.ClosureTxn.CommitBatchOps, nil, o.CommitInBatch.Ops...)
   130  			} else if o.Type == ClosureTxnType_Commit {
   131  				counts.ClosureTxn.Commit++
   132  			} else if o.Type == ClosureTxnType_Rollback {
   133  				counts.ClosureTxn.Rollback++
   134  			}
   135  		case *SplitOperation:
   136  			if _, ok := splits[string(o.Key)]; ok {
   137  				counts.Split.SplitAgain++
   138  			} else {
   139  				counts.Split.SplitNew++
   140  			}
   141  			splits[string(o.Key)] = struct{}{}
   142  		case *MergeOperation:
   143  			if _, ok := splits[string(o.Key)]; ok {
   144  				counts.Merge.MergeIsSplit++
   145  			} else {
   146  				counts.Merge.MergeNotSplit++
   147  			}
   148  		case *ChangeReplicasOperation:
   149  			var adds, removes int
   150  			for _, change := range o.Changes {
   151  				switch change.ChangeType {
   152  				case roachpb.ADD_REPLICA:
   153  					adds++
   154  				case roachpb.REMOVE_REPLICA:
   155  					removes++
   156  				}
   157  			}
   158  			if adds == 1 && removes == 0 {
   159  				counts.ChangeReplicas.AddReplica++
   160  			} else if adds == 0 && removes == 1 {
   161  				counts.ChangeReplicas.RemoveReplica++
   162  			} else if adds == 1 && removes == 1 {
   163  				counts.ChangeReplicas.AtomicSwapReplica++
   164  			}
   165  		}
   166  		updateKeys(step.Op)
   167  
   168  		// TODO(dan): Make sure the proportions match the requested ones to within
   169  		// some bounds.
   170  		if trueForEachIntField(&counts, func(count int) bool { return count >= minEachType }) {
   171  			break
   172  		}
   173  	}
   174  }