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 }