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  }