From dd84b9d64fb98746a230cd24233ff50a562c39c9 Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 28 Apr 2023 01:36:44 +0800 Subject: --- cli/internal/cache/cache_test.go | 318 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 cli/internal/cache/cache_test.go (limited to 'cli/internal/cache/cache_test.go') diff --git a/cli/internal/cache/cache_test.go b/cli/internal/cache/cache_test.go new file mode 100644 index 0000000..3f17877 --- /dev/null +++ b/cli/internal/cache/cache_test.go @@ -0,0 +1,318 @@ +package cache + +import ( + "net/http" + "reflect" + "sync/atomic" + "testing" + + "github.com/vercel/turbo/cli/internal/analytics" + "github.com/vercel/turbo/cli/internal/fs" + "github.com/vercel/turbo/cli/internal/turbopath" + "github.com/vercel/turbo/cli/internal/util" +) + +type testCache struct { + disabledErr *util.CacheDisabledError + entries map[string][]turbopath.AnchoredSystemPath +} + +func (tc *testCache) Fetch(_ turbopath.AbsoluteSystemPath, hash string, _ []string) (ItemStatus, []turbopath.AnchoredSystemPath, int, error) { + if tc.disabledErr != nil { + return ItemStatus{}, nil, 0, tc.disabledErr + } + foundFiles, ok := tc.entries[hash] + if ok { + duration := 5 + return ItemStatus{Local: true}, foundFiles, duration, nil + } + return ItemStatus{}, nil, 0, nil +} + +func (tc *testCache) Exists(hash string) ItemStatus { + if tc.disabledErr != nil { + return ItemStatus{} + } + _, ok := tc.entries[hash] + if ok { + return ItemStatus{Local: true} + } + return ItemStatus{} +} + +func (tc *testCache) Put(_ turbopath.AbsoluteSystemPath, hash string, _ int, files []turbopath.AnchoredSystemPath) error { + if tc.disabledErr != nil { + return tc.disabledErr + } + tc.entries[hash] = files + return nil +} + +func (tc *testCache) Clean(_ turbopath.AbsoluteSystemPath) {} +func (tc *testCache) CleanAll() {} +func (tc *testCache) Shutdown() {} + +func newEnabledCache() *testCache { + return &testCache{ + entries: make(map[string][]turbopath.AnchoredSystemPath), + } +} + +func newDisabledCache() *testCache { + return &testCache{ + disabledErr: &util.CacheDisabledError{ + Status: util.CachingStatusDisabled, + Message: "remote caching is disabled", + }, + } +} + +func TestPutCachingDisabled(t *testing.T) { + disabledCache := newDisabledCache() + caches := []Cache{ + newEnabledCache(), + disabledCache, + newEnabledCache(), + newEnabledCache(), + } + var removeCalled uint64 + mplex := &cacheMultiplexer{ + caches: caches, + onCacheRemoved: func(cache Cache, err error) { + atomic.AddUint64(&removeCalled, 1) + }, + } + + err := mplex.Put("unused-target", "some-hash", 5, []turbopath.AnchoredSystemPath{"a-file"}) + if err != nil { + // don't leak the cache removal + t.Errorf("Put got error %v, want ", err) + } + + removes := atomic.LoadUint64(&removeCalled) + if removes != 1 { + t.Errorf("removes count: %v, want 1", removes) + } + + mplex.mu.RLock() + if len(mplex.caches) != 3 { + t.Errorf("found %v caches, expected to have 3 after one was removed", len(mplex.caches)) + } + for _, cache := range mplex.caches { + if cache == disabledCache { + t.Error("found disabled cache, expected it to be removed") + } + } + mplex.mu.RUnlock() + + // subsequent Fetch should still work + cacheStatus, _, _, err := mplex.Fetch("unused-target", "some-hash", []string{"unused", "files"}) + if err != nil { + t.Errorf("got error fetching files: %v", err) + } + hit := cacheStatus.Local || cacheStatus.Remote + if !hit { + t.Error("failed to find previously stored files") + } + + removes = atomic.LoadUint64(&removeCalled) + if removes != 1 { + t.Errorf("removes count: %v, want 1", removes) + } +} + +func TestExists(t *testing.T) { + caches := []Cache{ + newEnabledCache(), + } + + mplex := &cacheMultiplexer{ + caches: caches, + } + + itemStatus := mplex.Exists("some-hash") + if itemStatus.Local { + t.Error("did not expect file to exist") + } + + err := mplex.Put("unused-target", "some-hash", 5, []turbopath.AnchoredSystemPath{"a-file"}) + if err != nil { + // don't leak the cache removal + t.Errorf("Put got error %v, want ", err) + } + + itemStatus = mplex.Exists("some-hash") + if !itemStatus.Local { + t.Error("failed to find previously stored files") + } +} + +type fakeClient struct{} + +// FetchArtifact implements client +func (*fakeClient) FetchArtifact(hash string) (*http.Response, error) { + panic("unimplemented") +} + +func (*fakeClient) ArtifactExists(hash string) (*http.Response, error) { + panic("unimplemented") +} + +// GetTeamID implements client +func (*fakeClient) GetTeamID() string { + return "fake-team-id" +} + +// PutArtifact implements client +func (*fakeClient) PutArtifact(hash string, body []byte, duration int, tag string) error { + panic("unimplemented") +} + +var _ client = &fakeClient{} + +func TestFetchCachingDisabled(t *testing.T) { + disabledCache := newDisabledCache() + caches := []Cache{ + newEnabledCache(), + disabledCache, + newEnabledCache(), + newEnabledCache(), + } + var removeCalled uint64 + mplex := &cacheMultiplexer{ + caches: caches, + onCacheRemoved: func(cache Cache, err error) { + atomic.AddUint64(&removeCalled, 1) + }, + } + + cacheStatus, _, _, err := mplex.Fetch("unused-target", "some-hash", []string{"unused", "files"}) + if err != nil { + // don't leak the cache removal + t.Errorf("Fetch got error %v, want ", err) + } + hit := cacheStatus.Local || cacheStatus.Remote + if hit { + t.Error("hit on empty cache, expected miss") + } + + removes := atomic.LoadUint64(&removeCalled) + if removes != 1 { + t.Errorf("removes count: %v, want 1", removes) + } + + mplex.mu.RLock() + if len(mplex.caches) != 3 { + t.Errorf("found %v caches, expected to have 3 after one was removed", len(mplex.caches)) + } + for _, cache := range mplex.caches { + if cache == disabledCache { + t.Error("found disabled cache, expected it to be removed") + } + } + mplex.mu.RUnlock() +} + +type nullRecorder struct{} + +func (nullRecorder) LogEvent(analytics.EventPayload) {} + +func TestNew(t *testing.T) { + // Test will bomb if this fails, no need to specially handle the error + repoRoot := fs.AbsoluteSystemPathFromUpstream(t.TempDir()) + type args struct { + opts Opts + recorder analytics.Recorder + onCacheRemoved OnCacheRemoved + client fakeClient + } + tests := []struct { + name string + args args + want Cache + wantErr bool + }{ + { + name: "With no caches configured, new returns a noopCache and an error", + args: args{ + opts: Opts{ + SkipFilesystem: true, + SkipRemote: true, + }, + recorder: &nullRecorder{}, + onCacheRemoved: func(Cache, error) {}, + }, + want: &noopCache{}, + wantErr: true, + }, + { + name: "With just httpCache configured, new returns an httpCache and a noopCache", + args: args{ + opts: Opts{ + SkipFilesystem: true, + RemoteCacheOpts: fs.RemoteCacheOptions{ + Signature: true, + }, + }, + recorder: &nullRecorder{}, + onCacheRemoved: func(Cache, error) {}, + }, + want: &cacheMultiplexer{ + caches: []Cache{&httpCache{}, &noopCache{}}, + }, + wantErr: false, + }, + { + name: "With just fsCache configured, new returns only an fsCache", + args: args{ + opts: Opts{ + SkipRemote: true, + }, + recorder: &nullRecorder{}, + onCacheRemoved: func(Cache, error) {}, + }, + want: &fsCache{}, + }, + { + name: "With both configured, new returns an fsCache and httpCache", + args: args{ + opts: Opts{ + RemoteCacheOpts: fs.RemoteCacheOptions{ + Signature: true, + }, + }, + recorder: &nullRecorder{}, + onCacheRemoved: func(Cache, error) {}, + }, + want: &cacheMultiplexer{ + caches: []Cache{&fsCache{}, &httpCache{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := New(tt.args.opts, repoRoot, &tt.args.client, tt.args.recorder, tt.args.onCacheRemoved) + if (err != nil) != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + return + } + switch multiplexer := got.(type) { + case *cacheMultiplexer: + want := tt.want.(*cacheMultiplexer) + for i := range multiplexer.caches { + if reflect.TypeOf(multiplexer.caches[i]) != reflect.TypeOf(want.caches[i]) { + t.Errorf("New() = %v, want %v", reflect.TypeOf(multiplexer.caches[i]), reflect.TypeOf(want.caches[i])) + } + } + case *fsCache: + if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { + t.Errorf("New() = %v, want %v", reflect.TypeOf(got), reflect.TypeOf(tt.want)) + } + case *noopCache: + if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { + t.Errorf("New() = %v, want %v", reflect.TypeOf(got), reflect.TypeOf(tt.want)) + } + } + }) + } +} -- cgit v1.2.3-70-g09d2