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  }