Repository: GoogleContainerTools/skaffold
Pull request #8000 (2022-10-27 21:39:15)

chore: make syncstore generic
This PR makes the `util.SyncStore` generic. This helps simplify the client code by providing type safety for method parameters and return types, and removes typecasting.

_Before_:
```
func (h *artifactHasherImpl) safeHash(ctx context.Context, a *latest.Artifact, platforms platform.Matcher) (string, error) {
	val := h.syncStore.Exec(a.ImageName,
		func() interface{} {
			hash, err := singleArtifactHash(ctx, h.lister, a, h.mode, platforms)
			if err != nil {
				return err
			}
			return hash
		})
	switch t := val.(type) {
	case error:
		return "", t
	case string:
		return t, nil
	default:
		return "", fmt.Errorf("internal error when retrieving cache result of type %T", t)
	}
}
```

_After_:
```

func (h *artifactHasherImpl) safeHash(ctx context.Context, a *latest.Artifact, platforms platform.Matcher) (string, error) {
	return h.syncStore.Exec(a.ImageName,
		func() (string, error) {
			return singleArtifactHash(ctx, h.lister, a, h.mode, platforms)
		})
}
```

codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#8000](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (0c34e1f) into [main](https://codecov.io/gh/GoogleContainerTools/skaffold/commit/290280e8c6c19231e9ed5b5c027f2c01d22f75be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (290280e) will **decrease** coverage by `3.82%`.
> The diff coverage is `54.09%`.

