github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/committed/compare_iterator_test.go (about)

     1  package committed_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/go-test/deep"
     9  	"github.com/treeverse/lakefs/pkg/graveler"
    10  	"github.com/treeverse/lakefs/pkg/graveler/committed"
    11  	"github.com/treeverse/lakefs/pkg/graveler/testutil"
    12  )
    13  
    14  var baseKeyToIdentity = map[string]string{"k1": "i1", "k2": "i2", "k3": "i3", "k4": "i4", "k6": "i6"}
    15  
    16  const (
    17  	added    = graveler.DiffTypeAdded
    18  	removed  = graveler.DiffTypeRemoved
    19  	changed  = graveler.DiffTypeChanged
    20  	conflict = graveler.DiffTypeConflict
    21  )
    22  
    23  func testMergeNewDiff(typ graveler.DiffType, key string, newIdentity string, oldIdentity string) graveler.Diff {
    24  	return graveler.Diff{
    25  		Type:         typ,
    26  		Key:          graveler.Key(key),
    27  		Value:        &graveler.Value{Identity: []byte(newIdentity)},
    28  		LeftIdentity: []byte(oldIdentity),
    29  	}
    30  }
    31  
    32  func TestCompare(t *testing.T) {
    33  	tests := map[string]struct {
    34  		baseKeys           []string
    35  		diffs              []graveler.Diff
    36  		expectedKeys       []string
    37  		expectedIdentities []string
    38  		expectedDiffTypes  []graveler.DiffType
    39  	}{
    40  		"added on right": {
    41  			baseKeys:           []string{"k1", "k2"},
    42  			diffs:              []graveler.Diff{testMergeNewDiff(added, "k3", "i3", "")},
    43  			expectedKeys:       []string{"k3"},
    44  			expectedIdentities: []string{"i3"},
    45  			expectedDiffTypes:  []graveler.DiffType{added},
    46  		},
    47  		"changed on right": {
    48  			baseKeys:           []string{"k1", "k2"},
    49  			diffs:              []graveler.Diff{testMergeNewDiff(changed, "k2", "i2a", "i2")},
    50  			expectedKeys:       []string{"k2"},
    51  			expectedIdentities: []string{"i2a"},
    52  			expectedDiffTypes:  []graveler.DiffType{changed},
    53  		},
    54  		"deleted on right": {
    55  			baseKeys:           []string{"k1", "k2"},
    56  			diffs:              []graveler.Diff{testMergeNewDiff(removed, "k2", "i2", "i2")},
    57  			expectedKeys:       []string{"k2"},
    58  			expectedIdentities: []string{"i2"},
    59  			expectedDiffTypes:  []graveler.DiffType{removed},
    60  		},
    61  		"added on left": {
    62  			baseKeys:           []string{"k1"},
    63  			diffs:              []graveler.Diff{testMergeNewDiff(removed, "k2", "i2", "i2")},
    64  			expectedIdentities: nil,
    65  		},
    66  		"removed on left": {
    67  			baseKeys:           []string{"k1", "k2"},
    68  			diffs:              []graveler.Diff{testMergeNewDiff(added, "k2", "i2", "")},
    69  			expectedIdentities: nil,
    70  		},
    71  		"changed on left": {
    72  			baseKeys:           []string{"k1", "k2"},
    73  			diffs:              []graveler.Diff{testMergeNewDiff(changed, "k2", "i2", "i2a")},
    74  			expectedIdentities: nil,
    75  		},
    76  		"changed on both": {
    77  			baseKeys:           []string{"k1", "k2"},
    78  			diffs:              []graveler.Diff{testMergeNewDiff(changed, "k2", "i2b", "i2a")},
    79  			expectedKeys:       []string{"k2"},
    80  			expectedIdentities: []string{"i2b"},
    81  			expectedDiffTypes:  []graveler.DiffType{conflict},
    82  		},
    83  		"changed on left, removed on right": {
    84  			baseKeys:           []string{"k1", "k2"},
    85  			diffs:              []graveler.Diff{testMergeNewDiff(removed, "k2", "i2a", "i2a")},
    86  			expectedKeys:       []string{"k2"},
    87  			expectedIdentities: []string{"i2a"},
    88  			expectedDiffTypes:  []graveler.DiffType{conflict},
    89  		},
    90  		"removed on left, changed on right": {
    91  			baseKeys:           []string{"k1", "k2"},
    92  			diffs:              []graveler.Diff{testMergeNewDiff(added, "k2", "i2a", "")},
    93  			expectedIdentities: []string{"i2a"},
    94  			expectedKeys:       []string{"k2"},
    95  			expectedDiffTypes:  []graveler.DiffType{conflict},
    96  		},
    97  		"added on both with different identities": {
    98  			baseKeys:           []string{"k1"},
    99  			diffs:              []graveler.Diff{testMergeNewDiff(changed, "k2", "i2a", "i2b")},
   100  			expectedIdentities: []string{"i2a"},
   101  			expectedKeys:       []string{"k2"},
   102  			expectedDiffTypes:  []graveler.DiffType{conflict},
   103  		},
   104  		"multiple add and removes": {
   105  			baseKeys: []string{"k1", "k3"},
   106  			diffs: []graveler.Diff{
   107  				testMergeNewDiff(removed, "k1", "i1", "i1"),
   108  				testMergeNewDiff(added, "k2", "i2", ""),
   109  				testMergeNewDiff(removed, "k3", "i3", "i3"),
   110  				testMergeNewDiff(added, "k4", "i4", ""),
   111  			},
   112  			expectedKeys:       []string{"k1", "k2", "k3", "k4"},
   113  			expectedIdentities: []string{"i1", "i2", "i3", "i4"},
   114  			expectedDiffTypes:  []graveler.DiffType{removed, added, removed, added},
   115  		},
   116  		"changes on each side": {
   117  			baseKeys: []string{"k1", "k2", "k3", "k4"},
   118  			diffs: []graveler.Diff{
   119  				testMergeNewDiff(changed, "k1", "i1a", "i1"),
   120  				testMergeNewDiff(changed, "k2", "i2", "i2a"),
   121  				testMergeNewDiff(changed, "k3", "i3a", "i3"),
   122  				testMergeNewDiff(changed, "k4", "i4", "i4a"),
   123  			},
   124  			expectedKeys:       []string{"k1", "k3"},
   125  			expectedIdentities: []string{"i1a", "i3a"},
   126  			expectedDiffTypes:  []graveler.DiffType{changed, changed},
   127  		},
   128  	}
   129  
   130  	for name, tst := range tests {
   131  		t.Run(name, func(t *testing.T) {
   132  			diffIt := testutil.NewDiffIter(tst.diffs)
   133  			defer diffIt.Close()
   134  			base := makeBaseIterator(tst.baseKeys)
   135  
   136  			ctx := context.Background()
   137  			it := committed.NewCompareValueIterator(ctx, committed.NewDiffIteratorWrapper(diffIt), base)
   138  			defer it.Close()
   139  
   140  			var gotValues, gotKeys []string
   141  			var gotDiffTypes []graveler.DiffType
   142  			idx := 0
   143  			for it.Next() {
   144  				idx++
   145  				gotKeys = append(gotKeys, string(it.Value().Key))
   146  				gotValues = append(gotValues, string(it.Value().Value.Identity))
   147  				gotDiffTypes = append(gotDiffTypes, it.Value().Type)
   148  			}
   149  			if it.Err() != nil {
   150  				t.Fatalf("got unexpected error: %v", it.Err())
   151  			}
   152  			if diff := deep.Equal(tst.expectedKeys, gotKeys); diff != nil {
   153  				t.Fatalf("got unexpected keys from merge iterator. diff=%s", diff)
   154  			}
   155  			if diff := deep.Equal(tst.expectedIdentities, gotValues); diff != nil {
   156  				t.Fatalf("got unexpected values from merge iterator. diff=%s", diff)
   157  			}
   158  			if diff := deep.Equal(tst.expectedDiffTypes, gotDiffTypes); diff != nil {
   159  				t.Fatalf("got unexpected diff types from merge iterator. diff=%s", diff)
   160  			}
   161  		})
   162  	}
   163  }
   164  
   165  func TestCompareSeek(t *testing.T) {
   166  	diffs := []graveler.Diff{
   167  		testMergeNewDiff(added, "k1", "i1", ""),
   168  		testMergeNewDiff(removed, "k2", "i2", "i2"),
   169  		testMergeNewDiff(changed, "k3", "i3a", "i3"),
   170  		testMergeNewDiff(added, "k4", "i4", ""),
   171  		testMergeNewDiff(removed, "k5", "i5", "i5"),
   172  		testMergeNewDiff(changed, "k6", "i6", "i6a"),
   173  		testMergeNewDiff(added, "k7", "i7", ""),
   174  		testMergeNewDiff(removed, "k8", "i8", "i8"),
   175  		testMergeNewDiff(changed, "k9", "i9a", "i9"),
   176  	}
   177  	diffIt := testutil.NewDiffIter(diffs)
   178  	baseKeys := []string{"k2", "k3", "k4", "k6"}
   179  	base := makeBaseIterator(baseKeys)
   180  	ctx := context.Background()
   181  	it := committed.NewCompareValueIterator(ctx, committed.NewDiffIteratorWrapper(diffIt), base)
   182  	// expected diffs, +k1, -k2, Chng:k3,+k7, Conf:k9,
   183  	defer it.Close()
   184  	tests := []struct {
   185  		seekTo             string
   186  		expectedKeys       []string
   187  		expectedIdentities []string
   188  		expectedDiffTypes  []graveler.DiffType
   189  	}{
   190  		{
   191  			seekTo:             "k1",
   192  			expectedKeys:       []string{"k1", "k2", "k3", "k7", "k9"},
   193  			expectedIdentities: []string{"i1", "i2", "i3a", "i7", "i9a"},
   194  			expectedDiffTypes:  []graveler.DiffType{added, removed, changed, added, conflict},
   195  		},
   196  		{
   197  			seekTo:             "k2",
   198  			expectedKeys:       []string{"k2", "k3", "k7", "k9"},
   199  			expectedIdentities: []string{"i2", "i3a", "i7", "i9a"},
   200  			expectedDiffTypes:  []graveler.DiffType{removed, changed, added, conflict},
   201  		},
   202  
   203  		{
   204  			seekTo:             "k3",
   205  			expectedKeys:       []string{"k3", "k7", "k9"},
   206  			expectedIdentities: []string{"i3a", "i7", "i9a"},
   207  			expectedDiffTypes:  []graveler.DiffType{changed, added, conflict},
   208  		},
   209  
   210  		{
   211  			seekTo:             "k4",
   212  			expectedKeys:       []string{"k7", "k9"},
   213  			expectedIdentities: []string{"i7", "i9a"},
   214  			expectedDiffTypes:  []graveler.DiffType{added, conflict},
   215  		},
   216  		{
   217  			seekTo:             "k8",
   218  			expectedKeys:       []string{"k9"},
   219  			expectedIdentities: []string{"i9a"},
   220  			expectedDiffTypes:  []graveler.DiffType{conflict},
   221  		},
   222  		{
   223  			seekTo:             "k9",
   224  			expectedKeys:       []string{"k9"},
   225  			expectedIdentities: []string{"i9a"},
   226  			expectedDiffTypes:  []graveler.DiffType{conflict},
   227  		},
   228  		{
   229  			seekTo:             "k99",
   230  			expectedKeys:       nil,
   231  			expectedIdentities: nil,
   232  		},
   233  	}
   234  	for _, tst := range tests {
   235  		t.Run(fmt.Sprintf("seek to %s", tst.seekTo), func(t *testing.T) {
   236  			it.SeekGE([]byte(tst.seekTo))
   237  			if it.Value() != nil {
   238  				t.Fatalf("value expected to be nil after SeekGE. got=%v", it.Value())
   239  			}
   240  			idx := 0
   241  			var gotValues, gotKeys []string
   242  			var gotDiffTypes []graveler.DiffType
   243  			for it.Next() {
   244  				idx++
   245  				gotKeys = append(gotKeys, string(it.Value().Key))
   246  				gotDiffTypes = append(gotDiffTypes, it.Value().Type)
   247  				gotValues = append(gotValues, string(it.Value().Value.Identity))
   248  			}
   249  			if it.Err() != nil {
   250  				t.Fatalf("got unexpected error: %v", it.Err())
   251  			}
   252  			if diff := deep.Equal(tst.expectedKeys, gotKeys); diff != nil {
   253  				t.Fatalf("got unexpected keys from compare iterator. diff=%s", diff)
   254  			}
   255  			if diff := deep.Equal(tst.expectedIdentities, gotValues); diff != nil {
   256  				t.Fatalf("got unexpected values from compare iterator. diff=%s", diff)
   257  			}
   258  			if diff := deep.Equal(tst.expectedDiffTypes, gotDiffTypes); diff != nil {
   259  				t.Fatalf("got unexpected diff types from compare iterator. diff=%s", diff)
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func makeBaseIterator(keys []string) *testutil.FakeIterator {
   266  	base := testutil.NewFakeIterator()
   267  	if len(keys) == 0 {
   268  		return base
   269  	}
   270  	var baseRecords []*graveler.ValueRecord
   271  	for _, key := range keys {
   272  		baseRecords = append(baseRecords, &graveler.ValueRecord{
   273  			Key:   []byte(key),
   274  			Value: &graveler.Value{Identity: []byte(baseKeyToIdentity[key])},
   275  		})
   276  	}
   277  	base.AddRange(&committed.Range{
   278  		ID:     "range",
   279  		MinKey: []byte(keys[0]),
   280  	})
   281  	base.AddValueRecords(baseRecords...)
   282  	return base
   283  }