github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/gc/gc_iterator_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 gc 12 13 import ( 14 "fmt" 15 "math/rand" 16 "testing" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/keys" 20 "github.com/cockroachdb/cockroach/pkg/roachpb" 21 "github.com/cockroachdb/cockroach/pkg/storage" 22 "github.com/cockroachdb/cockroach/pkg/util/hlc" 23 "github.com/cockroachdb/cockroach/pkg/util/uuid" 24 "github.com/stretchr/testify/require" 25 ) 26 27 // TestGCIterator exercises the GC iterator by writing data to the underlying 28 // engine and then validating the state of the iterator as it iterates that 29 // data. 30 func TestGCIterator(t *testing.T) { 31 // dataItem represents a version in the storage engine and optionally a 32 // corresponding transaction which will make the MVCCKeyValue an intent. 33 type dataItem struct { 34 storage.MVCCKeyValue 35 txn *roachpb.Transaction 36 } 37 // makeDataItem is a shorthand to construct dataItems. 38 makeDataItem := func(k roachpb.Key, val []byte, ts int64, txn *roachpb.Transaction) dataItem { 39 return dataItem{ 40 MVCCKeyValue: storage.MVCCKeyValue{ 41 Key: storage.MVCCKey{ 42 Key: k, 43 Timestamp: hlc.Timestamp{WallTime: ts * time.Nanosecond.Nanoseconds()}, 44 }, 45 Value: val, 46 }, 47 txn: txn, 48 } 49 } 50 // makeLiteralDistribution adapts dataItems for use with the data distribution 51 // infrastructure. 52 makeLiteralDataDistribution := func(items ...dataItem) dataDistribution { 53 return func() (storage.MVCCKeyValue, *roachpb.Transaction, bool) { 54 if len(items) == 0 { 55 return storage.MVCCKeyValue{}, nil, false 56 } 57 item := items[0] 58 defer func() { items = items[1:] }() 59 return item.MVCCKeyValue, item.txn, true 60 } 61 } 62 // stateExpectations are expectations about the state of the iterator. 63 type stateExpectations struct { 64 cur, next, afterNext int 65 isNewest bool 66 isIntent bool 67 isNotValue bool 68 } 69 // notation to mark that an iterator state element as either nil or metadata. 70 const ( 71 isNil = -1 72 isMD = -2 73 ) 74 // exp is a shorthand to construct state expectations. 75 exp := func(cur, next, afterNext int, isNewest, isIntent, isNotValue bool) stateExpectations { 76 return stateExpectations{ 77 cur: cur, next: next, afterNext: afterNext, 78 isNewest: isNewest, 79 isIntent: isIntent, 80 isNotValue: isNotValue, 81 } 82 } 83 vals := uniformValueDistribution(3, 5, 0, rand.New(rand.NewSource(1))) 84 tablePrefix := keys.SystemSQLCodec.TablePrefix(42) 85 desc := roachpb.RangeDescriptor{StartKey: roachpb.RKey(tablePrefix), EndKey: roachpb.RKey(tablePrefix.PrefixEnd())} 86 keyA := append(tablePrefix[0:len(tablePrefix):len(tablePrefix)], 'a') 87 keyB := append(tablePrefix[0:len(tablePrefix):len(tablePrefix)], 'b') 88 keyC := append(tablePrefix[0:len(tablePrefix):len(tablePrefix)], 'c') 89 makeTxn := func() *roachpb.Transaction { 90 txn := roachpb.Transaction{} 91 txn.Key = keyA 92 txn.ID = uuid.MakeV4() 93 txn.Status = roachpb.PENDING 94 return &txn 95 } 96 97 type testCase struct { 98 name string 99 data []dataItem 100 expectations []stateExpectations 101 } 102 // checkExpectations tests whether the state of the iterator matches the 103 // expectation. 104 checkExpectations := func( 105 t *testing.T, data []dataItem, ex stateExpectations, s gcIteratorState, 106 ) { 107 check := func(ex int, kv *storage.MVCCKeyValue) { 108 switch { 109 case ex >= 0: 110 require.EqualValues(t, &data[ex].MVCCKeyValue, kv) 111 case ex == isNil: 112 require.Nil(t, kv) 113 case ex == isMD: 114 require.False(t, kv.Key.IsValue()) 115 } 116 } 117 check(ex.cur, s.cur) 118 check(ex.next, s.next) 119 check(ex.afterNext, s.afterNext) 120 require.Equal(t, ex.isNewest, s.curIsNewest()) 121 require.Equal(t, ex.isIntent, s.curIsIntent()) 122 } 123 makeTest := func(tc testCase) func(t *testing.T) { 124 return func(t *testing.T) { 125 eng := storage.NewDefaultInMem() 126 defer eng.Close() 127 ds := makeLiteralDataDistribution(tc.data...) 128 ds.setupTest(t, eng, desc) 129 snap := eng.NewSnapshot() 130 defer snap.Close() 131 it := makeGCIterator(&desc, snap) 132 defer it.close() 133 expectations := tc.expectations 134 for i, ex := range expectations { 135 t.Run(fmt.Sprint(i), func(t *testing.T) { 136 s, ok := it.state() 137 require.True(t, ok) 138 checkExpectations(t, tc.data, ex, s) 139 }) 140 it.step() 141 } 142 } 143 } 144 di := makeDataItem // shorthand for convenient notation 145 for _, tc := range []testCase{ 146 { 147 name: "basic", 148 data: []dataItem{ 149 di(keyA, vals(), 2, nil), 150 di(keyA, vals(), 11, nil), 151 di(keyA, vals(), 14, nil), 152 di(keyB, vals(), 3, nil), 153 di(keyC, vals(), 7, makeTxn()), 154 }, 155 expectations: []stateExpectations{ 156 exp(4, isMD, isNil, false, true, false), 157 exp(isMD, isNil, isNil, false, false, true), 158 exp(3, isNil, isNil, true, false, false), 159 exp(0, 1, 2, false, false, false), 160 exp(1, 2, isNil, false, false, false), 161 exp(2, isNil, isNil, true, false, false), 162 }, 163 }, 164 } { 165 t.Run(tc.name, makeTest(tc)) 166 } 167 }