// Package teststore provides a common.Storage implementation for testing/mocking purposes. // // A new teststore.TestStore can be obtained with teststore.New. Whenever one of the common.Storage // methods is called, the implementation selects what function to call in the following order: // 1. If an override for that method was provided at construction time (via teststore.WithXXX()) or // afterwards via SetOption, that override is used. // 2. If a substorage was provided at construction time (via teststore.WithSubstorage()) or afterwars // via SetOption, the corresponding method in the substorage is used. // 3. If none of the above apply, the call panics with an error describing the unexpected call. // // It's safe to call SetOption and the overrides from multiple goroutines, but it's the override's // responsibility to ensure safety of whatever operation it executes. package teststore import ( "context" "errors" "fmt" "sync" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/compression" ) // TestStore is a common.Storage implementation for testing/mocking purposes. type TestStore struct { mu sync.RWMutex *cfg } // ErrDiskExploded is a phony error which can be used for testing purposes to differentiate it from // more common errors. var ErrDiskExploded = errors.New("disk exploded") // New returns a teststore.TestStore from the given options. func New(opts ...Option) *TestStore { c := &cfg{} for _, opt := range opts { opt(c) } return &TestStore{cfg: c} } // SetOption overrides an option of an existing teststore.TestStore. // This is useful for overriding methods during a test so that different // behaviors are simulated. func (s *TestStore) SetOption(opt Option) { s.mu.Lock() defer s.mu.Unlock() opt(s.cfg) } func (s *TestStore) Open(readOnly bool) error { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Open != nil: return s.overrides.Open(readOnly) case s.st != nil: return s.st.Open(readOnly) default: panic(fmt.Sprintf("unexpected storage call: Open(%v)", readOnly)) } } func (s *TestStore) Init() error { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Init != nil: return s.overrides.Init() case s.st != nil: return s.st.Init() default: panic("unexpected storage call: Init()") } } func (s *TestStore) Close() error { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Close != nil: return s.overrides.Close() case s.st != nil: return s.st.Close() default: panic("unexpected storage call: Close()") } } func (s *TestStore) Type() string { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Type != nil: return s.overrides.Type() case s.st != nil: return s.st.Type() default: panic("unexpected storage call: Type()") } } func (s *TestStore) Path() string { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Path != nil: return s.overrides.Path() case s.st != nil: return s.st.Path() default: panic("unexpected storage call: Path()") } } func (s *TestStore) SetCompressor(cc *compression.Config) { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.SetCompressor != nil: s.overrides.SetCompressor(cc) case s.st != nil: s.st.SetCompressor(cc) default: panic(fmt.Sprintf("unexpected storage call: SetCompressor(%+v)", cc)) } } func (s *TestStore) Compressor() *compression.Config { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Compressor != nil: return s.overrides.Compressor() case s.st != nil: return s.st.Compressor() default: panic("unexpected storage call: Compressor()") } } func (s *TestStore) SetReportErrorFunc(f func(string, error)) { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.SetReportErrorFunc != nil: s.overrides.SetReportErrorFunc(f) case s.st != nil: s.st.SetReportErrorFunc(f) default: panic("unexpected storage call: SetReportErrorFunc()") } } func (s *TestStore) Get(ctx context.Context, req common.GetPrm) (common.GetRes, error) { switch { case s.overrides.Get != nil: return s.overrides.Get(req) case s.st != nil: return s.st.Get(ctx, req) default: panic(fmt.Sprintf("unexpected storage call: Get(%+v)", req)) } } func (s *TestStore) GetRange(ctx context.Context, req common.GetRangePrm) (common.GetRangeRes, error) { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.GetRange != nil: return s.overrides.GetRange(req) case s.st != nil: return s.st.GetRange(ctx, req) default: panic(fmt.Sprintf("unexpected storage call: GetRange(%+v)", req)) } } func (s *TestStore) Exists(ctx context.Context, req common.ExistsPrm) (common.ExistsRes, error) { switch { case s.overrides.Exists != nil: return s.overrides.Exists(req) case s.st != nil: return s.st.Exists(ctx, req) default: panic(fmt.Sprintf("unexpected storage call: Exists(%+v)", req)) } } func (s *TestStore) Put(ctx context.Context, req common.PutPrm) (common.PutRes, error) { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Put != nil: return s.overrides.Put(req) case s.st != nil: return s.st.Put(ctx, req) default: panic(fmt.Sprintf("unexpected storage call: Put(%+v)", req)) } } func (s *TestStore) Delete(ctx context.Context, req common.DeletePrm) (common.DeleteRes, error) { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Delete != nil: return s.overrides.Delete(req) case s.st != nil: return s.st.Delete(ctx, req) default: panic(fmt.Sprintf("unexpected storage call: Delete(%+v)", req)) } } func (s *TestStore) Iterate(ctx context.Context, req common.IteratePrm) (common.IterateRes, error) { s.mu.RLock() defer s.mu.RUnlock() switch { case s.overrides.Iterate != nil: return s.overrides.Iterate(req) case s.st != nil: return s.st.Iterate(ctx, req) default: panic(fmt.Sprintf("unexpected storage call: Iterate(%+v)", req)) } } func (s *TestStore) SetParentID(string) {} func (s *TestStore) Rebuild(_ context.Context, _ common.RebuildPrm) (common.RebuildRes, error) { return common.RebuildRes{}, nil }