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

     1  package committed_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/gob"
     7  	"errors"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/go-test/deep"
    12  	"github.com/treeverse/lakefs/pkg/graveler"
    13  	"github.com/treeverse/lakefs/pkg/graveler/committed"
    14  	"github.com/treeverse/lakefs/pkg/graveler/testutil"
    15  )
    16  
    17  type diffTestRange struct {
    18  	MinKey string
    19  	MaxKey string
    20  	Count  int64
    21  }
    22  
    23  func newDiffTestRange(p *committed.RangeDiff) *diffTestRange {
    24  	if p == nil || p.Range == nil {
    25  		return nil
    26  	}
    27  	return &diffTestRange{string(p.Range.MinKey), string(p.Range.MaxKey), p.Range.Count}
    28  }
    29  
    30  func TestDiff(t *testing.T) {
    31  	const (
    32  		added   = graveler.DiffTypeAdded
    33  		removed = graveler.DiffTypeRemoved
    34  		changed = graveler.DiffTypeChanged
    35  	)
    36  	tests := map[string]struct {
    37  		leftKeys                  [][]string
    38  		leftIdentities            [][]string
    39  		rightKeys                 [][]string
    40  		rightIdentities           [][]string
    41  		expectedDiffKeys          []string
    42  		expectedRanges            []*diffTestRange
    43  		expectedDiffTypes         []graveler.DiffType
    44  		expectedDiffIdentities    []string
    45  		expectedLeftReadsByRange  []int
    46  		expectedRightReadsByRange []int
    47  	}{
    48  		"empty diff": {
    49  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3"}},
    50  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3"}},
    51  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3"}},
    52  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3"}},
    53  			expectedDiffKeys:          []string{},
    54  			expectedRanges:            []*diffTestRange{},
    55  			expectedLeftReadsByRange:  []int{0, 0},
    56  			expectedRightReadsByRange: []int{0, 0},
    57  		},
    58  		"added in existing rng": {
    59  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3"}},
    60  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3"}},
    61  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3", "k4"}},
    62  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3", "i4"}},
    63  			expectedDiffKeys:          []string{"k4"},
    64  			expectedRanges:            []*diffTestRange{nil},
    65  			expectedDiffTypes:         []graveler.DiffType{added},
    66  			expectedDiffIdentities:    []string{"i4"},
    67  			expectedLeftReadsByRange:  []int{0, 1},
    68  			expectedRightReadsByRange: []int{0, 2},
    69  		},
    70  		"removed from existing rng": {
    71  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3", "k4"}},
    72  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3", "i4"}},
    73  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3"}},
    74  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3"}},
    75  			expectedDiffKeys:          []string{"k4"},
    76  			expectedRanges:            []*diffTestRange{nil},
    77  			expectedDiffTypes:         []graveler.DiffType{removed},
    78  			expectedDiffIdentities:    []string{"i4"},
    79  			expectedLeftReadsByRange:  []int{0, 2},
    80  			expectedRightReadsByRange: []int{0, 1},
    81  		},
    82  		"added and removed": {
    83  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3", "k5"}},
    84  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3", "i5"}},
    85  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3", "k4"}},
    86  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3", "i4"}},
    87  			expectedDiffKeys:          []string{"k4", "k5"},
    88  			expectedRanges:            []*diffTestRange{nil, nil},
    89  			expectedDiffTypes:         []graveler.DiffType{added, removed},
    90  			expectedDiffIdentities:    []string{"i4", "i5"},
    91  			expectedLeftReadsByRange:  []int{0, 2},
    92  			expectedRightReadsByRange: []int{0, 2},
    93  		},
    94  		"change in existing rng": {
    95  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3"}},
    96  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3"}},
    97  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3"}},
    98  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3a"}},
    99  			expectedDiffKeys:          []string{"k3"},
   100  			expectedRanges:            []*diffTestRange{{"k3", "k3", 1}, nil},
   101  			expectedDiffTypes:         []graveler.DiffType{changed},
   102  			expectedDiffIdentities:    []string{"i3a"},
   103  			expectedLeftReadsByRange:  []int{0, 1},
   104  			expectedRightReadsByRange: []int{0, 1},
   105  		},
   106  		"ranges were split": {
   107  			leftKeys:                  [][]string{{"k1", "k2", "k3"}},
   108  			leftIdentities:            [][]string{{"i1", "i2", "i3"}},
   109  			rightKeys:                 [][]string{{"k3", "k4"}, {"k5", "k6"}},
   110  			rightIdentities:           [][]string{{"i3a", "i4"}, {"i5", "i6"}},
   111  			expectedDiffKeys:          []string{"k1", "k2", "k3", "k4", "k5", "k6"},
   112  			expectedRanges:            []*diffTestRange{nil, nil, nil, nil, {"k5", "k6", 2}, {"k5", "k6", 2}, {"k5", "k6", 2}},
   113  			expectedDiffTypes:         []graveler.DiffType{removed, removed, changed, added, added, added},
   114  			expectedDiffIdentities:    []string{"i1", "i2", "i3a", "i4", "i5", "i6"},
   115  			expectedLeftReadsByRange:  []int{3},
   116  			expectedRightReadsByRange: []int{2, 2},
   117  		},
   118  		"diff between empty iterators": {
   119  			expectedDiffKeys: []string{},
   120  			expectedRanges:   []*diffTestRange{},
   121  		},
   122  		"added on empty": {
   123  			leftKeys:                  [][]string{},
   124  			leftIdentities:            [][]string{},
   125  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3"}},
   126  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3"}},
   127  			expectedDiffKeys:          []string{"k1", "k2", "k3"},
   128  			expectedRanges:            []*diffTestRange{{"k1", "k2", 2}, {"k1", "k2", 2}, {"k1", "k2", 2}, {"k3", "k3", 1}, {"k3", "k3", 1}},
   129  			expectedDiffTypes:         []graveler.DiffType{added, added, added},
   130  			expectedDiffIdentities:    []string{"i1", "i2", "i3"},
   131  			expectedLeftReadsByRange:  nil,
   132  			expectedRightReadsByRange: []int{2, 1},
   133  		},
   134  		"whole rng was replaced": {
   135  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3", "k4", "k5", "k6"}},
   136  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3", "i4", "i5", "i6"}},
   137  			rightKeys:                 [][]string{{"k3", "k4"}, {"k5", "k6", "k7"}},
   138  			rightIdentities:           [][]string{{"i3", "i4"}, {"i5", "i6", "i7"}},
   139  			expectedDiffKeys:          []string{"k1", "k2", "k7"},
   140  			expectedRanges:            []*diffTestRange{{"k1", "k2", 2}, {"k1", "k2", 2}, {"k1", "k2", 2}, nil},
   141  			expectedDiffTypes:         []graveler.DiffType{removed, removed, added},
   142  			expectedDiffIdentities:    []string{"i1", "i2", "i7"},
   143  			expectedLeftReadsByRange:  []int{2, 4},
   144  			expectedRightReadsByRange: []int{2, 3},
   145  		},
   146  		"added in beginning of rng": {
   147  			leftKeys:                  [][]string{{"k3", "k4", "k5"}},
   148  			leftIdentities:            [][]string{{"i3", "i4", "i5"}},
   149  			rightKeys:                 [][]string{{"k1", "k2", "k3", "k4", "k5"}},
   150  			rightIdentities:           [][]string{{"i1", "i2", "i3", "i4", "i5"}},
   151  			expectedDiffKeys:          []string{"k1", "k2"},
   152  			expectedRanges:            []*diffTestRange{nil, nil},
   153  			expectedDiffTypes:         []graveler.DiffType{added, added},
   154  			expectedDiffIdentities:    []string{"i1", "i2"},
   155  			expectedLeftReadsByRange:  []int{3},
   156  			expectedRightReadsByRange: []int{5},
   157  		},
   158  		"small ranges removed": {
   159  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3"}, {"k4"}, {"k5"}, {"k6", "k7"}},
   160  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3"}, {"i4"}, {"i5"}, {"i6", "i7"}},
   161  			rightKeys:                 [][]string{{"k1", "k2"}, {"k6", "k7"}},
   162  			rightIdentities:           [][]string{{"i1", "i2"}, {"i6", "i7"}},
   163  			expectedDiffKeys:          []string{"k3", "k4", "k5"},
   164  			expectedRanges:            []*diffTestRange{{"k3", "k3", 1}, {"k3", "k3", 1}, {"k4", "k4", 1}, {"k4", "k4", 1}, {"k5", "k5", 1}, {"k5", "k5", 1}},
   165  			expectedDiffTypes:         []graveler.DiffType{removed, removed, removed},
   166  			expectedDiffIdentities:    []string{"i3", "i4", "i5"},
   167  			expectedLeftReadsByRange:  []int{0, 1, 1, 1, 0},
   168  			expectedRightReadsByRange: []int{0, 0},
   169  		},
   170  		"small ranges merged": {
   171  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3"}, {"k4"}, {"k5"}, {"k6", "k7"}},
   172  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3"}, {"i4"}, {"i5"}, {"i6", "i7"}},
   173  			rightKeys:                 [][]string{{"k1", "k2"}, {"k4", "k5"}},
   174  			rightIdentities:           [][]string{{"i1", "i2"}, {"i4", "i5"}},
   175  			expectedDiffKeys:          []string{"k3", "k6", "k7"},
   176  			expectedRanges:            []*diffTestRange{{"k3", "k3", 1}, {"k3", "k3", 1}, {"k6", "k7", 2}, {"k6", "k7", 2}, {"k6", "k7", 2}},
   177  			expectedDiffTypes:         []graveler.DiffType{removed, removed, removed},
   178  			expectedDiffIdentities:    []string{"i3", "i6", "i7"},
   179  			expectedLeftReadsByRange:  []int{0, 1, 1, 1, 2},
   180  			expectedRightReadsByRange: []int{0, 2},
   181  		},
   182  		"empty ranges": {
   183  			leftKeys:                  [][]string{{"k1", "k2"}, {}, {}, {}, {}, {"k3", "k4"}},
   184  			leftIdentities:            [][]string{{"i1", "i2"}, {}, {}, {}, {}, {"i3", "i4"}},
   185  			rightKeys:                 [][]string{{"k1", "k2"}, {}, {}, {"k3", "k4"}},
   186  			rightIdentities:           [][]string{{"i1", "i2"}, {}, {}, {"i3", "i4"}},
   187  			expectedDiffKeys:          []string{},
   188  			expectedRanges:            []*diffTestRange{{"", "", 0}, {"", "", 0}},
   189  			expectedDiffTypes:         []graveler.DiffType{},
   190  			expectedDiffIdentities:    []string{},
   191  			expectedLeftReadsByRange:  []int{0, 0, 0, 0, 0, 0},
   192  			expectedRightReadsByRange: []int{0, 0, 0, 0},
   193  		},
   194  		"rng added in the middle": {
   195  			leftKeys:                  [][]string{{"k1", "k2"}, {"k5", "k6"}},
   196  			leftIdentities:            [][]string{{"i1", "i2"}, {"i5", "i6"}},
   197  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3", "k4"}, {"k5", "k6"}},
   198  			rightIdentities:           [][]string{{"i1", "i2"}, {"i3", "i4"}, {"i5", "i6"}},
   199  			expectedDiffKeys:          []string{"k3", "k4"},
   200  			expectedRanges:            []*diffTestRange{{"k3", "k4", 2}, {"k3", "k4", 2}, {"k3", "k4", 2}},
   201  			expectedDiffTypes:         []graveler.DiffType{added, added},
   202  			expectedDiffIdentities:    []string{"i3", "i4"},
   203  			expectedLeftReadsByRange:  []int{0, 0},
   204  			expectedRightReadsByRange: []int{0, 2, 0},
   205  		},
   206  		"identical ranges in the middle": {
   207  			leftKeys:                  [][]string{{"k1", "k2"}, {"k3", "k4"}, {"k5", "k6"}},
   208  			leftIdentities:            [][]string{{"i1", "i2"}, {"i3", "i4"}, {"i5", "i6"}},
   209  			rightKeys:                 [][]string{{"k1", "k2"}, {"k3", "k4"}, {"k5", "k6"}},
   210  			rightIdentities:           [][]string{{"i1", "i2a"}, {"i3", "i4"}, {"i5", "i6a"}},
   211  			expectedDiffKeys:          []string{"k2", "k6"},
   212  			expectedRanges:            []*diffTestRange{{"k1", "k2", 2}, nil, {"k5", "k6", 2}, nil},
   213  			expectedDiffTypes:         []graveler.DiffType{changed, changed},
   214  			expectedDiffIdentities:    []string{"i2a", "i6a"},
   215  			expectedLeftReadsByRange:  []int{2, 0, 2},
   216  			expectedRightReadsByRange: []int{2, 0, 2},
   217  		},
   218  	}
   219  	for name, tst := range tests {
   220  		t.Run(name, func(t *testing.T) {
   221  			fakeLeft := newFakeMetaRangeIterator(tst.leftKeys, tst.leftIdentities)
   222  			fakeRight := newFakeMetaRangeIterator(tst.rightKeys, tst.rightIdentities)
   223  			ctx := context.Background()
   224  			it := committed.NewDiffIterator(ctx, fakeLeft, fakeRight)
   225  			defer it.Close()
   226  			var diffs []*graveler.Diff
   227  			actualDiffKeys := make([]string, 0)
   228  			actualDiffRanges := make([]*diffTestRange, 0)
   229  			for it.Next() {
   230  				diff, rng := it.Value()
   231  				actualDiffRanges = append(actualDiffRanges, newDiffTestRange(rng))
   232  				if diff != nil {
   233  					actualDiffKeys = append(actualDiffKeys, string(diff.Key))
   234  					diffs = append(diffs, diff)
   235  				}
   236  			}
   237  			if it.Err() != nil {
   238  				t.Fatalf("got unexpected error: %v", it.Err())
   239  			}
   240  			if diff := deep.Equal(tst.expectedDiffKeys, actualDiffKeys); diff != nil {
   241  				t.Fatalf("keys in diff different than expected. diff=%s", diff)
   242  			}
   243  			if diff := deep.Equal(tst.expectedRanges, actualDiffRanges); diff != nil {
   244  				t.Fatalf("ranges in diff different than expected. diff=%s", diff)
   245  			}
   246  			for i, d := range diffs {
   247  				if d.Type != tst.expectedDiffTypes[i] {
   248  					t.Fatalf("unexpected key in diff index %d. expected=%s, got=%s", i, tst.expectedDiffKeys[i], string(d.Key))
   249  				}
   250  				if string(d.Value.Identity) != tst.expectedDiffIdentities[i] {
   251  					t.Fatalf("unexpected identity in diff index %d. expected=%s, got=%s", i, tst.expectedDiffIdentities[i], string(d.Value.Identity))
   252  				}
   253  			}
   254  			if diff := deep.Equal(tst.expectedLeftReadsByRange, fakeLeft.ReadsByRange()); diff != nil {
   255  				t.Fatalf("unexpected number of reads on left ranges. diff=%s", diff)
   256  			}
   257  			if diff := deep.Equal(tst.expectedRightReadsByRange, fakeRight.ReadsByRange()); diff != nil {
   258  				t.Fatalf("unexpected number of reads on right ranges. diff=%s", diff)
   259  			}
   260  		})
   261  	}
   262  }
   263  
   264  func TestDiffCancelContext(t *testing.T) {
   265  	left := newFakeMetaRangeIterator([][]string{{"k1", "k2"}}, [][]string{{"v1", "v2"}})
   266  	right := newFakeMetaRangeIterator([][]string{{"k1", "k2"}}, [][]string{{"v1", "v2"}})
   267  	ctx, cancel := context.WithCancel(context.Background())
   268  	cancel()
   269  	it := committed.NewDiffValueIterator(ctx, left, right)
   270  	defer it.Close()
   271  	if it.Next() {
   272  		t.Fatal("Next() should return false")
   273  	}
   274  	if err := it.Err(); !errors.Is(err, context.Canceled) {
   275  		t.Fatalf("Err() returned %v, should return context.Canceled", err)
   276  	}
   277  }
   278  
   279  // TODO(Guys): add test for range changed
   280  func TestNextRange(t *testing.T) {
   281  	ctx := context.Background()
   282  	it := committed.NewDiffIterator(ctx,
   283  		newFakeMetaRangeIterator([][]string{{"k1", "k2"}, {"k3", "k4"}}, [][]string{{"i1", "i2"}, {"i3", "i4"}}),
   284  		newFakeMetaRangeIterator([][]string{{"k3", "k4", "k5"}, {"k6", "k7"}}, [][]string{{"i3a", "i4a", "i5a"}, {"i6", "i7"}}))
   285  
   286  	t.Run("next range - in middle of range", func(t *testing.T) {
   287  		if !it.Next() { // move to range k1-k2
   288  			t.Fatal("expected iterator to have value")
   289  		}
   290  		record, _ := it.Value()
   291  		if record != nil {
   292  			t.Errorf("expected record to be nil got %v", record)
   293  		}
   294  		if !it.Next() { // move to k1
   295  			t.Fatalf("expected it.Next() to return true (err %v)", it.Err())
   296  		}
   297  		record, _ = it.Value()
   298  		if record == nil || string(record.Key) != "k1" {
   299  			t.Errorf("expected record with key=k1, got record %v", record)
   300  		}
   301  		if !it.NextRange() { // move to k3 (moves to end of current range, but can't start a new range because k3 is part of two different ranges)
   302  			t.Fatalf("expected it.NextRange() to return true (err %v)", it.Err())
   303  		}
   304  		record, rng := it.Value()
   305  		if rng != nil {
   306  			t.Errorf("expected range to be nil got range %v", rng)
   307  		}
   308  		if record == nil || string(record.Key) != "k3" {
   309  			t.Errorf("expected record with key=k3, got record %v", record)
   310  		}
   311  		if !it.Next() { // move to k4
   312  			t.Fatalf("expected it.Next() to return true (err %v)", it.Err())
   313  		}
   314  		if !it.Next() { // move to k5
   315  			t.Fatalf("expected it.Next() to return true (err %v)", it.Err())
   316  		}
   317  		if !it.Next() { // move to range k6-k7
   318  			t.Fatalf("expected it.Next() to return true (err %v)", it.Err())
   319  		}
   320  		record, _ = it.Value()
   321  		if record != nil {
   322  			t.Errorf("expected record to be nil got record %v", record)
   323  		}
   324  		if !it.Next() { // move to k6
   325  			t.Fatalf("expected it.Next() to return true (err %v)", it.Err())
   326  		}
   327  		record, _ = it.Value()
   328  		if record == nil || string(record.Key) != "k6" {
   329  			t.Errorf("expected record with key=k6, got record %v", record)
   330  		}
   331  		if it.NextRange() { // move to end
   332  			t.Fatal("expected it.NextRange() to return false")
   333  		}
   334  		if err := it.Err(); err != nil {
   335  			t.Fatalf("unexpected error:%v", err)
   336  		}
   337  	})
   338  	t.Run("call next range with no range", func(t *testing.T) {
   339  		it.SeekGE(graveler.Key("k3"))
   340  		if !it.Next() {
   341  			t.Fatal("expected iterator to have value")
   342  		}
   343  		record, rng := it.Value()
   344  		if record == nil || string(record.Key) != "k3" {
   345  			t.Fatal("expected record to have a value equal to k3")
   346  		}
   347  		if rng != nil {
   348  			t.Fatal("expected range to not have value")
   349  		}
   350  		if it.NextRange() {
   351  			t.Fatal("expected false from iterator after close")
   352  		}
   353  		if err := it.Err(); !errors.Is(err, committed.ErrNoRange) {
   354  			t.Fatalf("expected to get err=%s, got: %s", committed.ErrNoRange, err)
   355  		}
   356  	})
   357  }
   358  
   359  func TestNextRangeChange(t *testing.T) {
   360  	ctx := context.Background()
   361  	it := committed.NewDiffIterator(ctx,
   362  		newFakeMetaRangeIterator([][]string{{"k1", "k3"}, {"k5", "k6"}}, [][]string{{"i1", "i3"}, {"i5", "i6"}}),
   363  		newFakeMetaRangeIterator([][]string{{"k1", "k2", "k3"}, {"k5", "k6"}}, [][]string{{"i1", "i2", "i3"}, {"i5", "i6"}}))
   364  	if !it.Next() {
   365  		t.Fatal("expected iterator to have value")
   366  	}
   367  	record, rng := it.Value()
   368  	if record != nil {
   369  		t.Errorf("expected record to be nil got record %v", record)
   370  	}
   371  	if rng.Type != changed {
   372  		t.Errorf("expected range diff type to be changed got %v", rng.Type)
   373  	}
   374  	if it.NextRange() {
   375  		t.Fatal("expected next range to return false")
   376  	}
   377  }
   378  
   379  func TestNextErr(t *testing.T) {
   380  	ctx := context.Background()
   381  	it := committed.NewDiffIterator(ctx,
   382  		newFakeMetaRangeIterator([][]string{{"k1", "k2"}}, [][]string{{"i1", "i2"}}),
   383  		newFakeMetaRangeIterator([][]string{{"k1", "k2", "k3"}}, [][]string{{"i1a", "i2a", "i3a"}}))
   384  	if !it.Next() {
   385  		t.Fatalf("unexptected result from it.Next(), expected true, got false with err=%s", it.Err())
   386  	}
   387  	if it.NextRange() {
   388  		val, rng := it.Value()
   389  		t.Fatalf("unexptected result from it.NextRange(), expected false, got true with value=%v , rng=%v", val, rng)
   390  	}
   391  	if err := it.Err(); !errors.Is(err, committed.ErrNoRange) {
   392  		t.Fatalf("expected to get err=%s, got: %s", committed.ErrNoRange, err)
   393  	}
   394  }
   395  
   396  func TestSameBounds(t *testing.T) {
   397  	ctx := context.Background()
   398  	it := committed.NewDiffIterator(ctx,
   399  		newFakeMetaRangeIterator([][]string{{"k1", "k2"}, {"k3", "k4"}}, [][]string{{"i1", "i2"}, {"i3", "i4"}}),
   400  		newFakeMetaRangeIterator([][]string{{"k1", "k2"}, {"k3", "k4"}}, [][]string{{"i1a", "i2a"}, {"i3a", "i4a"}}))
   401  	if !it.Next() {
   402  		t.Fatalf("unexptected result from it.Next(), expected true, got false with err=%s", it.Err())
   403  	}
   404  	if !it.NextRange() {
   405  		t.Fatalf("unexptected result from it.Next(), expected true, got false with err=%s", it.Err())
   406  	}
   407  	val, _ := it.Value()
   408  	if val != nil {
   409  		t.Errorf("unexptected value after it.NextRange(), expected nil, got=%v", val)
   410  	}
   411  	if !it.Next() {
   412  		t.Fatalf("unexptected result from it.Next(), expected true, got false with err=%s", it.Err())
   413  	}
   414  	val, _ = it.Value()
   415  	if val == nil || string(val.Value.Identity) != "i3a" {
   416  		t.Errorf("unexptected value after it.Next(), expected i3a, got=%v", val)
   417  	}
   418  	if err := it.Err(); err != nil {
   419  		t.Fatalf("unexpected error, got: %s", err)
   420  	}
   421  }
   422  
   423  func TestDiffSeek(t *testing.T) {
   424  	const (
   425  		added   = graveler.DiffTypeAdded
   426  		removed = graveler.DiffTypeRemoved
   427  		changed = graveler.DiffTypeChanged
   428  	)
   429  	left := [][]string{{"k1", "k2"}, {"k4", "k5"}, {"k6", "k7"}}
   430  	right := [][]string{{"k1", "k3"}, {"k3a", "k3b"}, {"k4", "k5"}, {"k6", "k7"}}
   431  	leftIdentities := [][]string{{"i1", "i2"}, {"i4", "i5"}, {"i6", "i7"}}
   432  	rightIdentities := [][]string{{"i1", "i3"}, {"i2a", "i2b"}, {"i4", "i5"}, {"i6", "i7a"}}
   433  	diffTypeByKey := map[string]graveler.DiffType{"k2": removed, "k3a": added, "k3b": added, "k3": added, "k7": changed}
   434  	diffIdentityByKey := map[string]string{"k2": "i2", "k3a": "i2a", "k3b": "i2b", "k3": "i3", "k7": "i7a"}
   435  	ctx := context.Background()
   436  	it := committed.NewDiffIterator(ctx, newFakeMetaRangeIterator(left, leftIdentities), newFakeMetaRangeIterator(right, rightIdentities))
   437  	defer it.Close()
   438  
   439  	tests := []struct {
   440  		seekTo             string
   441  		expectedDiffKeys   []string
   442  		expectedDiffRanges []*diffTestRange
   443  	}{
   444  		{
   445  			seekTo:             "k1",
   446  			expectedDiffKeys:   []string{"k2", "k3", "k3a", "k3b", "k7"},
   447  			expectedDiffRanges: []*diffTestRange{nil, nil, {"k3a", "k3b", 2}, {"k3a", "k3b", 2}, {"k3a", "k3b", 2}, {"k6", "k7", 2}, nil},
   448  		},
   449  		{
   450  			seekTo:             "k2",
   451  			expectedDiffKeys:   []string{"k2", "k3", "k3a", "k3b", "k7"},
   452  			expectedDiffRanges: []*diffTestRange{nil, nil, {"k3a", "k3b", 2}, {"k3a", "k3b", 2}, {"k3a", "k3b", 2}, {"k6", "k7", 2}, nil},
   453  		},
   454  		{
   455  			seekTo:             "k3",
   456  			expectedDiffKeys:   []string{"k3", "k3a", "k3b", "k7"},
   457  			expectedDiffRanges: []*diffTestRange{nil, {"k3a", "k3b", 2}, {"k3a", "k3b", 2}, {"k3a", "k3b", 2}, {"k6", "k7", 2}, nil},
   458  		},
   459  		{
   460  			seekTo:             "k4",
   461  			expectedDiffKeys:   []string{"k7"},
   462  			expectedDiffRanges: []*diffTestRange{{"k6", "k7", 2}, nil},
   463  		},
   464  		{
   465  			seekTo:             "k8",
   466  			expectedDiffKeys:   []string{},
   467  			expectedDiffRanges: []*diffTestRange{},
   468  		},
   469  	}
   470  	for _, tst := range tests {
   471  		t.Run(tst.seekTo, func(t *testing.T) {
   472  			it.SeekGE([]byte(tst.seekTo))
   473  			diff, rangeDiff := it.Value()
   474  			if diff != nil || rangeDiff != nil {
   475  				t.Fatalf("value expected to be nil after SeekGE. got diff=%v rangeDiff=%v", diff, rangeDiff)
   476  			}
   477  			keys := make([]string, 0)
   478  			ranges := make([]*diffTestRange, 0)
   479  			for it.Next() {
   480  				currentDiff, currentRangeDiff := it.Value()
   481  				ranges = append(ranges, newDiffTestRange(currentRangeDiff))
   482  				if currentDiff != nil {
   483  					key := currentDiff.Key.String()
   484  					identity := string(currentDiff.Value.Identity)
   485  					if currentDiff.Type != diffTypeByKey[key] {
   486  						t.Fatalf("unexpected diff type in index %d. expected=%d, got=%d", len(keys), diffTypeByKey[key], currentDiff.Type)
   487  					}
   488  					if identity != diffIdentityByKey[key] {
   489  						t.Fatalf("unexpected identity in diff index %d. expected=%s, got=%s", len(keys), diffIdentityByKey[key], identity)
   490  					}
   491  					keys = append(keys, key)
   492  				}
   493  			}
   494  			if diff := deep.Equal(keys, tst.expectedDiffKeys); diff != nil {
   495  				t.Fatal("unexpected keys in diff", diff)
   496  			}
   497  			if diff := deep.Equal(tst.expectedDiffRanges, ranges); diff != nil {
   498  				t.Fatalf("ranges in diff different than expected. diff=%s", diff)
   499  			}
   500  		})
   501  	}
   502  }
   503  
   504  func TestNextOnClose(t *testing.T) {
   505  	ctx := context.Background()
   506  	it := committed.NewDiffIterator(ctx, newFakeMetaRangeIterator([][]string{{"k1", "k2"}}, [][]string{{"i1", "i2"}}), newFakeMetaRangeIterator([][]string{{"k1", "k2"}}, [][]string{{"i1a", "i2a"}}))
   507  	if !it.Next() {
   508  		t.Fatal("expected iterator to have value")
   509  	}
   510  	it.Close()
   511  	if it.Next() {
   512  		t.Fatal("expected false from iterator after close")
   513  	}
   514  }
   515  
   516  func TestDiffErr(t *testing.T) {
   517  	leftErr := errors.New("error from left")
   518  	leftIt := newFakeMetaRangeIterator([][]string{{"k1"}, {"k2"}}, [][]string{{"i1"}, {"i2"}})
   519  	leftIt.SetErr(leftErr)
   520  	rightIt := newFakeMetaRangeIterator([][]string{{"k2"}}, [][]string{{"i2a"}})
   521  	ctx := context.Background()
   522  	it := committed.NewDiffIterator(ctx, leftIt, rightIt)
   523  	defer it.Close()
   524  	if it.Next() {
   525  		t.Fatalf("expected false from iterator with error")
   526  	}
   527  	if !errors.Is(it.Err(), leftErr) {
   528  		t.Fatalf("unexpected error from iterator. expected=%v, got=%v", leftErr, it.Err())
   529  	}
   530  	it.SeekGE([]byte("k2"))
   531  	if it.Err() != nil {
   532  		t.Fatalf("error expected to be nil after SeekGE. got=%v", it.Err())
   533  	}
   534  	if it.Next() {
   535  		t.Fatalf("expected false from iterator with error")
   536  	}
   537  	if !errors.Is(it.Err(), leftErr) {
   538  		t.Fatalf("unexpected error from iterator. expected=%v, got=%v", leftErr, it.Err())
   539  	}
   540  	rightErr := errors.New("error from right")
   541  	leftIt.SetErr(nil)
   542  	rightIt.SetErr(rightErr)
   543  	it.SeekGE([]byte("k2"))
   544  	if it.Err() != nil {
   545  		t.Fatalf("error expected to be nil after SeekGE. got=%v", it.Err())
   546  	}
   547  	if it.Next() {
   548  		t.Fatalf("expected false from iterator with error")
   549  	}
   550  	if !errors.Is(it.Err(), rightErr) {
   551  		t.Fatalf("unexpected error from iterator. expected=%v, got=%v", rightErr, it.Err())
   552  	}
   553  }
   554  
   555  func newFakeMetaRangeIterator(rangeKeys [][]string, rangeIdentities [][]string) *testutil.FakeIterator {
   556  	res := testutil.NewFakeIterator()
   557  	for rangeIdx, keys := range rangeKeys {
   558  		identities := rangeIdentities[rangeIdx]
   559  		var b bytes.Buffer
   560  		encoder := gob.NewEncoder(&b)
   561  		_ = encoder.Encode(rangeKeys[rangeIdx])
   562  		_ = encoder.Encode(rangeIdentities[rangeIdx])
   563  		var minKey, maxKey committed.Key
   564  		if len(rangeKeys[rangeIdx]) > 0 {
   565  			minKey = []byte(rangeKeys[rangeIdx][0])
   566  			maxKey = []byte(rangeKeys[rangeIdx][len(rangeKeys[rangeIdx])-1])
   567  		}
   568  		rangeValues := make([]*graveler.ValueRecord, 0, len(rangeKeys[rangeIdx]))
   569  		for idx := range keys {
   570  			rangeValues = append(rangeValues, &graveler.ValueRecord{
   571  				Key: []byte(keys[idx]),
   572  				Value: &graveler.Value{
   573  					Identity: []byte(identities[idx]),
   574  					Data:     []byte("some-data"),
   575  				},
   576  			})
   577  		}
   578  		res.
   579  			AddRange(&committed.Range{ID: committed.ID(strings.Join(identities, "-")), MinKey: minKey, MaxKey: maxKey, Count: int64(len(rangeKeys[rangeIdx]))}).
   580  			AddValueRecords(rangeValues...)
   581  	}
   582  	return res
   583  }