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 }