github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/util/diff/diff_test.go (about) 1 // Copyright 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // These tests depend on `diff` which is not available on Windows 16 //go:build !windows 17 18 // Package diff_test tests the diff package 19 package diff_test 20 21 import ( 22 "bufio" 23 "bytes" 24 "io" 25 "regexp" 26 "strings" 27 "testing" 28 29 "github.com/GoogleContainerTools/kpt/internal/printer/fake" 30 "github.com/GoogleContainerTools/kpt/internal/testutil" 31 "github.com/GoogleContainerTools/kpt/internal/testutil/pkgbuilder" 32 . "github.com/GoogleContainerTools/kpt/internal/util/diff" 33 "github.com/stretchr/testify/assert" 34 ) 35 36 func TestCommand_Diff(t *testing.T) { 37 testCases := map[string]struct { 38 reposChanges map[string][]testutil.Content 39 updatedLocal testutil.Content 40 fetchRef string 41 diffRef string 42 diffType Type 43 diffTool string 44 diffOpts string 45 expDiff string 46 hasLocalSubpackageChanges bool 47 }{ 48 49 // 1. add data to the upstream master branch 50 // 2. commit and tag the upstream master branch 51 // 3. add more data to the upstream master branch, commit it 52 // 4. create a local clone at the tag 53 // 5. add more data to the upstream master branch, commit it 54 // 6. Run remote diff between upstream and the local fork. 55 "remoteDiff": { 56 reposChanges: map[string][]testutil.Content{ 57 testutil.Upstream: { 58 { 59 Data: testutil.Dataset2, 60 Branch: "master", 61 Tag: "v2", 62 }, 63 { 64 Data: testutil.Dataset3, 65 }, 66 }, 67 }, 68 fetchRef: "v2", 69 diffRef: "master", 70 diffType: TypeRemote, 71 diffTool: "diff", 72 diffOpts: "-r -i -w", 73 expDiff: ` 74 41c41 75 < - containerPort: 80 76 --- 77 > - containerPort: 8081 78 27,29c27,29 79 < - name: "80" 80 < port: 80 81 < targetPort: 80 82 --- 83 > - name: "8081" 84 > port: 8081 85 > targetPort: 8081 86 `, 87 }, 88 89 // 1. add data to the upstream master branch 90 // 2. commit and tag the upstream master branch 91 // 3. add more data to the upstream master branch, commit it 92 // 4. create a local clone at the tag 93 // 5. add more data to the upstream master branch, commit it 94 // 6. Run combined diff between upstream and the local fork 95 "combinedDiff": { 96 reposChanges: map[string][]testutil.Content{ 97 testutil.Upstream: { 98 { 99 Data: testutil.Dataset2, 100 Branch: "master", 101 Tag: "v2", 102 }, 103 { 104 Data: testutil.Dataset3, 105 }, 106 }, 107 }, 108 fetchRef: "v2", 109 diffRef: "master", 110 diffType: TypeCombined, 111 diffTool: "diff", 112 diffOpts: "-r -i -w", 113 expDiff: ` 114 41c41 115 < - containerPort: 80 116 --- 117 > - containerPort: 8081 118 27,29c27,29 119 < - name: "80" 120 < port: 80 121 < targetPort: 80 122 --- 123 > - name: "8081" 124 > port: 8081 125 > targetPort: 8081 126 `, 127 }, 128 129 // 1. add data to the upstream master branch 130 // 2. commit and tag the upstream master branch 131 // 3. add more data to the upstream master branch, commit it 132 // 4. create a local clone at the tag 133 // 5. add more data to the upstream master branch, commit it 134 // 6. Update the local fork with dataset3 135 // 7. Run remote diff and verify the output 136 "localDiff": { 137 reposChanges: map[string][]testutil.Content{ 138 testutil.Upstream: { 139 { 140 Data: testutil.Dataset2, 141 Branch: "master", 142 Tag: "v2", 143 }, 144 }, 145 }, 146 updatedLocal: testutil.Content{ 147 Data: testutil.Dataset3, 148 }, 149 fetchRef: "v2", 150 diffRef: "master", 151 diffType: TypeCombined, 152 diffTool: "diff", 153 diffOpts: "-r -i -w", 154 expDiff: ` 155 41c41 156 < - containerPort: 8081 157 --- 158 > - containerPort: 80 159 27,29c27,29 160 < - name: "8081" 161 < port: 8081 162 < targetPort: 8081 163 --- 164 > - name: "80" 165 > port: 80 166 > targetPort: 80 167 `, 168 }, 169 "nested local packages updated in local": { 170 reposChanges: map[string][]testutil.Content{ 171 testutil.Upstream: { 172 { 173 Pkg: pkgbuilder.NewRootPkg(). 174 WithResource(pkgbuilder.DeploymentResource), 175 Branch: "main", 176 }, 177 }, 178 }, 179 updatedLocal: testutil.Content{ 180 Pkg: pkgbuilder.NewRootPkg(). 181 WithKptfile( 182 pkgbuilder.NewKptfile(). 183 WithUpstreamRef(testutil.Upstream, "/", "main", "resource-merge"). 184 WithUpstreamLockRef(testutil.Upstream, "/", "main", 0), 185 ). 186 WithResource(pkgbuilder.DeploymentResource, 187 pkgbuilder.SetFieldPath("5", "spec", "replicas")). 188 WithSubPackages( 189 pkgbuilder.NewSubPkg("foo"). 190 WithKptfile(pkgbuilder.NewKptfile()). 191 WithResource(pkgbuilder.SecretResource). 192 WithResource(pkgbuilder.DeploymentResource, pkgbuilder.SetFieldPath("2", "spec", "replicas")), 193 ), 194 }, 195 fetchRef: "main", 196 diffRef: "main", 197 diffType: TypeCombined, 198 diffTool: "diff", 199 diffOpts: "-r -i -w", 200 expDiff: ` 201 9c9 202 < replicas: 5 203 --- 204 > replicas: 3 205 locally changed: foo 206 `, 207 hasLocalSubpackageChanges: true, 208 }, 209 "nested remote packages updated in local": { 210 reposChanges: map[string][]testutil.Content{ 211 testutil.Upstream: { 212 { 213 Pkg: pkgbuilder.NewRootPkg(). 214 WithResource(pkgbuilder.DeploymentResource), 215 Branch: "main", 216 }, 217 }, 218 "foo": { 219 { 220 Pkg: pkgbuilder.NewRootPkg(). 221 WithResource(pkgbuilder.SecretResource), 222 Branch: "master", 223 }, 224 }, 225 }, 226 updatedLocal: testutil.Content{ 227 Pkg: pkgbuilder.NewRootPkg(). 228 WithKptfile( 229 pkgbuilder.NewKptfile(). 230 WithUpstreamRef(testutil.Upstream, "/", "main", "resource-merge"). 231 WithUpstreamLockRef(testutil.Upstream, "/", "main", 0), 232 ). 233 WithResource(pkgbuilder.DeploymentResource, 234 pkgbuilder.SetFieldPath("5", "spec", "replicas")). 235 WithSubPackages( 236 pkgbuilder.NewSubPkg("foo"). 237 WithKptfile( 238 pkgbuilder.NewKptfile(). 239 WithUpstreamRef("foo", "/", "master", "resource-merge"). 240 WithUpstreamLockRef("foo", "/", "master", 0), 241 ). 242 WithResource(pkgbuilder.SecretResource). 243 WithResource(pkgbuilder.DeploymentResource, pkgbuilder.SetFieldPath("2", "spec", "replicas")), 244 ), 245 }, 246 fetchRef: "main", 247 diffRef: "main", 248 diffType: TypeCombined, 249 diffTool: "diff", 250 diffOpts: "-r -i -w", 251 expDiff: ` 252 9c9 253 < replicas: 5 254 --- 255 > replicas: 3 256 `, 257 }, 258 259 //nolint:gocritic 260 // TODO(mortent): Diff functionality must be updated to handle nested packages. 261 "nested remote package updated in upstream": { 262 reposChanges: map[string][]testutil.Content{ 263 testutil.Upstream: { 264 { 265 Pkg: pkgbuilder.NewRootPkg(). 266 WithResource(pkgbuilder.DeploymentResource), 267 Branch: "main", 268 }, 269 { 270 Pkg: pkgbuilder.NewRootPkg(). 271 WithResource(pkgbuilder.DeploymentResource, 272 pkgbuilder.SetFieldPath("5", "spec", "replicas")), 273 }, 274 }, 275 "foo": { 276 { 277 Pkg: pkgbuilder.NewRootPkg(). 278 WithResource(pkgbuilder.SecretResource), 279 Branch: "master", 280 }, 281 }, 282 }, 283 updatedLocal: testutil.Content{ 284 Pkg: pkgbuilder.NewRootPkg(). 285 WithKptfile( 286 pkgbuilder.NewKptfile(). 287 WithUpstreamRef(testutil.Upstream, "/", "main", "resource-merge"). 288 WithUpstreamLockRef(testutil.Upstream, "/", "main", 0), 289 ). 290 WithResource(pkgbuilder.DeploymentResource). 291 WithSubPackages( 292 pkgbuilder.NewSubPkg("foo"). 293 WithKptfile( 294 pkgbuilder.NewKptfile(). 295 WithUpstreamRef("foo", "/", "master", "resource-merge"). 296 WithUpstreamLockRef("foo", "/", "master", 0), 297 ). 298 WithResource(pkgbuilder.SecretResource), 299 ), 300 }, 301 fetchRef: "main", 302 diffRef: "main", 303 diffType: TypeCombined, 304 diffTool: "diff", 305 diffOpts: "-r -i -w", 306 expDiff: ` 307 9c9 308 < replicas: 3 309 --- 310 > replicas: 5 311 `, 312 }, 313 } 314 315 for tn, tc := range testCases { 316 t.Run(tn, func(t *testing.T) { 317 g := &testutil.TestSetupManager{ 318 T: t, 319 ReposChanges: tc.reposChanges, 320 GetRef: tc.fetchRef, 321 } 322 defer g.Clean() 323 324 if tc.updatedLocal.Pkg != nil || len(tc.updatedLocal.Data) > 0 { 325 g.LocalChanges = []testutil.Content{ 326 tc.updatedLocal, 327 } 328 } 329 if !g.Init() { 330 return 331 } 332 333 diffOutput := &bytes.Buffer{} 334 err := (&Command{ 335 Path: g.LocalWorkspace.FullPackagePath(), 336 Ref: tc.diffRef, 337 DiffType: tc.diffType, 338 DiffTool: tc.diffTool, 339 DiffToolOpts: tc.diffOpts, 340 Output: diffOutput, 341 }).Run(fake.CtxWithDefaultPrinter()) 342 if !assert.NoError(t, err) { 343 t.FailNow() 344 } 345 346 filteredOutput := filterDiffMetadata(diffOutput) 347 if tc.hasLocalSubpackageChanges { 348 filteredOutput = regexp.MustCompile("Only in /(tmp|var).+:").ReplaceAllString(filteredOutput, "locally changed:") 349 } 350 assert.Equal(t, strings.TrimSpace(tc.expDiff)+"\n", filteredOutput) 351 }) 352 } 353 } 354 355 func TestCommand_InvalidRef(t *testing.T) { 356 reposChanges := map[string][]testutil.Content{ 357 testutil.Upstream: { 358 { 359 Data: testutil.Dataset2, 360 Branch: "master", 361 Tag: "v2", 362 }, 363 { 364 Data: testutil.Dataset3, 365 }, 366 }, 367 } 368 369 g := &testutil.TestSetupManager{ 370 T: t, 371 ReposChanges: reposChanges, 372 GetRef: "v2", 373 } 374 defer g.Clean() 375 376 if !g.Init() { 377 return 378 } 379 380 diffOutput := &bytes.Buffer{} 381 err := (&Command{ 382 Path: g.LocalWorkspace.FullPackagePath(), 383 Ref: "hurdygurdy", // ref should not exist in upstream 384 DiffType: TypeCombined, 385 DiffTool: "diff", 386 DiffToolOpts: "-r -i -w", 387 Output: diffOutput, 388 }).Run(fake.CtxWithDefaultPrinter()) 389 assert.Error(t, err) 390 391 assert.Contains(t, err.Error(), "unknown revision or path not in the working tree.") 392 } 393 394 // Validate that all three directories are staged and provided to diff command 395 func TestCommand_Diff3Parameters(t *testing.T) { 396 reposChanges := map[string][]testutil.Content{ 397 testutil.Upstream: { 398 { 399 Data: testutil.Dataset2, 400 Branch: "master", 401 Tag: "v2", 402 }, 403 { 404 Data: testutil.Dataset3, 405 }, 406 }, 407 } 408 409 g := &testutil.TestSetupManager{ 410 T: t, 411 ReposChanges: reposChanges, 412 GetRef: "v2", 413 } 414 defer g.Clean() 415 416 if !g.Init() { 417 return 418 } 419 420 diffOutput := &bytes.Buffer{} 421 err := (&Command{ 422 Path: g.LocalWorkspace.FullPackagePath(), 423 Ref: "master", 424 DiffType: Type3Way, 425 DiffTool: "echo", // this is a proxy for 3 way diffing to validate we pass proper values 426 DiffToolOpts: "", 427 Output: diffOutput, 428 }).Run(fake.CtxWithDefaultPrinter()) 429 assert.NoError(t, err) 430 431 // Expect 3 value to be printed (1 per source) 432 results := strings.Split(diffOutput.String(), " ") 433 assert.Equal(t, 3, len(results)) 434 // Validate diff argument ordering 435 assert.Contains(t, results[0], LocalPackageSource) 436 assert.Contains(t, results[1], RemotePackageSource) 437 assert.Contains(t, results[2], TargetRemotePackageSource) 438 } 439 440 // Tests against directories in different states 441 func TestCommand_NotAKptDirectory(t *testing.T) { 442 // Initial test setup 443 dir := t.TempDir() 444 445 testCases := map[string]struct { 446 directory string 447 }{ 448 "Directory Is Not Kpt Package": {directory: dir}, 449 "Directory Does Not Exist": {directory: "/not/a/directory"}, 450 } 451 452 for tn, tc := range testCases { 453 t.Run(tn, func(t *testing.T) { 454 diffOutput := &bytes.Buffer{} 455 cmdErr := (&Command{ 456 Path: tc.directory, 457 Ref: "master", 458 DiffType: TypeCombined, 459 DiffTool: "diff", 460 DiffToolOpts: "-r -i -w", 461 Output: diffOutput, 462 }).Run(fake.CtxWithDefaultPrinter()) 463 assert.Error(t, cmdErr) 464 465 assert.Contains(t, cmdErr.Error(), "no such file or directory") 466 }) 467 } 468 } 469 470 // filterDiffMetadata removes information from the diff output that is test-run 471 // specific for ex. removing directory name being used. 472 func filterDiffMetadata(r io.Reader) string { 473 scanner := bufio.NewScanner(r) 474 b := &bytes.Buffer{} 475 476 for scanner.Scan() { 477 text := scanner.Text() 478 // filter out the diff command that contains directory names 479 if strings.HasPrefix(text, "diff ") { 480 continue 481 } 482 b.WriteString(text) 483 b.WriteString("\n") 484 } 485 return b.String() 486 } 487 488 func TestStagingDirectoryNames(t *testing.T) { 489 var tests = []struct { 490 source string 491 branch string 492 expected string 493 }{ 494 {"source", "branch", "source-branch"}, 495 {"source", "refs/tags/version", "source-version"}, 496 } 497 498 for i := range tests { 499 tt := tests[i] 500 t.Run(tt.expected, func(t *testing.T) { 501 result := NameStagingDirectory(tt.source, tt.branch) 502 assert.Equal(t, tt.expected, result) 503 }) 504 } 505 }