github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cache/lookup_test.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cache 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "reflect" 24 "testing" 25 26 "github.com/docker/docker/client" 27 specs "github.com/opencontainers/image-spec/specs-go/v1" 28 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 31 sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 35 "github.com/GoogleContainerTools/skaffold/proto/v1" 36 "github.com/GoogleContainerTools/skaffold/testutil" 37 ) 38 39 func TestLookupLocal(t *testing.T) { 40 tests := []struct { 41 description string 42 hasher artifactHasher 43 cache map[string]ImageDetails 44 api *testutil.FakeAPIClient 45 expected cacheDetails 46 }{ 47 { 48 description: "miss", 49 hasher: mockHasher{"thehash"}, 50 api: &testutil.FakeAPIClient{}, 51 cache: map[string]ImageDetails{}, 52 expected: needsBuilding{hash: "thehash"}, 53 }, 54 { 55 description: "hash failure", 56 hasher: failingHasher{errors.New("BUG")}, 57 expected: failed{err: errors.New("getting hash for artifact \"artifact\": BUG")}, 58 }, 59 { 60 description: "miss no imageID", 61 hasher: mockHasher{"hash"}, 62 cache: map[string]ImageDetails{ 63 "hash": {Digest: "ignored"}, 64 }, 65 expected: needsBuilding{hash: "hash"}, 66 }, 67 { 68 description: "hit but not found", 69 hasher: mockHasher{"hash"}, 70 cache: map[string]ImageDetails{ 71 "hash": {ID: "imageID"}, 72 }, 73 api: &testutil.FakeAPIClient{}, 74 expected: needsBuilding{hash: "hash"}, 75 }, 76 { 77 description: "hit but not found with error", 78 hasher: mockHasher{"hash"}, 79 cache: map[string]ImageDetails{ 80 "hash": {ID: "imageID"}, 81 }, 82 api: &testutil.FakeAPIClient{ 83 ErrImageInspect: true, 84 }, 85 expected: failed{err: sErrors.NewError( 86 fmt.Errorf("getting imageID for tag: "), 87 &proto.ActionableErr{ 88 Message: "getting imageID for tag: ", 89 ErrCode: proto.StatusCode_BUILD_DOCKER_GET_DIGEST_ERR, 90 })}, 91 }, 92 { 93 description: "hit", 94 hasher: mockHasher{"hash"}, 95 cache: map[string]ImageDetails{ 96 "hash": {ID: "imageID"}, 97 }, 98 api: (&testutil.FakeAPIClient{}).Add("tag", "imageID"), 99 expected: found{hash: "hash"}, 100 }, 101 { 102 description: "hit but different tag", 103 hasher: mockHasher{"hash"}, 104 cache: map[string]ImageDetails{ 105 "hash": {ID: "imageID"}, 106 }, 107 api: (&testutil.FakeAPIClient{}).Add("tag", "otherImageID").Add("othertag", "imageID"), 108 expected: needsLocalTagging{hash: "hash", tag: "tag", imageID: "imageID"}, 109 }, 110 { 111 description: "hit but imageID not found", 112 hasher: mockHasher{"hash"}, 113 cache: map[string]ImageDetails{ 114 "hash": {ID: "imageID"}, 115 }, 116 api: (&testutil.FakeAPIClient{}).Add("tag", "otherImageID"), 117 expected: needsBuilding{hash: "hash"}, 118 }, 119 } 120 for _, test := range tests { 121 testutil.Run(t, test.description, func(t *testutil.T) { 122 cache := &cache{ 123 isLocalImage: func(string) (bool, error) { return true, nil }, 124 importMissingImage: func(imageName string) (bool, error) { return false, nil }, 125 artifactCache: test.cache, 126 client: fakeLocalDaemon(test.api), 127 cfg: &mockConfig{mode: config.RunModes.Build}, 128 } 129 130 t.Override(&newArtifactHasherFunc, func(_ graph.ArtifactGraph, _ DependencyLister, _ config.RunMode) artifactHasher { return test.hasher }) 131 details := cache.lookupArtifacts(context.Background(), map[string]string{"artifact": "tag"}, platform.Resolver{}, []*latest.Artifact{{ 132 ImageName: "artifact", 133 }}) 134 135 // cmp.Diff cannot access unexported fields in *exec.Cmd, so use reflect.DeepEqual here directly 136 if !reflect.DeepEqual(test.expected, details[0]) { 137 t.Errorf("Expected result different from actual result. Expected: \n%v, \nActual: \n%v", test.expected, details) 138 } 139 }) 140 } 141 } 142 143 func TestLookupRemote(t *testing.T) { 144 tests := []struct { 145 description string 146 hasher artifactHasher 147 cache map[string]ImageDetails 148 api *testutil.FakeAPIClient 149 expected cacheDetails 150 }{ 151 { 152 description: "miss", 153 hasher: mockHasher{"hash"}, 154 api: &testutil.FakeAPIClient{ErrImagePull: true}, 155 cache: map[string]ImageDetails{}, 156 expected: needsBuilding{hash: "hash"}, 157 }, 158 { 159 description: "hash failure", 160 hasher: failingHasher{errors.New("BUG")}, 161 expected: failed{err: errors.New("getting hash for artifact \"artifact\": BUG")}, 162 }, 163 { 164 description: "hit", 165 hasher: mockHasher{"hash"}, 166 cache: map[string]ImageDetails{ 167 "hash": {Digest: "digest"}, 168 }, 169 expected: found{hash: "hash"}, 170 }, 171 { 172 description: "hit with different tag", 173 hasher: mockHasher{"hash"}, 174 cache: map[string]ImageDetails{ 175 "hash": {Digest: "otherdigest"}, 176 }, 177 expected: needsRemoteTagging{hash: "hash", tag: "tag", digest: "otherdigest"}, 178 }, 179 { 180 description: "found locally", 181 hasher: mockHasher{"hash"}, 182 cache: map[string]ImageDetails{ 183 "hash": {ID: "imageID"}, 184 }, 185 api: (&testutil.FakeAPIClient{}).Add("tag", "imageID"), 186 expected: needsPushing{hash: "hash", tag: "tag", imageID: "imageID"}, 187 }, 188 { 189 description: "not found", 190 hasher: mockHasher{"hash"}, 191 cache: map[string]ImageDetails{ 192 "hash": {ID: "imageID"}, 193 }, 194 api: &testutil.FakeAPIClient{}, 195 expected: needsBuilding{hash: "hash"}, 196 }, 197 } 198 for _, test := range tests { 199 testutil.Run(t, test.description, func(t *testutil.T) { 200 t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config, _ []specs.Platform) (string, error) { 201 switch { 202 case identifier == "tag": 203 return "digest", nil 204 case identifier == "tag@otherdigest": 205 return "otherdigest", nil 206 default: 207 return "", errors.New("unknown remote tag") 208 } 209 }) 210 211 cache := &cache{ 212 isLocalImage: func(string) (bool, error) { return false, nil }, 213 importMissingImage: func(imageName string) (bool, error) { return false, nil }, 214 artifactCache: test.cache, 215 client: fakeLocalDaemon(test.api), 216 cfg: &mockConfig{mode: config.RunModes.Build}, 217 } 218 t.Override(&newArtifactHasherFunc, func(_ graph.ArtifactGraph, _ DependencyLister, _ config.RunMode) artifactHasher { return test.hasher }) 219 details := cache.lookupArtifacts(context.Background(), map[string]string{"artifact": "tag"}, platform.Resolver{}, []*latest.Artifact{{ 220 ImageName: "artifact", 221 }}) 222 223 // cmp.Diff cannot access unexported fields in *exec.Cmd, so use reflect.DeepEqual here directly 224 if !reflect.DeepEqual(test.expected, details[0]) { 225 t.Errorf("Expected result different from actual result. Expected: \n%v, \nActual: \n%v", test.expected, details) 226 } 227 }) 228 } 229 } 230 231 type mockHasher struct { 232 val string 233 } 234 235 func (m mockHasher) hash(context.Context, *latest.Artifact, platform.Resolver) (string, error) { 236 return m.val, nil 237 } 238 239 type failingHasher struct { 240 err error 241 } 242 243 func (f failingHasher) hash(context.Context, *latest.Artifact, platform.Resolver) (string, error) { 244 return "", f.err 245 } 246 247 func fakeLocalDaemon(api client.CommonAPIClient) docker.LocalDaemon { 248 return docker.NewLocalDaemon(api, nil, false, nil) 249 }