github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/jib/sync_test.go (about)

     1  /*
     2  Copyright 2020 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 jib
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/filemon"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    31  	"github.com/GoogleContainerTools/skaffold/testutil"
    32  )
    33  
    34  func TestGetSyncMapFromSystem(t *testing.T) {
    35  	tmpDir := testutil.NewTempDir(t)
    36  
    37  	tmpDir.Touch("dep1", "dir/dep2")
    38  	dep1 := tmpDir.Path("dep1")
    39  	dep2 := tmpDir.Path("dir/dep2")
    40  
    41  	dep1Time := getFileTime(dep1, t)
    42  	dep2Time := getFileTime(dep2, t)
    43  
    44  	dep1Target := "/target/dep1"
    45  	dep2Target := "/target/anotherDir/dep2"
    46  
    47  	tests := []struct {
    48  		description string
    49  		stdout      string
    50  		shouldErr   bool
    51  		expected    *SyncMap
    52  	}{
    53  		{
    54  			description: "empty",
    55  			stdout:      "",
    56  			shouldErr:   true,
    57  			expected:    nil,
    58  		},
    59  		{
    60  			description: "old style marker",
    61  			stdout:      "BEGIN JIB JSON\n{}",
    62  			shouldErr:   true,
    63  			expected:    nil,
    64  		},
    65  		{
    66  			description: "bad marker",
    67  			stdout:      "BEGIN JIB JSON: BAD/1\n{}",
    68  			shouldErr:   true,
    69  			expected:    nil,
    70  		},
    71  		{
    72  			description: "direct only",
    73  			stdout: "BEGIN JIB JSON: SYNCMAP/1\n" +
    74  				fmt.Sprintf(`{"direct":[{"src":"%s","dest":"%s"}]}`, escapeBackslashes(dep1), dep1Target),
    75  			shouldErr: false,
    76  			expected: &SyncMap{
    77  				dep1: SyncEntry{
    78  					[]string{dep1Target},
    79  					dep1Time,
    80  					true,
    81  				},
    82  			},
    83  		},
    84  		{
    85  			description: "generated only",
    86  			stdout: "BEGIN JIB JSON: SYNCMAP/1\n" +
    87  				fmt.Sprintf(`{"generated":[{"src":"%s","dest":"%s"}]}`, escapeBackslashes(dep1), dep1Target),
    88  			shouldErr: false,
    89  			expected: &SyncMap{
    90  				dep1: SyncEntry{
    91  					[]string{dep1Target},
    92  					dep1Time,
    93  					false,
    94  				},
    95  			},
    96  		},
    97  		{
    98  			description: "generated and direct",
    99  			stdout: "BEGIN JIB JSON: SYNCMAP/1\n" +
   100  				fmt.Sprintf(`{"direct":[{"src":"%s","dest":"%s"}],"generated":[{"src":"%s","dest":"%s"}]}"`, escapeBackslashes(dep1), dep1Target, escapeBackslashes(dep2), dep2Target),
   101  			shouldErr: false,
   102  			expected: &SyncMap{
   103  				dep1: SyncEntry{
   104  					[]string{dep1Target},
   105  					dep1Time,
   106  					true,
   107  				},
   108  				dep2: SyncEntry{
   109  					Dest:     []string{dep2Target},
   110  					FileTime: dep2Time,
   111  					IsDirect: false,
   112  				},
   113  			},
   114  		},
   115  	}
   116  	for _, test := range tests {
   117  		testutil.Run(t, test.description, func(t *testutil.T) {
   118  			t.Override(&util.DefaultExecCommand, testutil.CmdRunOut(
   119  				"ignored",
   120  				test.stdout,
   121  			))
   122  
   123  			results, err := getSyncMapFromSystem(context.Background(), &exec.Cmd{Args: []string{"ignored"}})
   124  
   125  			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, results)
   126  		})
   127  	}
   128  }
   129  
   130  func TestGetSyncDiff(t *testing.T) {
   131  	tmpDir := testutil.NewTempDir(t)
   132  
   133  	ctx := context.Background()
   134  	workspace := "testworkspace"
   135  	expectedDelete := map[string][]string(nil)
   136  
   137  	tmpDir.Touch("build-def", "direct", "dir/generated")
   138  
   139  	buildFile := tmpDir.Path("build-def")
   140  
   141  	directFile := tmpDir.Path("direct")
   142  	directTarget := []string{"/target/direct"}
   143  	directFileTime := getFileTime(directFile, t)
   144  
   145  	generatedFile := tmpDir.Path("dir/generated")
   146  	generatedTarget := []string{"/target/anotherDir/generated"}
   147  	generatedFileTime := getFileTime(generatedFile, t)
   148  
   149  	newFile := tmpDir.Path("some/new/file")
   150  	newFileTarget := []string{"/target/some/new/file/place"}
   151  
   152  	currSyncMap := SyncMap{
   153  		directFile:    SyncEntry{directTarget, directFileTime, true},
   154  		generatedFile: SyncEntry{generatedTarget, generatedFileTime, false},
   155  	}
   156  
   157  	tests := []struct {
   158  		description      string
   159  		artifact         *latest.JibArtifact
   160  		events           filemon.Events
   161  		buildDefinitions []string
   162  		nextSyncMap      SyncMap
   163  		expectedCopy     map[string][]string
   164  		shouldErr        bool
   165  	}{
   166  		{
   167  			description:      "build file changed (nil, nil, nil)",
   168  			artifact:         &latest.JibArtifact{},
   169  			events:           filemon.Events{Modified: []string{buildFile}},
   170  			buildDefinitions: []string{buildFile},
   171  			expectedCopy:     nil,
   172  			shouldErr:        false,
   173  		},
   174  		{
   175  			description:      "something is deleted (nil, nil, nil)",
   176  			artifact:         &latest.JibArtifact{},
   177  			events:           filemon.Events{Deleted: []string{directFile}},
   178  			buildDefinitions: []string{},
   179  			expectedCopy:     nil,
   180  			shouldErr:        false,
   181  		},
   182  		{
   183  			description:      "only direct sync entries changed",
   184  			artifact:         &latest.JibArtifact{},
   185  			events:           filemon.Events{Modified: []string{directFile}},
   186  			buildDefinitions: []string{},
   187  			expectedCopy:     map[string][]string{directFile: directTarget},
   188  			shouldErr:        false,
   189  		},
   190  		{
   191  			description:      "only generated sync entries changed",
   192  			artifact:         &latest.JibArtifact{},
   193  			events:           filemon.Events{Modified: []string{generatedFile}},
   194  			buildDefinitions: []string{},
   195  			nextSyncMap: SyncMap{
   196  				directFile:    SyncEntry{directTarget, directFileTime, true},
   197  				generatedFile: SyncEntry{generatedTarget, time.Now(), false},
   198  			},
   199  			expectedCopy: map[string][]string{generatedFile: generatedTarget},
   200  			shouldErr:    false,
   201  		},
   202  		{
   203  			description:      "generated and direct sync entries changed",
   204  			artifact:         &latest.JibArtifact{},
   205  			events:           filemon.Events{Modified: []string{directFile, generatedFile}},
   206  			buildDefinitions: []string{},
   207  			nextSyncMap: SyncMap{
   208  				directFile:    SyncEntry{directTarget, time.Now(), true},
   209  				generatedFile: SyncEntry{generatedTarget, time.Now(), false},
   210  			},
   211  			expectedCopy: map[string][]string{directFile: directTarget, generatedFile: generatedTarget},
   212  			shouldErr:    false,
   213  		},
   214  		{
   215  			description:      "new file created",
   216  			artifact:         &latest.JibArtifact{},
   217  			events:           filemon.Events{Added: []string{newFile}},
   218  			buildDefinitions: []string{},
   219  			nextSyncMap: SyncMap{
   220  				directFile:    SyncEntry{directTarget, directFileTime, true},
   221  				generatedFile: SyncEntry{generatedTarget, generatedFileTime, false},
   222  				newFile:       SyncEntry{newFileTarget, time.Now(), false},
   223  			},
   224  			expectedCopy: map[string][]string{newFile: newFileTarget},
   225  			shouldErr:    false,
   226  		},
   227  	}
   228  
   229  	for _, test := range tests {
   230  		testutil.Run(t, test.description, func(t *testutil.T) {
   231  			t.Override(&getSyncMapFunc, func(_ context.Context, _ string, _ *latest.JibArtifact) (*SyncMap, error) {
   232  				return &test.nextSyncMap, nil
   233  			})
   234  			pk := getProjectKey(workspace, test.artifact)
   235  			t.Override(&watchedFiles, map[projectKey]filesLists{
   236  				pk: {BuildDefinitions: test.buildDefinitions},
   237  			})
   238  			t.Override(&syncLists, map[projectKey]SyncMap{
   239  				pk: currSyncMap,
   240  			})
   241  
   242  			toCopy, toDelete, err := GetSyncDiff(ctx, workspace, test.artifact, test.events)
   243  
   244  			t.CheckError(test.shouldErr, err)
   245  			t.CheckDeepEqual(test.expectedCopy, toCopy)
   246  			t.CheckDeepEqual(expectedDelete, toDelete)
   247  		})
   248  	}
   249  }
   250  
   251  func TestGetSyncDiff_directChecksUpdateFileTime(testing *testing.T) {
   252  	tmpDir := testutil.NewTempDir(testing)
   253  
   254  	ctx := context.Background()
   255  	workspace := "testworkspace"
   256  	artifact := &latest.JibArtifact{}
   257  
   258  	tmpDir.Touch("direct")
   259  
   260  	directFile := tmpDir.Path("direct")
   261  	directTarget := []string{"/target/direct"}
   262  	directFileTime := getFileTime(directFile, testing)
   263  
   264  	currSyncMap := SyncMap{
   265  		directFile: SyncEntry{directTarget, directFileTime, true},
   266  	}
   267  
   268  	testutil.Run(testing, "Checks on direct files also update file times", func(t *testutil.T) {
   269  		pk := getProjectKey(workspace, artifact)
   270  		t.Override(&getSyncMapFunc, func(_ context.Context, _ string, _ *latest.JibArtifact) (*SyncMap, error) {
   271  			t.Fatal("getSyncMapFunc should not have been called in this test")
   272  			return nil, nil
   273  		})
   274  		t.Override(&watchedFiles, map[projectKey]filesLists{
   275  			pk: {BuildDefinitions: []string{}},
   276  		})
   277  		t.Override(&syncLists, map[projectKey]SyncMap{
   278  			pk: currSyncMap,
   279  		})
   280  
   281  		// turns out macOS doesn't exactly set the time you pass to Chtimes so set the time and then read it in.
   282  		tmpDir.Chtimes("direct", time.Now())
   283  		updatedFileTime := getFileTime(directFile, testing)
   284  
   285  		_, _, err := GetSyncDiff(ctx, workspace, artifact, filemon.Events{Modified: []string{directFile}})
   286  
   287  		t.CheckNoError(err)
   288  		t.CheckDeepEqual(SyncMap{directFile: SyncEntry{directTarget, updatedFileTime, true}}, syncLists[pk])
   289  	})
   290  }
   291  
   292  func getFileTime(file string, t *testing.T) time.Time {
   293  	info, err := os.Stat(file)
   294  	if err != nil {
   295  		t.Fatalf("Failed to stat %s", file)
   296  		return time.Time{}
   297  	}
   298  	return info.ModTime()
   299  }
   300  
   301  // for paths that contain "\", they must be escaped in json strings
   302  func escapeBackslashes(path string) string {
   303  	return strings.ReplaceAll(path, `\`, `\\`)
   304  }