github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/test/custom/custom_test.go (about) 1 /* 2 Copyright 2021 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 custom 18 19 import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "path/filepath" 24 "runtime" 25 "testing" 26 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 31 "github.com/GoogleContainerTools/skaffold/testutil" 32 testEvent "github.com/GoogleContainerTools/skaffold/testutil/event" 33 ) 34 35 func fakeLocalDaemonWithExtraEnv(extraEnv []string) docker.LocalDaemon { 36 return docker.NewLocalDaemon(&testutil.FakeAPIClient{}, extraEnv, false, nil) 37 } 38 39 func TestNewCustomTestRunner(t *testing.T) { 40 testutil.Run(t, "Testing new custom test runner", func(t *testutil.T) { 41 if runtime.GOOS == Windows { 42 t.Override(&util.DefaultExecCommand, testutil.CmdRun("cmd.exe /C echo Running Custom Test command.")) 43 } else { 44 t.Override(&util.DefaultExecCommand, testutil.CmdRun("sh -c echo Running Custom Test command.")) 45 } 46 t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) { 47 return fakeLocalDaemonWithExtraEnv([]string{}), nil 48 }) 49 50 tmpDir := t.NewTempDir().Touch("test.yaml") 51 52 custom := latest.CustomTest{ 53 Command: "echo Running Custom Test command.", 54 TimeoutSeconds: 10, 55 Dependencies: &latest.CustomTestDependencies{ 56 Paths: []string{"**"}, 57 Ignore: []string{"b*"}, 58 }, 59 } 60 61 testCase := &latest.TestCase{ 62 ImageName: "image", 63 Workspace: tmpDir.Root(), 64 CustomTests: []latest.CustomTest{custom}, 65 } 66 67 cfg := &mockConfig{ 68 tests: []*latest.TestCase{testCase}, 69 } 70 testEvent.InitializeState([]latest.Pipeline{{}}) 71 72 testRunner, err := New(cfg, testCase.ImageName, testCase.Workspace, custom) 73 t.CheckNoError(err) 74 err = testRunner.Test(context.Background(), ioutil.Discard, "image:tag") 75 76 t.CheckNoError(err) 77 }) 78 } 79 80 func TestCustomCommandError(t *testing.T) { 81 tests := []struct { 82 description string 83 custom latest.CustomTest 84 shouldErr bool 85 expectedCmd string 86 expectedWindowsCmd string 87 expectedError string 88 }{ 89 { 90 description: "Non zero exit", 91 custom: latest.CustomTest{ 92 Command: "exit 20", 93 }, 94 shouldErr: true, 95 expectedCmd: "sh -c exit 20", 96 expectedWindowsCmd: "cmd.exe /C exit 20", 97 expectedError: "exit status 20", 98 }, 99 { 100 description: "Command timed out", 101 custom: latest.CustomTest{ 102 Command: "sleep 20", 103 TimeoutSeconds: 2, 104 }, 105 shouldErr: true, 106 expectedCmd: "sh -c sleep 20", 107 expectedWindowsCmd: "cmd.exe /C sleep 20", 108 expectedError: "context deadline exceeded", 109 }, 110 } 111 for _, test := range tests { 112 testutil.Run(t, "Testing new custom test runner", func(t *testutil.T) { 113 tmpDir := t.NewTempDir().Touch("test.yaml") 114 command := test.expectedCmd 115 if runtime.GOOS == Windows { 116 command = test.expectedWindowsCmd 117 } 118 t.Override(&util.DefaultExecCommand, testutil.CmdRunErr(command, fmt.Errorf(test.expectedError))) 119 t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) { 120 return fakeLocalDaemonWithExtraEnv([]string{}), nil 121 }) 122 123 testCase := &latest.TestCase{ 124 ImageName: "image", 125 Workspace: tmpDir.Root(), 126 CustomTests: []latest.CustomTest{test.custom}, 127 } 128 129 cfg := &mockConfig{ 130 tests: []*latest.TestCase{testCase}, 131 } 132 testEvent.InitializeState([]latest.Pipeline{{}}) 133 134 testRunner, err := New(cfg, testCase.ImageName, testCase.Workspace, test.custom) 135 t.CheckNoError(err) 136 err = testRunner.Test(context.Background(), ioutil.Discard, "image:tag") 137 138 // TODO(modali): Update the logic to check for error code instead of error string. 139 t.CheckError(test.shouldErr, err) 140 if test.expectedError != "" { 141 t.CheckErrorContains(test.expectedError, err) 142 } 143 }) 144 } 145 } 146 147 func TestTestDependenciesCommand(t *testing.T) { 148 testutil.Run(t, "Testing new custom test runner", func(t *testutil.T) { 149 tmpDir := t.NewTempDir().Touch("test.yaml") 150 151 custom := latest.CustomTest{ 152 Command: "echo Hello!", 153 Dependencies: &latest.CustomTestDependencies{ 154 Command: "echo [\"file1\",\"file2\",\"file3\"]", 155 }, 156 } 157 158 testCase := &latest.TestCase{ 159 ImageName: "image", 160 Workspace: tmpDir.Root(), 161 CustomTests: []latest.CustomTest{custom}, 162 } 163 164 cfg := &mockConfig{ 165 tests: []*latest.TestCase{testCase}, 166 } 167 testEvent.InitializeState([]latest.Pipeline{{}}) 168 169 if runtime.GOOS == Windows { 170 t.Override(&util.DefaultExecCommand, testutil.CmdRunOut( 171 "cmd.exe /C echo [\"file1\",\"file2\",\"file3\"]", 172 "[\"file1\",\"file2\",\"file3\"]", 173 )) 174 } else { 175 t.Override(&util.DefaultExecCommand, testutil.CmdRunOut( 176 "sh -c echo [\"file1\",\"file2\",\"file3\"]", 177 "[\"file1\",\"file2\",\"file3\"]", 178 )) 179 } 180 181 expected := []string{"file1", "file2", "file3"} 182 testRunner, err := New(cfg, testCase.ImageName, testCase.Workspace, custom) 183 t.CheckNoError(err) 184 deps, err := testRunner.TestDependencies(context.Background()) 185 186 t.CheckNoError(err) 187 t.CheckDeepEqual(expected, deps) 188 }) 189 } 190 191 func TestTestDependenciesPaths(t *testing.T) { 192 tests := []struct { 193 description string 194 ignore []string 195 paths []string 196 expected []string 197 shouldErr bool 198 }{ 199 { 200 description: "watch everything", 201 paths: []string{"."}, 202 expected: []string{"bar", filepath.FromSlash("baz/file"), "foo"}, 203 }, 204 { 205 description: "watch nothing", 206 }, 207 { 208 description: "ignore some paths", 209 paths: []string{"."}, 210 ignore: []string{"b*"}, 211 expected: []string{"foo"}, 212 }, 213 { 214 description: "glob", 215 paths: []string{"**"}, 216 expected: []string{"bar", filepath.FromSlash("baz/file"), "foo"}, 217 }, 218 { 219 description: "error", 220 paths: []string{"unknown"}, 221 shouldErr: true, 222 }, 223 } 224 for _, test := range tests { 225 testutil.Run(t, test.description, func(t *testutil.T) { 226 // Directory structure: 227 // foo 228 // bar 229 // - baz 230 // file 231 tmpDir := t.NewTempDir(). 232 Touch("foo", "bar", "baz/file") 233 234 custom := latest.CustomTest{ 235 Command: "echo Hello!", 236 Dependencies: &latest.CustomTestDependencies{ 237 Paths: test.paths, 238 Ignore: test.ignore, 239 }, 240 } 241 242 testCase := &latest.TestCase{ 243 ImageName: "image", 244 Workspace: tmpDir.Root(), 245 CustomTests: []latest.CustomTest{custom}, 246 } 247 248 cfg := &mockConfig{ 249 tests: []*latest.TestCase{testCase}, 250 } 251 testEvent.InitializeState([]latest.Pipeline{{}}) 252 253 testRunner, err := New(cfg, testCase.ImageName, testCase.Workspace, custom) 254 t.CheckNoError(err) 255 deps, err := testRunner.TestDependencies(context.Background()) 256 257 t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, deps) 258 }) 259 } 260 } 261 262 func TestGetEnv(t *testing.T) { 263 tests := []struct { 264 description string 265 tag string 266 testContext string 267 environ []string 268 expected []string 269 extraEnv []string 270 }{ 271 272 { 273 description: "make sure tags are correct", 274 tag: "gcr.io/image/tag:mytag", 275 environ: nil, 276 testContext: "/some/path", 277 expected: []string{"IMAGE=gcr.io/image/tag:mytag", "TEST_CONTEXT=/some/path"}, 278 }, { 279 description: "make sure environ is correctly applied", 280 tag: "gcr.io/image/tag:anothertag", 281 environ: []string{"PATH=/path", "HOME=/root"}, 282 testContext: "/some/path", 283 expected: []string{"IMAGE=gcr.io/image/tag:anothertag", "TEST_CONTEXT=/some/path", "PATH=/path", "HOME=/root"}, 284 }, 285 { 286 description: "make sure minikube docker env is applied when minikube profile present", 287 tag: "gcr.io/image/tag:anothertag", 288 environ: []string{"PATH=/path", "HOME=/root"}, 289 testContext: "/some/path", 290 expected: []string{"IMAGE=gcr.io/image/tag:anothertag", "TEST_CONTEXT=/some/path", "PATH=/path", "HOME=/root", 291 "DOCKER_CERT_PATH=/path/.minikube/certs", "DOCKER_HOST=tcp://192.168.49.2:2376", 292 "DOCKER_TLS_VERIFY=1", "MINIKUBE_ACTIVE_DOCKERD=minikube"}, 293 extraEnv: []string{"DOCKER_CERT_PATH=/path/.minikube/certs", "DOCKER_HOST=tcp://192.168.49.2:2376", 294 "DOCKER_TLS_VERIFY=1", "MINIKUBE_ACTIVE_DOCKERD=minikube"}, 295 }, 296 } 297 for _, test := range tests { 298 testutil.Run(t, test.description, func(t *testutil.T) { 299 t.Override(&util.OSEnviron, func() []string { return test.environ }) 300 t.Override(&testContext, func(string) (string, error) { return test.testContext, nil }) 301 t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) { 302 return fakeLocalDaemonWithExtraEnv(test.extraEnv), nil 303 }) 304 tmpDir := t.NewTempDir().Touch("test.yaml") 305 306 custom := latest.CustomTest{ 307 Command: "echo Running Custom Test command.", 308 } 309 310 testCase := &latest.TestCase{ 311 ImageName: "image", 312 Workspace: tmpDir.Root(), 313 CustomTests: []latest.CustomTest{custom}, 314 } 315 316 cfg := &mockConfig{ 317 tests: []*latest.TestCase{testCase}, 318 } 319 testEvent.InitializeState([]latest.Pipeline{{}}) 320 321 testRunner, err := New(cfg, testCase.ImageName, testCase.Workspace, custom) 322 t.CheckNoError(err) 323 actual, err := testRunner.getEnv(context.Background(), test.tag) 324 325 t.CheckNoError(err) 326 t.CheckDeepEqual(test.expected, actual) 327 }) 328 } 329 } 330 331 type mockConfig struct { 332 runcontext.RunContext // Embedded to provide the default values. 333 tests []*latest.TestCase 334 }