github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/catalog/testutils/testutils.go (about)

     1  package testutils
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"math/rand"
     9  	"net/url"
    10  	"sort"
    11  	"time"
    12  
    13  	"github.com/treeverse/lakefs/pkg/block"
    14  	"github.com/treeverse/lakefs/pkg/catalog"
    15  	"github.com/treeverse/lakefs/pkg/graveler"
    16  	"github.com/treeverse/lakefs/pkg/ingest/store"
    17  	"github.com/treeverse/lakefs/pkg/testutil"
    18  )
    19  
    20  type FakeValueIterator struct {
    21  	Records []*graveler.ValueRecord
    22  	Error   error
    23  	index   int
    24  }
    25  
    26  func (f *FakeValueIterator) Next() bool {
    27  	if f.Error != nil {
    28  		return false
    29  	}
    30  	f.index++
    31  	return f.index < len(f.Records)
    32  }
    33  
    34  func (f *FakeValueIterator) SeekGE(id graveler.Key) {
    35  	if f.Error != nil {
    36  		return
    37  	}
    38  	for i, ent := range f.Records {
    39  		if bytes.Compare(ent.Key, id) >= 0 {
    40  			f.index = i - 1
    41  			return
    42  		}
    43  	}
    44  }
    45  
    46  func (f *FakeValueIterator) Value() *graveler.ValueRecord {
    47  	if f.Error != nil || f.index < 0 || f.index >= len(f.Records) {
    48  		return nil
    49  	}
    50  	return f.Records[f.index]
    51  }
    52  
    53  func (f *FakeValueIterator) Err() error {
    54  	return f.Error
    55  }
    56  
    57  func (f *FakeValueIterator) Close() {}
    58  
    59  func NewFakeValueIterator(records []*graveler.ValueRecord) *FakeValueIterator {
    60  	return &FakeValueIterator{
    61  		Records: records,
    62  		Error:   nil,
    63  		index:   -1,
    64  	}
    65  }
    66  
    67  type FakeEntryIterator struct {
    68  	Entries []*catalog.EntryRecord
    69  	Error   error
    70  	index   int
    71  }
    72  
    73  func NewFakeEntryIterator(entries []*catalog.EntryRecord) *FakeEntryIterator {
    74  	return &FakeEntryIterator{
    75  		Entries: entries,
    76  		index:   -1,
    77  	}
    78  }
    79  
    80  func (f *FakeEntryIterator) Next() bool {
    81  	if f.Error != nil {
    82  		return false
    83  	}
    84  	f.index++
    85  	return f.index < len(f.Entries)
    86  }
    87  
    88  func (f *FakeEntryIterator) SeekGE(id catalog.Path) {
    89  	if f.Error != nil {
    90  		return
    91  	}
    92  	for i, ent := range f.Entries {
    93  		if ent.Path >= id {
    94  			f.index = i - 1
    95  			return
    96  		}
    97  	}
    98  }
    99  
   100  func (f *FakeEntryIterator) Value() *catalog.EntryRecord {
   101  	if f.Error != nil || f.index < 0 || f.index >= len(f.Entries) {
   102  		return nil
   103  	}
   104  	return f.Entries[f.index]
   105  }
   106  
   107  func (f *FakeEntryIterator) Err() error {
   108  	return f.Error
   109  }
   110  
   111  func (f *FakeEntryIterator) Close() {}
   112  
   113  type FakeFactory struct {
   114  	Walker *FakeWalker
   115  }
   116  
   117  func (f FakeFactory) GetWalker(_ context.Context, op store.WalkerOptions) (*store.WalkerWrapper, error) {
   118  	u, _ := url.Parse(op.StorageURI)
   119  	return store.NewWrapper(f.Walker, u), nil
   120  }
   121  
   122  func NewFakeWalker(count, max int, uriPrefix, expectedAfter, expectedContinuationToken, expectedFromSourceURIWithPrefix string, err error) *FakeWalker {
   123  	w := &FakeWalker{
   124  		Max:                             max,
   125  		uriPrefix:                       uriPrefix,
   126  		expectedAfter:                   expectedAfter,
   127  		expectedContinuationToken:       expectedContinuationToken,
   128  		expectedFromSourceURIWithPrefix: expectedFromSourceURIWithPrefix,
   129  		err:                             err,
   130  	}
   131  	w.createEntries(count)
   132  	return w
   133  }
   134  
   135  type FakeWalker struct {
   136  	Entries                         []block.ObjectStoreEntry
   137  	curr                            int
   138  	Max                             int
   139  	uriPrefix                       string
   140  	expectedAfter                   string
   141  	expectedContinuationToken       string
   142  	expectedFromSourceURIWithPrefix string
   143  	err                             error
   144  }
   145  
   146  func (w *FakeWalker) GetSkippedEntries() []block.ObjectStoreEntry {
   147  	return nil
   148  }
   149  
   150  const (
   151  	randomKeyLength = 64
   152  	entrySize       = 132
   153  )
   154  
   155  func (w *FakeWalker) createEntries(count int) {
   156  	ents := make([]block.ObjectStoreEntry, count)
   157  
   158  	// Use same sequence to overcome Graveler ability to create small ranges.
   159  	// Calling test functions rely on Graveler to not break on the first 1000 entries.
   160  	// For example, setting "5" here will cause the test to constantly fail.
   161  	// Fix Bug #3384
   162  	const seed = 6
   163  	//nolint:gosec
   164  	randGen := rand.New(rand.NewSource(seed))
   165  	for i := 0; i < count; i++ {
   166  		relativeKey := testutil.RandomString(randGen, randomKeyLength)
   167  		fullkey := w.uriPrefix + "/" + relativeKey
   168  		ents[i] = block.ObjectStoreEntry{
   169  			RelativeKey: relativeKey,
   170  			FullKey:     fullkey,
   171  			Address:     w.expectedFromSourceURIWithPrefix + "/" + relativeKey,
   172  			ETag:        "some_etag",
   173  			Mtime:       time.Time{},
   174  			Size:        entrySize,
   175  		}
   176  	}
   177  	sort.Slice(ents, func(i, j int) bool {
   178  		return ents[i].RelativeKey < ents[j].RelativeKey
   179  	})
   180  	w.Entries = ents
   181  }
   182  
   183  var errUnexpectedValue = errors.New("unexpected value")
   184  
   185  func (w *FakeWalker) Walk(_ context.Context, storageURI *url.URL, op block.WalkOptions, walkFn func(e block.ObjectStoreEntry) error) error {
   186  	if w.expectedAfter != op.After {
   187  		return fmt.Errorf("(after) expected %s, got %s: %w", w.expectedAfter, op.After, errUnexpectedValue)
   188  	}
   189  	if w.expectedContinuationToken != op.ContinuationToken {
   190  		return fmt.Errorf("(continuationToken) expected %s, got %s: %w", w.expectedContinuationToken, op.ContinuationToken, errUnexpectedValue)
   191  	}
   192  	if w.expectedFromSourceURIWithPrefix != storageURI.String() {
   193  		return fmt.Errorf("(fromSourceURIWithPrefix) expected %s, got %s: %w", w.expectedFromSourceURIWithPrefix, storageURI.String(), errUnexpectedValue)
   194  	}
   195  	if w.err != nil {
   196  		return w.err
   197  	}
   198  
   199  	for i, e := range w.Entries {
   200  		w.curr = i
   201  		if err := walkFn(e); err != nil {
   202  			if i < w.Max {
   203  				return fmt.Errorf("didn't expect walk err: %w", err)
   204  			}
   205  			if i >= w.Max && !errors.Is(err, catalog.ErrItClosed) {
   206  				return fmt.Errorf("expected error; expected (%s), got: %w", catalog.ErrItClosed, err)
   207  			}
   208  		}
   209  	}
   210  	return nil
   211  }
   212  
   213  const ContinuationTokenOpaque = "FakeContToken"
   214  
   215  func (w *FakeWalker) Marker() block.Mark {
   216  	token := ""
   217  	if w.curr < len(w.Entries)-1 {
   218  		token = ContinuationTokenOpaque
   219  	}
   220  	return block.Mark{
   221  		LastKey:           w.Entries[w.curr].FullKey,
   222  		HasMore:           w.curr < len(w.Entries)-1,
   223  		ContinuationToken: token,
   224  	}
   225  }