```diff
@@            Coverage Diff             @@
##             main    #8000      +/-   ##
==========================================
- Coverage   70.48%   66.66%   -3.83%     
==========================================
  Files         515      596      +81     
  Lines       23150    28894    +5744     
==========================================
+ Hits        16317    19261    +2944     
- Misses       5776     8204    +2428     
- Partials     1057     1429     +372     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | Coverage Δ | |
|---|---|---|
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy9leHBvcnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/deploy.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `40.90% <0.00%> (-12.94%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdGVzdC5nbw==) | `44.44% <0.00%> (ø)` | |
| [cmd/skaffold/app/exitcode.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9leGl0Y29kZS5nbw==) | `100.00% <ø> (+6.66%)` | :arrow_up: |
| [cmd/skaffold/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL3NrYWZmb2xkLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_tests.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF90ZXN0cy5nbw==) | `62.50% <14.28%> (-1.14%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/render.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcmVuZGVyLmdv) | `35.48% <18.18%> (-5.90%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbHNwLmdv) | `28.12% <28.12%> (ø)` | |
| [cmd/skaffold/app/cmd/run.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcnVuLmdv) | `64.28% <33.33%> (-9.63%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/fix.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZml4Lmdv) | `56.41% <37.50%> (-20.07%)` | :arrow_down: |
| ... and [389 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8000/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/pkg/skaffold/build/cache/hash.go b/pkg/skaffold/build/cache/hash.go
index 71c3c67a6..c8781347b 100644
--- a/pkg/skaffold/build/cache/hash.go
+++ b/pkg/skaffold/build/cache/hash.go
@@ -54,7 +54,7 @@ type artifactHasherImpl struct {
 	artifacts graph.ArtifactGraph
 	lister    DependencyLister
 	mode      config.RunMode
-	syncStore *util.SyncStore
+	syncStore *util.SyncStore[string]
 }
 
 // newArtifactHasher returns a new instance of an artifactHasher. Use newArtifactHasherFunc instead of calling this function directly.
@@ -63,7 +63,7 @@ func newArtifactHasher(artifacts graph.ArtifactGraph, lister DependencyLister, m
 		artifacts: artifacts,
 		lister:    lister,
 		mode:      mode,
-		syncStore: util.NewSyncStore(),
+		syncStore: util.NewSyncStore[string](),
 	}
 }
 
@@ -95,22 +95,10 @@ func (h *artifactHasherImpl) hash(ctx context.Context, a *latest.Artifact, platf
 }
 
 func (h *artifactHasherImpl) safeHash(ctx context.Context, a *latest.Artifact, platforms platform.Matcher) (string, error) {
-	val := h.syncStore.Exec(a.ImageName,
-		func() interface{} {
-			hash, err := singleArtifactHash(ctx, h.lister, a, h.mode, platforms)
-			if err != nil {
-				return err
-			}
-			return hash
+	return h.syncStore.Exec(a.ImageName,
+		func() (string, error) {
+			return singleArtifactHash(ctx, h.lister, a, h.mode, platforms)
 		})
-	switch t := val.(type) {
-	case error:
-		return "", t
-	case string:
-		return t, nil
-	default:
-		return "", fmt.Errorf("internal error when retrieving cache result of type %T", t)
-	}
 }
 
 // singleArtifactHash calculates the hash for a single artifact, and ignores its required artifacts.
diff --git a/pkg/skaffold/docker/dependencies.go b/pkg/skaffold/docker/dependencies.go
index 9ce5013d6..3fa067abb 100644
--- a/pkg/skaffold/docker/dependencies.go
+++ b/pkg/skaffold/docker/dependencies.go
@@ -32,7 +32,7 @@ import (
 )
 
 var (
-	dependencyCache = util.NewSyncStore()
+	dependencyCache = util.NewSyncStore[[]string]()
 )
 
 // BuildConfig encapsulates all the build configuration required for performing a dockerbuild.
@@ -78,9 +78,9 @@ func GetDependencies(ctx context.Context, buildCfg BuildConfig, cfg Config) ([]s
 	if err != nil {
 		return nil, fmt.Errorf("normalizing dockerfilePath path: %w", err)
 	}
-	result := getDependencies(ctx, buildCfg.workspace, buildCfg.dockerfilePath, absDockerfilePath, buildCfg.args, cfg)
-	dependencyCache.Store(buildCfg.artifact, result)
-	return resultPair(result)
+	result, err := getDependencies(ctx, buildCfg.workspace, buildCfg.dockerfilePath, absDockerfilePath, buildCfg.args, cfg)
+	dependencyCache.Store(buildCfg.artifact, result, err)
+	return result, err
 }
 
 // GetDependencies finds the sources dependency for the given docker artifact.
@@ -103,21 +103,9 @@ func GetDependenciesCached(ctx context.Context, buildCfg BuildConfig, cfg Config
 		return nil, fmt.Errorf("normalizing dockerfilePath path: %w", err)
 	}
 
-	deps := dependencyCache.Exec(buildCfg.artifact, func() interface{} {
+	return dependencyCache.Exec(buildCfg.artifact, func() ([]string, error) {
 		return getDependencies(ctx, buildCfg.workspace, buildCfg.dockerfilePath, absDockerfilePath, buildCfg.args, cfg)
 	})
-	return resultPair(deps)
-}
-
-func resultPair(deps interface{}) ([]string, error) {
-	switch t := deps.(type) {
-	case error:
-		return nil, t
-	case []string:
-		return t, nil
-	default:
-		return nil, fmt.Errorf("internal error when retrieving cache result of type %T", t)
-	}
 }
 
 func resultPairForDockerCopyFromTo(deps interface{}) (map[string][]string, error) {
@@ -131,23 +119,23 @@ func resultPairForDockerCopyFromTo(deps interface{}) (map[string][]string, error
 	}
 }
 
-func getDependencies(ctx context.Context, workspace string, dockerfilePath string, absDockerfilePath string, buildArgs map[string]*string, cfg Config) interface{} {
+func getDependencies(ctx context.Context, workspace string, dockerfilePath string, absDockerfilePath string, buildArgs map[string]*string, cfg Config) ([]string, error) {
 	// If the Dockerfile doesn't exist, we can't compute the dependency.
 	// But since we know the Dockerfile is a dependency, let's return a list
 	// with only that file. It makes errors down the line more actionable
 	// than returning an error now.
 	if _, err := os.Stat(absDockerfilePath); os.IsNotExist(err) {
-		return []string{dockerfilePath}
+		return []string{dockerfilePath}, nil
 	}
 
 	fts, err := ReadCopyCmdsFromDockerfile(ctx, false, absDockerfilePath, workspace, buildArgs, cfg)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	excludes, err := readDockerignore(workspace, absDockerfilePath)
 	if err != nil {
-		return fmt.Errorf("reading .dockerignore: %w", err)
+		return nil, fmt.Errorf("reading .dockerignore: %w", err)
 	}
 
 	deps := make([]string, 0, len(fts))
@@ -157,7 +145,7 @@ func getDependencies(ctx context.Context, workspace string, dockerfilePath strin
 
 	files, err := WalkWorkspace(workspace, excludes, deps)
 	if err != nil {
-		return fmt.Errorf("walking workspace: %w", err)
+		return nil, fmt.Errorf("walking workspace: %w", err)
 	}
 
 	// Always add dockerfile even if it's .dockerignored. The daemon will need it anyways.
@@ -176,7 +164,7 @@ func getDependencies(ctx context.Context, workspace string, dockerfilePath strin
 	}
 	sort.Strings(dependencies)
 
-	return dependencies
+	return dependencies, nil
 }
 
 func getDependenciesByDockerCopyFromTo(ctx context.Context, workspace string, dockerfilePath string, absDockerfilePath string, buildArgs map[string]*string, cfg Config) interface{} {
diff --git a/pkg/skaffold/graph/dependencies.go b/pkg/skaffold/graph/dependencies.go
index aa8e6a9b1..7c4bf1d7a 100644
--- a/pkg/skaffold/graph/dependencies.go
+++ b/pkg/skaffold/graph/dependencies.go
@@ -49,14 +49,14 @@ type SourceDependenciesCache interface {
 }
 
 func NewSourceDependenciesCache(cfg docker.Config, r docker.ArtifactResolver, g ArtifactGraph) SourceDependenciesCache {
-	return &dependencyResolverImpl{cfg: cfg, artifactResolver: r, artifactGraph: g, cache: util.NewSyncStore()}
+	return &dependencyResolverImpl{cfg: cfg, artifactResolver: r, artifactGraph: g, cache: util.NewSyncStore[[]string]()}
 }
 
 type dependencyResolverImpl struct {
 	cfg              docker.Config
 	artifactResolver docker.ArtifactResolver
 	artifactGraph    ArtifactGraph
-	cache            *util.SyncStore
+	cache            *util.SyncStore[[]string]
 }
 
 func (r *dependencyResolverImpl) TransitiveArtifactDependencies(ctx context.Context, a *latest.Artifact) ([]string, error) {
@@ -87,25 +87,21 @@ func (r *dependencyResolverImpl) SingleArtifactDependencies(ctx context.Context,
 	})
 	defer endTrace()
 
-	res := r.cache.Exec(a.ImageName, func() interface{} {
-		d, e := getDependenciesFunc(ctx, a, r.cfg, r.artifactResolver)
-		if e != nil {
-			return e
-		}
-		return d
+	res, err := r.cache.Exec(a.ImageName, func() ([]string, error) {
+		return getDependenciesFunc(ctx, a, r.cfg, r.artifactResolver)
 	})
-	if err, ok := res.(error); ok {
+	if err != nil {
 		endTrace(instrumentation.TraceEndError(err))
 		return nil, err
 	}
 
-	deps := make([]string, len(res.([]string)))
-	copy(deps, res.([]string))
+	deps := make([]string, len(res))
+	copy(deps, res)
 	return deps, nil
 }
 
 func (r *dependencyResolverImpl) Reset() {
-	r.cache = util.NewSyncStore()
+	r.cache = util.NewSyncStore[[]string]()
 }
 
 // sourceDependenciesForArtifact returns the build dependencies for the current artifact.
diff --git a/pkg/skaffold/util/store.go b/pkg/skaffold/util/store.go
index 36a8b9636..dedf512ab 100644
--- a/pkg/skaffold/util/store.go
+++ b/pkg/skaffold/util/store.go
@@ -25,15 +25,42 @@ import (
 
 // SyncStore exports a single method `Exec` to ensure single execution of a function
 // and share the result between all callers of the function.
-type SyncStore struct {
+type SyncStore[T any] struct {
 	sf      singleflight.Group
-	results sync.Map
+	results syncMap[T]
+}
+
+type syncMap[T any] struct {
+	sync.Map
+}
+
+func (m *syncMap[T]) Load(k any) (v T, err error, ok bool) {
+	val, found := m.Map.Load(k)
+	if !found {
+		return
+	}
+	ok = true
+	switch t := val.(type) {
+	case error:
+		err = t
+	case T:
+		v = t
+	}
+	return
+}
+
+func (m *syncMap[T]) Store(k any, v T, err error) {
+	if err != nil {
+		m.Map.Store(k, err)
+	} else {
+		m.Map.Store(k, v)
+	}
 }
 
 // Exec executes the function f if and only if it's being called the first time for a specific key.
 // If it's called multiple times for the same key only the first call will execute and store the result of f.
 // All other calls will be blocked until the running instance of f returns and all of them receive the same result.
-func (o *SyncStore) Exec(key string, f func() interface{}) interface{} {
+func (o *SyncStore[T]) Exec(key string, f func() (T, error)) (T, error) {
 	val, err := o.sf.Do(key, func() (_ interface{}, err error) {
 		// trap any runtime error due to synchronization issues.
 		defer func() {
@@ -41,31 +68,40 @@ func (o *SyncStore) Exec(key string, f func() interface{}) interface{} {
 				err = retrieveError(key, rErr)
 			}
 		}()
-		v, ok := o.results.Load(key)
-		if !ok {
-			v = f()
-			o.results.Store(key, v)
+		v, err, ok := o.results.Load(key)
+		if ok {
+			return v, err
 		}
-		return v, nil
+		v, err = f()
+		o.results.Store(key, v, err)
+		return v, err
 	})
+	var defaultT T
 	if err != nil {
-		return err
+		return defaultT, err
+	}
+	switch t := val.(type) {
+	case error:
+		return defaultT, t
+	case T:
+		return t, nil
+	default:
+		return defaultT, err
 	}
-	return val
 }
 
 // Store will store the results for a key in a cache
 // This function is not safe to use if multiple subroutines store the
 // result for the same key.
-func (o *SyncStore) Store(key string, r interface{}) {
-	o.results.Store(key, r)
+func (o *SyncStore[T]) Store(key string, r T, err error) {
+	o.results.Store(key, r, err)
 }
 
 // NewSyncStore returns a new instance of `SyncStore`
-func NewSyncStore() *SyncStore {
-	return &SyncStore{
+func NewSyncStore[T any]() *SyncStore[T] {
+	return &SyncStore[T]{
 		sf:      singleflight.Group{},
-		results: sync.Map{},
+		results: syncMap[T]{Map: sync.Map{}},
 	}
 }
 


Test patch:
diff --git a/pkg/skaffold/docker/dependencies_test.go b/pkg/skaffold/docker/dependencies_test.go
index 083855146..2726917c8 100644
--- a/pkg/skaffold/docker/dependencies_test.go
+++ b/pkg/skaffold/docker/dependencies_test.go
@@ -712,16 +712,17 @@ func checkSameFile(t *testutil.T, expected, result string) {
 func TestGetDependenciesCached(t *testing.T) {
 	imageFetcher := fakeImageFetcher{}
 	tests := []struct {
-		description     string
-		retrieveImgMock func(context.Context, string, Config) (*v1.ConfigFile, error)
-		dependencyCache map[string]interface{}
-		expected        []string
-		shouldErr       bool
+		description        string
+		retrieveImgMock    func(context.Context, string, Config) (*v1.ConfigFile, error)
+		dependencyCache    map[string][]string
+		dependencyCacheErr map[string]error
+		expected           []string
+		shouldErr          bool
 	}{
 		{
 			description:     "with no cached results getDependencies will retrieve image",
 			retrieveImgMock: imageFetcher.fetch,
-			dependencyCache: map[string]interface{}{},
+			dependencyCache: map[string][]string{},
 			expected:        []string{"Dockerfile", "server.go"},
 		},
 		{
@@ -729,7 +730,7 @@ func TestGetDependenciesCached(t *testing.T) {
 			retrieveImgMock: func(context.Context, string, Config) (*v1.ConfigFile, error) {
 				return nil, fmt.Errorf("unexpected call")
 			},
-			dependencyCache: map[string]interface{}{"dummy": []string{"random.go"}},
+			dependencyCache: map[string][]string{"dummy": {"random.go"}},
 			expected:        []string{"random.go"},
 		},
 		{
@@ -737,13 +738,13 @@ func TestGetDependenciesCached(t *testing.T) {
 			retrieveImgMock: func(context.Context, string, Config) (*v1.ConfigFile, error) {
 				return &v1.ConfigFile{}, nil
 			},
-			dependencyCache: map[string]interface{}{"dummy": fmt.Errorf("remote manifest fetch")},
-			shouldErr:       true,
+			dependencyCacheErr: map[string]error{"dummy": fmt.Errorf("remote manifest fetch")},
+			shouldErr:          true,
 		},
 		{
 			description:     "with cached results for dockerfile in another app",
 			retrieveImgMock: imageFetcher.fetch,
-			dependencyCache: map[string]interface{}{"another": []string{"random.go"}},
+			dependencyCache: map[string][]string{"another": {"random.go"}},
 			expected:        []string{"Dockerfile", "server.go"},
 		},
 	}
@@ -752,14 +753,19 @@ func TestGetDependenciesCached(t *testing.T) {
 		testutil.Run(t, test.description, func(t *testutil.T) {
 			t.Override(&RetrieveImage, test.retrieveImgMock)
 			t.Override(&util.OSEnviron, func() []string { return []string{} })
-			t.Override(&dependencyCache, util.NewSyncStore())
+			t.Override(&dependencyCache, util.NewSyncStore[[]string]())
 
 			tmpDir := t.NewTempDir().Touch("server.go", "random.go")
 			tmpDir.Write("Dockerfile", copyServerGo)
 
 			for k, v := range test.dependencyCache {
-				dependencyCache.Exec(k, func() interface{} {
-					return v
+				dependencyCache.Exec(k, func() ([]string, error) {
+					return v, nil
+				})
+			}
+			for k, v := range test.dependencyCacheErr {
+				dependencyCache.Exec(k, func() ([]string, error) {
+					return nil, v
 				})
 			}
 			deps, err := GetDependenciesCached(context.Background(), NewBuildConfig(tmpDir.Root(), "dummy", "Dockerfile", map[string]*string{}), nil)
diff --git a/pkg/skaffold/util/store_test.go b/pkg/skaffold/util/store_test.go
index 455c3b6d2..f26345054 100644
--- a/pkg/skaffold/util/store_test.go
+++ b/pkg/skaffold/util/store_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package util
 
 import (
+	"errors"
 	"fmt"
 	"strconv"
 	"sync"
@@ -37,13 +38,13 @@ func TestSyncStore(t *testing.T) {
 		var wg sync.WaitGroup
 		wg.Add(10)
 
-		s := NewSyncStore()
+		s := NewSyncStore[int]()
 		for i := 0; i < 5; i++ {
 			for j := 0; j < 2; j++ {
 				go func(i int) {
 					k := strconv.Itoa(i)
-					val := s.Exec(k, func() interface{} {
-						return f(i)
+					val, _ := s.Exec(k, func() (int, error) {
+						return f(i), nil
 					})
 					t.CheckDeepEqual(i, val)
 					wg.Done()
@@ -59,16 +60,16 @@ func TestSyncStore(t *testing.T) {
 	})
 
 	testutil.Run(t, "test panic handled correctly", func(t *testutil.T) {
-		s := NewSyncStore()
-		val := s.Exec("panic", func() interface{} {
+		s := NewSyncStore[int]()
+		_, err := s.Exec("panic", func() (int, error) {
 			panic(fmt.Errorf("message"))
 		})
 		// make sure val is of type StoreError
-		switch tv := val.(type) {
-		case StoreError:
-			t.CheckDeepEqual("internal error retrieving cached results for key panic: message", tv.Error())
-		default:
-			t.Fatalf("expected to retrieve result of type StoreError but found %T", tv)
+		var e StoreError
+		if errors.As(err, &e) {
+			t.CheckDeepEqual("internal error retrieving cached results for key panic: message", e.Error())
+		} else {
+			t.Fatalf("expected to retrieve result of type StoreError but found %T", err)
 		}
 	})
 }