github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/git/v2/interactor_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes 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 git 18 19 import ( 20 "errors" 21 "fmt" 22 "reflect" 23 "testing" 24 25 "github.com/sirupsen/logrus" 26 27 "k8s.io/apimachinery/pkg/util/diff" 28 ) 29 30 func TestInteractor_Clone(t *testing.T) { 31 var testCases = []struct { 32 name string 33 dir string 34 from string 35 remote RemoteResolver 36 responses map[string]execResponse 37 expectedCalls [][]string 38 expectedErr bool 39 }{ 40 { 41 name: "happy case", 42 dir: "/secondaryclone", 43 from: "/mirrorclone", 44 responses: map[string]execResponse{ 45 "clone /mirrorclone /secondaryclone": { 46 out: []byte(`ok`), 47 }, 48 }, 49 expectedCalls: [][]string{ 50 {"clone", "/mirrorclone", "/secondaryclone"}, 51 }, 52 expectedErr: false, 53 }, 54 { 55 name: "clone fails", 56 dir: "/secondaryclone", 57 from: "/mirrorclone", 58 responses: map[string]execResponse{ 59 "clone /mirrorclone /secondaryclone": { 60 err: errors.New("oops"), 61 }, 62 }, 63 expectedCalls: [][]string{ 64 {"clone", "/mirrorclone", "/secondaryclone"}, 65 }, 66 expectedErr: true, 67 }, 68 } 69 70 for _, testCase := range testCases { 71 t.Run(testCase.name, func(t *testing.T) { 72 e := fakeExecutor{ 73 records: [][]string{}, 74 responses: testCase.responses, 75 } 76 i := interactor{ 77 executor: &e, 78 remote: testCase.remote, 79 dir: testCase.dir, 80 logger: logrus.WithField("test", testCase.name), 81 } 82 actualErr := i.Clone(testCase.from) 83 if testCase.expectedErr && actualErr == nil { 84 t.Errorf("%s: expected an error but got none", testCase.name) 85 } 86 if !testCase.expectedErr && actualErr != nil { 87 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 88 } 89 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 90 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 91 } 92 }) 93 } 94 } 95 96 func TestInteractor_CloneWithRepoOpts(t *testing.T) { 97 var testCases = []struct { 98 name string 99 dir string 100 from string 101 remote RemoteResolver 102 repoOpts RepoOpts 103 responses map[string]execResponse 104 expectedCalls [][]string 105 expectedErr bool 106 }{ 107 { 108 name: "blank RepoOpts", 109 dir: "/secondaryclone", 110 from: "/mirrorclone", 111 repoOpts: RepoOpts{}, 112 responses: map[string]execResponse{ 113 "clone /mirrorclone /secondaryclone": { 114 out: []byte(`ok`), 115 }, 116 }, 117 expectedCalls: [][]string{ 118 {"clone", "/mirrorclone", "/secondaryclone"}, 119 }, 120 expectedErr: false, 121 }, 122 { 123 name: "shared git objects", 124 dir: "/secondaryclone", 125 from: "/mirrorclone", 126 repoOpts: RepoOpts{ 127 SparseCheckoutDirs: nil, 128 ShareObjectsWithPrimaryClone: true, 129 }, 130 responses: map[string]execResponse{ 131 "clone --shared /mirrorclone /secondaryclone": { 132 out: []byte(`ok`), 133 }, 134 }, 135 expectedCalls: [][]string{ 136 {"clone", "--shared", "/mirrorclone", "/secondaryclone"}, 137 }, 138 expectedErr: false, 139 }, 140 { 141 name: "shared git objects and sparse checkout (toplevel only)", 142 dir: "/secondaryclone", 143 from: "/mirrorclone", 144 repoOpts: RepoOpts{ 145 SparseCheckoutDirs: []string{}, 146 ShareObjectsWithPrimaryClone: true, 147 }, 148 responses: map[string]execResponse{ 149 "clone --shared --sparse /mirrorclone /secondaryclone": { 150 out: []byte(`ok`), 151 }, 152 }, 153 expectedCalls: [][]string{ 154 {"clone", "--shared", "--sparse", "/mirrorclone", "/secondaryclone"}, 155 }, 156 expectedErr: false, 157 }, 158 { 159 name: "shared git objects and sparse checkout (toplevel+subdirs)", 160 dir: "/secondaryclone", 161 from: "/mirrorclone", 162 repoOpts: RepoOpts{ 163 SparseCheckoutDirs: []string{"a", "b"}, 164 ShareObjectsWithPrimaryClone: true, 165 }, 166 responses: map[string]execResponse{ 167 "clone --shared --sparse /mirrorclone /secondaryclone": { 168 out: []byte(`ok`), 169 }, 170 "-C /secondaryclone sparse-checkout set a b": { 171 out: []byte(`ok`), 172 }, 173 }, 174 expectedCalls: [][]string{ 175 {"clone", "--shared", "--sparse", "/mirrorclone", "/secondaryclone"}, 176 {"-C", "/secondaryclone", "sparse-checkout", "set", "a", "b"}, 177 }, 178 expectedErr: false, 179 }, 180 } 181 182 for _, testCase := range testCases { 183 t.Run(testCase.name, func(t *testing.T) { 184 e := fakeExecutor{ 185 records: [][]string{}, 186 responses: testCase.responses, 187 } 188 i := interactor{ 189 executor: &e, 190 remote: testCase.remote, 191 dir: testCase.dir, 192 logger: logrus.WithField("test", testCase.name), 193 } 194 actualErr := i.CloneWithRepoOpts(testCase.from, testCase.repoOpts) 195 if testCase.expectedErr && actualErr == nil { 196 t.Errorf("%s: expected an error but got none", testCase.name) 197 } 198 if !testCase.expectedErr && actualErr != nil { 199 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 200 } 201 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 202 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 203 } 204 }) 205 } 206 } 207 208 func TestInteractor_MirrorClone(t *testing.T) { 209 var testCases = []struct { 210 name string 211 dir string 212 remote RemoteResolver 213 responses map[string]execResponse 214 expectedCalls [][]string 215 expectedErr bool 216 }{ 217 { 218 name: "happy case", 219 dir: "/else", 220 remote: func() (string, error) { 221 return "someone.com", nil 222 }, 223 responses: map[string]execResponse{ 224 "clone --mirror someone.com /else": { 225 out: []byte(`ok`), 226 }, 227 }, 228 expectedCalls: [][]string{ 229 {"clone", "--mirror", "someone.com", "/else"}, 230 }, 231 expectedErr: false, 232 }, 233 { 234 name: "remote resolution fails", 235 dir: "/else", 236 remote: func() (string, error) { 237 return "", errors.New("oops") 238 }, 239 responses: map[string]execResponse{}, 240 expectedCalls: [][]string{}, 241 expectedErr: true, 242 }, 243 { 244 name: "clone fails", 245 dir: "/else", 246 remote: func() (string, error) { 247 return "someone.com", nil 248 }, 249 responses: map[string]execResponse{ 250 "clone --mirror someone.com /else": { 251 err: errors.New("oops"), 252 }, 253 }, 254 expectedCalls: [][]string{ 255 {"clone", "--mirror", "someone.com", "/else"}, 256 }, 257 expectedErr: true, 258 }, 259 } 260 261 for _, testCase := range testCases { 262 t.Run(testCase.name, func(t *testing.T) { 263 e := fakeExecutor{ 264 records: [][]string{}, 265 responses: testCase.responses, 266 } 267 i := interactor{ 268 executor: &e, 269 remote: testCase.remote, 270 dir: testCase.dir, 271 logger: logrus.WithField("test", testCase.name), 272 } 273 actualErr := i.MirrorClone() 274 if testCase.expectedErr && actualErr == nil { 275 t.Errorf("%s: expected an error but got none", testCase.name) 276 } 277 if !testCase.expectedErr && actualErr != nil { 278 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 279 } 280 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 281 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 282 } 283 }) 284 } 285 } 286 287 func TestInteractor_Checkout(t *testing.T) { 288 var testCases = []struct { 289 name string 290 commitlike string 291 remote RemoteResolver 292 responses map[string]execResponse 293 expectedCalls [][]string 294 expectedErr bool 295 }{ 296 { 297 name: "happy case", 298 commitlike: "shasum", 299 responses: map[string]execResponse{ 300 "checkout shasum": { 301 out: []byte(`ok`), 302 }, 303 }, 304 expectedCalls: [][]string{ 305 {"checkout", "shasum"}, 306 }, 307 expectedErr: false, 308 }, 309 { 310 name: "checkout fails", 311 commitlike: "shasum", 312 responses: map[string]execResponse{ 313 "checkout shasum": { 314 err: errors.New("oops"), 315 }, 316 }, 317 expectedCalls: [][]string{ 318 {"checkout", "shasum"}, 319 }, 320 expectedErr: true, 321 }, 322 } 323 324 for _, testCase := range testCases { 325 t.Run(testCase.name, func(t *testing.T) { 326 e := fakeExecutor{ 327 records: [][]string{}, 328 responses: testCase.responses, 329 } 330 i := interactor{ 331 executor: &e, 332 remote: testCase.remote, 333 logger: logrus.WithField("test", testCase.name), 334 } 335 actualErr := i.Checkout(testCase.commitlike) 336 if testCase.expectedErr && actualErr == nil { 337 t.Errorf("%s: expected an error but got none", testCase.name) 338 } 339 if !testCase.expectedErr && actualErr != nil { 340 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 341 } 342 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 343 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 344 } 345 }) 346 } 347 } 348 349 func TestInteractor_RevParse(t *testing.T) { 350 var testCases = []struct { 351 name string 352 commitlike string 353 remote RemoteResolver 354 responses map[string]execResponse 355 expectedCalls [][]string 356 expectedOut string 357 expectedErr bool 358 }{ 359 { 360 name: "happy case", 361 commitlike: "shasum", 362 responses: map[string]execResponse{ 363 "rev-parse shasum": { 364 out: []byte(`ok`), 365 }, 366 }, 367 expectedCalls: [][]string{ 368 {"rev-parse", "shasum"}, 369 }, 370 expectedOut: "ok", 371 expectedErr: false, 372 }, 373 { 374 name: "rev-parse fails", 375 commitlike: "shasum", 376 responses: map[string]execResponse{ 377 "rev-parse shasum": { 378 err: errors.New("oops"), 379 }, 380 }, 381 expectedCalls: [][]string{ 382 {"rev-parse", "shasum"}, 383 }, 384 expectedErr: true, 385 }, 386 } 387 388 for _, testCase := range testCases { 389 t.Run(testCase.name, func(t *testing.T) { 390 e := fakeExecutor{ 391 records: [][]string{}, 392 responses: testCase.responses, 393 } 394 i := interactor{ 395 executor: &e, 396 remote: testCase.remote, 397 logger: logrus.WithField("test", testCase.name), 398 } 399 actualOut, actualErr := i.RevParse(testCase.commitlike) 400 if testCase.expectedErr && actualErr == nil { 401 t.Errorf("%s: expected an error but got none", testCase.name) 402 } 403 if !testCase.expectedErr && actualErr != nil { 404 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 405 } 406 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 407 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 408 } 409 if actualOut != testCase.expectedOut { 410 t.Errorf("%s: got incorrect output: expected %v, got %v", testCase.name, testCase.expectedOut, actualOut) 411 } 412 }) 413 } 414 } 415 416 func TestInteractor_BranchExists(t *testing.T) { 417 var testCases = []struct { 418 name string 419 branch string 420 remote RemoteResolver 421 responses map[string]execResponse 422 expectedCalls [][]string 423 expectedOut bool 424 }{ 425 { 426 name: "happy case", 427 branch: "branch", 428 responses: map[string]execResponse{ 429 "ls-remote --exit-code --heads origin branch": { 430 out: []byte(`c165713776618ff3162643ea4d0382ca039adfeb refs/heads/branch`), 431 }, 432 }, 433 expectedCalls: [][]string{ 434 {"ls-remote", "--exit-code", "--heads", "origin", "branch"}, 435 }, 436 expectedOut: true, 437 }, 438 { 439 name: "ls-remote fails", 440 branch: "branch", 441 responses: map[string]execResponse{ 442 "ls-remote --exit-code --heads origin branch": { 443 err: errors.New("oops"), 444 }, 445 }, 446 expectedCalls: [][]string{ 447 {"ls-remote", "--exit-code", "--heads", "origin", "branch"}, 448 }, 449 expectedOut: false, 450 }, 451 } 452 453 for _, testCase := range testCases { 454 t.Run(testCase.name, func(t *testing.T) { 455 e := fakeExecutor{ 456 records: [][]string{}, 457 responses: testCase.responses, 458 } 459 i := interactor{ 460 executor: &e, 461 remote: testCase.remote, 462 logger: logrus.WithField("test", testCase.name), 463 } 464 actualOut := i.BranchExists(testCase.branch) 465 if testCase.expectedOut != actualOut { 466 t.Errorf("%s: got incorrect output: expected %v, got %v", testCase.name, testCase.expectedOut, actualOut) 467 } 468 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 469 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 470 } 471 }) 472 } 473 } 474 475 func TestInteractor_ObjectExists(t *testing.T) { 476 var testCases = []struct { 477 name string 478 object string 479 responses map[string]execResponse 480 expectedCalls [][]string 481 expectedOut bool 482 // ObjectExists always returns a nil error, so we don't test it. 483 }{ 484 { 485 name: "happy case", 486 object: "abc123", 487 responses: map[string]execResponse{ 488 "cat-file -e abc123": {out: []byte("")}, 489 }, 490 expectedCalls: [][]string{ 491 {"cat-file", "-e", "abc123"}, 492 }, 493 expectedOut: true, 494 }, 495 { 496 name: "Does not exist", 497 object: "000000", 498 responses: map[string]execResponse{ 499 "cat-file -e 000000": {out: []byte(""), err: errors.New("")}, 500 }, 501 expectedCalls: [][]string{ 502 {"cat-file", "-e", "000000"}, 503 }, 504 expectedOut: false, 505 }, 506 } 507 508 for _, testCase := range testCases { 509 t.Run(testCase.name, func(t *testing.T) { 510 e := fakeExecutor{ 511 records: [][]string{}, 512 responses: testCase.responses, 513 } 514 i := interactor{ 515 executor: &e, 516 logger: logrus.WithField("test", testCase.name), 517 } 518 actualOut, _ := i.ObjectExists(testCase.object) 519 if testCase.expectedOut != actualOut { 520 t.Errorf("%s: got incorrect output: expected %v, got %v", testCase.name, testCase.expectedOut, actualOut) 521 } 522 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 523 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 524 } 525 }) 526 } 527 } 528 529 func TestInteractor_CheckoutNewBranch(t *testing.T) { 530 var testCases = []struct { 531 name string 532 branch string 533 remote RemoteResolver 534 responses map[string]execResponse 535 expectedCalls [][]string 536 expectedErr bool 537 }{ 538 { 539 name: "happy case", 540 branch: "new-branch", 541 responses: map[string]execResponse{ 542 "checkout -b new-branch": { 543 out: []byte(`ok`), 544 }, 545 }, 546 expectedCalls: [][]string{ 547 {"checkout", "-b", "new-branch"}, 548 }, 549 expectedErr: false, 550 }, 551 { 552 name: "checkout fails", 553 branch: "new-branch", 554 responses: map[string]execResponse{ 555 "checkout -b new-branch": { 556 err: errors.New("oops"), 557 }, 558 }, 559 expectedCalls: [][]string{ 560 {"checkout", "-b", "new-branch"}, 561 }, 562 expectedErr: true, 563 }, 564 } 565 566 for _, testCase := range testCases { 567 t.Run(testCase.name, func(t *testing.T) { 568 e := fakeExecutor{ 569 records: [][]string{}, 570 responses: testCase.responses, 571 } 572 i := interactor{ 573 executor: &e, 574 remote: testCase.remote, 575 logger: logrus.WithField("test", testCase.name), 576 } 577 actualErr := i.CheckoutNewBranch(testCase.branch) 578 if testCase.expectedErr && actualErr == nil { 579 t.Errorf("%s: expected an error but got none", testCase.name) 580 } 581 if !testCase.expectedErr && actualErr != nil { 582 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 583 } 584 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 585 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 586 } 587 }) 588 } 589 } 590 591 func TestInteractor_Merge(t *testing.T) { 592 var testCases = []struct { 593 name string 594 commitlike string 595 remote RemoteResolver 596 responses map[string]execResponse 597 expectedCalls [][]string 598 expectedMerge bool 599 expectedErr bool 600 }{ 601 { 602 name: "happy case", 603 commitlike: "shasum", 604 responses: map[string]execResponse{ 605 "merge --no-ff --no-stat -m merge shasum": { 606 out: []byte(`ok`), 607 }, 608 }, 609 expectedCalls: [][]string{ 610 {"merge", "--no-ff", "--no-stat", "-m", "merge", "shasum"}, 611 }, 612 expectedMerge: true, 613 expectedErr: false, 614 }, 615 { 616 name: "merge fails but abort succeeds", 617 commitlike: "shasum", 618 responses: map[string]execResponse{ 619 "merge --no-ff --no-stat -m merge shasum": { 620 err: errors.New("oops"), 621 }, 622 "merge --abort": { 623 out: []byte(`ok`), 624 }, 625 }, 626 expectedCalls: [][]string{ 627 {"merge", "--no-ff", "--no-stat", "-m", "merge", "shasum"}, 628 {"merge", "--abort"}, 629 }, 630 expectedMerge: false, 631 expectedErr: false, 632 }, 633 { 634 name: "merge fails and abort fails", 635 commitlike: "shasum", 636 responses: map[string]execResponse{ 637 "merge --no-ff --no-stat -m merge shasum": { 638 err: errors.New("oops"), 639 }, 640 "merge --abort": { 641 err: errors.New("oops"), 642 }, 643 }, 644 expectedCalls: [][]string{ 645 {"merge", "--no-ff", "--no-stat", "-m", "merge", "shasum"}, 646 {"merge", "--abort"}, 647 }, 648 expectedMerge: false, 649 expectedErr: true, 650 }, 651 } 652 653 for _, testCase := range testCases { 654 t.Run(testCase.name, func(t *testing.T) { 655 e := fakeExecutor{ 656 records: [][]string{}, 657 responses: testCase.responses, 658 } 659 i := interactor{ 660 executor: &e, 661 remote: testCase.remote, 662 logger: logrus.WithField("test", testCase.name), 663 } 664 actualMerge, actualErr := i.Merge(testCase.commitlike) 665 if testCase.expectedMerge != actualMerge { 666 t.Errorf("%s: got incorrect output: expected %v, got %v", testCase.name, testCase.expectedMerge, actualMerge) 667 } 668 if testCase.expectedErr && actualErr == nil { 669 t.Errorf("%s: expected an error but got none", testCase.name) 670 } 671 if !testCase.expectedErr && actualErr != nil { 672 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 673 } 674 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 675 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 676 } 677 }) 678 } 679 } 680 681 func TestInteractor_MergeWithStrategy(t *testing.T) { 682 var testCases = []struct { 683 name string 684 commitlike string 685 strategy string 686 opts []MergeOpt 687 remote RemoteResolver 688 responses map[string]execResponse 689 expectedCalls [][]string 690 expectedMerge bool 691 expectedErr bool 692 }{ 693 { 694 name: "happy merge case", 695 commitlike: "shasum", 696 strategy: "merge", 697 responses: map[string]execResponse{ 698 "merge --no-ff --no-stat -m merge shasum": { 699 out: []byte(`ok`), 700 }, 701 }, 702 expectedCalls: [][]string{ 703 {"merge", "--no-ff", "--no-stat", "-m", "merge", "shasum"}, 704 }, 705 expectedMerge: true, 706 expectedErr: false, 707 }, 708 { 709 name: "happy merge case with options", 710 commitlike: "shasum", 711 strategy: "merge", 712 opts: []MergeOpt{{CommitMessage: "message"}}, 713 responses: map[string]execResponse{ 714 "merge --no-ff --no-stat -m message shasum": { 715 out: []byte(`ok`), 716 }, 717 }, 718 expectedCalls: [][]string{ 719 {"merge", "--no-ff", "--no-stat", "-m", "message", "shasum"}, 720 }, 721 expectedMerge: true, 722 expectedErr: false, 723 }, 724 { 725 name: "happy merge case with multi words message", 726 commitlike: "shasum", 727 strategy: "merge", 728 opts: []MergeOpt{{CommitMessage: "my happy merge message"}}, 729 responses: map[string]execResponse{ 730 "merge --no-ff --no-stat -m my happy merge message shasum": { 731 out: []byte(`ok`), 732 }, 733 }, 734 expectedCalls: [][]string{ 735 {"merge", "--no-ff", "--no-stat", "-m", "my happy merge message", "shasum"}, 736 }, 737 expectedMerge: true, 738 expectedErr: false, 739 }, 740 { 741 name: "happy merge case with multiple options with single/multi words message", 742 commitlike: "shasum", 743 strategy: "merge", 744 opts: []MergeOpt{ 745 {CommitMessage: "my"}, 746 {CommitMessage: "happy merge"}, 747 {CommitMessage: "message"}, 748 }, 749 responses: map[string]execResponse{ 750 "merge --no-ff --no-stat -m my -m happy merge -m message shasum": { 751 out: []byte(`ok`), 752 }, 753 }, 754 expectedCalls: [][]string{ 755 {"merge", "--no-ff", "--no-stat", "-m", "my", "-m", "happy merge", "-m", "message", "shasum"}, 756 }, 757 expectedMerge: true, 758 expectedErr: false, 759 }, 760 { 761 name: "happy squash case", 762 commitlike: "shasum", 763 strategy: "squash", 764 responses: map[string]execResponse{ 765 "merge --squash --no-stat shasum": { 766 out: []byte(`ok`), 767 }, 768 "commit --no-stat -m merge": { 769 out: []byte(`ok`), 770 }, 771 }, 772 expectedCalls: [][]string{ 773 {"merge", "--squash", "--no-stat", "shasum"}, 774 {"commit", "--no-stat", "-m", "merge"}, 775 }, 776 expectedMerge: true, 777 expectedErr: false, 778 }, 779 { 780 name: "invalid strategy", 781 commitlike: "shasum", 782 strategy: "whatever", 783 responses: map[string]execResponse{}, 784 expectedCalls: [][]string{}, 785 expectedMerge: false, 786 expectedErr: true, 787 }, 788 { 789 name: "merge fails but abort succeeds", 790 commitlike: "shasum", 791 strategy: "merge", 792 responses: map[string]execResponse{ 793 "merge --no-ff --no-stat -m merge shasum": { 794 err: errors.New("oops"), 795 }, 796 "merge --abort": { 797 out: []byte(`ok`), 798 }, 799 }, 800 expectedCalls: [][]string{ 801 {"merge", "--no-ff", "--no-stat", "-m", "merge", "shasum"}, 802 {"merge", "--abort"}, 803 }, 804 expectedMerge: false, 805 expectedErr: false, 806 }, 807 { 808 name: "merge fails and abort fails", 809 commitlike: "shasum", 810 strategy: "merge", 811 responses: map[string]execResponse{ 812 "merge --no-ff --no-stat -m merge shasum": { 813 err: errors.New("oops"), 814 }, 815 "merge --abort": { 816 err: errors.New("oops"), 817 }, 818 }, 819 expectedCalls: [][]string{ 820 {"merge", "--no-ff", "--no-stat", "-m", "merge", "shasum"}, 821 {"merge", "--abort"}, 822 }, 823 expectedMerge: false, 824 expectedErr: true, 825 }, 826 { 827 name: "squash merge fails but abort succeeds", 828 commitlike: "shasum", 829 strategy: "squash", 830 responses: map[string]execResponse{ 831 "merge --squash --no-stat shasum": { 832 err: errors.New("oops"), 833 }, 834 "reset --hard HEAD": { 835 out: []byte(`ok`), 836 }, 837 }, 838 expectedCalls: [][]string{ 839 {"merge", "--squash", "--no-stat", "shasum"}, 840 {"reset", "--hard", "HEAD"}, 841 }, 842 expectedMerge: false, 843 expectedErr: false, 844 }, 845 { 846 name: "squash merge fails and abort fails", 847 commitlike: "shasum", 848 strategy: "squash", 849 responses: map[string]execResponse{ 850 "merge --squash --no-stat shasum": { 851 err: errors.New("oops"), 852 }, 853 "reset --hard HEAD": { 854 err: errors.New("oops"), 855 }, 856 }, 857 expectedCalls: [][]string{ 858 {"merge", "--squash", "--no-stat", "shasum"}, 859 {"reset", "--hard", "HEAD"}, 860 }, 861 expectedMerge: false, 862 expectedErr: true, 863 }, 864 { 865 name: "squash merge staging succeeds, commit fails and abort succeeds", 866 commitlike: "shasum", 867 strategy: "squash", 868 responses: map[string]execResponse{ 869 "merge --squash --no-stat shasum": { 870 out: []byte(`ok`), 871 }, 872 "commit --no-stat -m merge": { 873 err: errors.New("oops"), 874 }, 875 "reset --hard HEAD": { 876 out: []byte(`ok`), 877 }, 878 }, 879 expectedCalls: [][]string{ 880 {"merge", "--squash", "--no-stat", "shasum"}, 881 {"commit", "--no-stat", "-m", "merge"}, 882 {"reset", "--hard", "HEAD"}, 883 }, 884 expectedMerge: false, 885 expectedErr: false, 886 }, 887 { 888 name: "squash merge staging succeeds, commit fails and abort fails", 889 commitlike: "shasum", 890 strategy: "squash", 891 responses: map[string]execResponse{ 892 "merge --squash --no-stat shasum": { 893 out: []byte(`ok`), 894 }, 895 "commit --no-stat -m merge": { 896 err: errors.New("oops"), 897 }, 898 "reset --hard HEAD": { 899 err: errors.New("oops"), 900 }, 901 }, 902 expectedCalls: [][]string{ 903 {"merge", "--squash", "--no-stat", "shasum"}, 904 {"commit", "--no-stat", "-m", "merge"}, 905 {"reset", "--hard", "HEAD"}, 906 }, 907 expectedMerge: false, 908 expectedErr: true, 909 }, 910 } 911 912 for _, testCase := range testCases { 913 t.Run(testCase.name, func(t *testing.T) { 914 e := fakeExecutor{ 915 records: [][]string{}, 916 responses: testCase.responses, 917 } 918 i := interactor{ 919 executor: &e, 920 remote: testCase.remote, 921 logger: logrus.WithField("test", testCase.name), 922 } 923 actualMerge, actualErr := i.MergeWithStrategy(testCase.commitlike, testCase.strategy, testCase.opts...) 924 if testCase.expectedMerge != actualMerge { 925 t.Errorf("%s: got incorrect output: expected %v, got %v", testCase.name, testCase.expectedMerge, actualMerge) 926 } 927 if testCase.expectedErr && actualErr == nil { 928 t.Errorf("%s: expected an error but got none", testCase.name) 929 } 930 if !testCase.expectedErr && actualErr != nil { 931 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 932 } 933 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 934 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 935 } 936 }) 937 } 938 } 939 940 func TestInteractor_MergeAndCheckout(t *testing.T) { 941 var testCases = []struct { 942 name string 943 baseSHA string 944 commitlikes []string 945 strategy string 946 remote RemoteResolver 947 responses map[string]execResponse 948 expectedCalls [][]string 949 expectedErr bool 950 }{ 951 { 952 name: "happy do nothing case", 953 baseSHA: "base", 954 commitlikes: []string{}, 955 strategy: "merge", 956 responses: map[string]execResponse{ 957 "checkout base": { 958 out: []byte(`ok`), 959 }, 960 }, 961 expectedCalls: [][]string{ 962 {"checkout", "base"}, 963 }, 964 expectedErr: false, 965 }, 966 { 967 name: "happy merge case", 968 baseSHA: "base", 969 commitlikes: []string{"first", "second"}, 970 strategy: "merge", 971 responses: map[string]execResponse{ 972 "checkout base": { 973 out: []byte(`ok`), 974 }, 975 "merge --no-ff --no-stat -m merge first": { 976 out: []byte(`ok`), 977 }, 978 "merge --no-ff --no-stat -m merge second": { 979 out: []byte(`ok`), 980 }, 981 }, 982 expectedCalls: [][]string{ 983 {"checkout", "base"}, 984 {"merge", "--no-ff", "--no-stat", "-m", "merge", "first"}, 985 {"merge", "--no-ff", "--no-stat", "-m", "merge", "second"}, 986 }, 987 expectedErr: false, 988 }, 989 { 990 name: "happy squash case", 991 baseSHA: "base", 992 commitlikes: []string{"first", "second"}, 993 strategy: "squash", 994 responses: map[string]execResponse{ 995 "checkout base": { 996 out: []byte(`ok`), 997 }, 998 "merge --squash --no-stat first": { 999 out: []byte(`ok`), 1000 }, 1001 "commit --no-stat -m merge": { 1002 out: []byte(`ok`), 1003 }, 1004 "merge --squash --no-stat second": { 1005 out: []byte(`ok`), 1006 }, 1007 }, 1008 expectedCalls: [][]string{ 1009 {"checkout", "base"}, 1010 {"merge", "--squash", "--no-stat", "first"}, 1011 {"commit", "--no-stat", "-m", "merge"}, 1012 {"merge", "--squash", "--no-stat", "second"}, 1013 {"commit", "--no-stat", "-m", "merge"}, 1014 }, 1015 expectedErr: false, 1016 }, 1017 { 1018 name: "invalid strategy", 1019 commitlikes: []string{"shasum"}, 1020 strategy: "whatever", 1021 responses: map[string]execResponse{}, 1022 expectedCalls: [][]string{}, 1023 expectedErr: true, 1024 }, 1025 { 1026 name: "checkout fails", 1027 baseSHA: "base", 1028 commitlikes: []string{"first", "second"}, 1029 strategy: "squash", 1030 responses: map[string]execResponse{ 1031 "checkout base": { 1032 err: errors.New("oops"), 1033 }, 1034 }, 1035 expectedCalls: [][]string{ 1036 {"checkout", "base"}, 1037 }, 1038 expectedErr: true, 1039 }, 1040 { 1041 name: "merge fails but abort succeeds", 1042 baseSHA: "base", 1043 commitlikes: []string{"first", "second"}, 1044 strategy: "merge", 1045 responses: map[string]execResponse{ 1046 "checkout base": { 1047 out: []byte(`ok`), 1048 }, 1049 "merge --no-ff --no-stat -m merge first": { 1050 err: errors.New("oops"), 1051 }, 1052 "merge --abort": { 1053 out: []byte(`ok`), 1054 }, 1055 }, 1056 expectedCalls: [][]string{ 1057 {"checkout", "base"}, 1058 {"merge", "--no-ff", "--no-stat", "-m", "merge", "first"}, 1059 {"merge", "--abort"}, 1060 }, 1061 expectedErr: true, 1062 }, 1063 { 1064 name: "merge fails and abort fails", 1065 baseSHA: "base", 1066 commitlikes: []string{"first", "second"}, 1067 strategy: "merge", 1068 responses: map[string]execResponse{ 1069 "checkout base": { 1070 out: []byte(`ok`), 1071 }, 1072 "merge --no-ff --no-stat -m merge first": { 1073 err: errors.New("oops"), 1074 }, 1075 "merge --abort": { 1076 err: errors.New("oops"), 1077 }, 1078 }, 1079 expectedCalls: [][]string{ 1080 {"checkout", "base"}, 1081 {"merge", "--no-ff", "--no-stat", "-m", "merge", "first"}, 1082 {"merge", "--abort"}, 1083 }, 1084 expectedErr: true, 1085 }, 1086 } 1087 1088 for _, testCase := range testCases { 1089 t.Run(testCase.name, func(t *testing.T) { 1090 e := fakeExecutor{ 1091 records: [][]string{}, 1092 responses: testCase.responses, 1093 } 1094 i := interactor{ 1095 executor: &e, 1096 remote: testCase.remote, 1097 logger: logrus.WithField("test", testCase.name), 1098 } 1099 actualErr := i.MergeAndCheckout(testCase.baseSHA, testCase.strategy, testCase.commitlikes...) 1100 if testCase.expectedErr && actualErr == nil { 1101 t.Errorf("%s: expected an error but got none", testCase.name) 1102 } 1103 if !testCase.expectedErr && actualErr != nil { 1104 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1105 } 1106 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1107 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1108 } 1109 }) 1110 } 1111 } 1112 1113 func TestInteractor_Am(t *testing.T) { 1114 var testCases = []struct { 1115 name string 1116 path string 1117 remote RemoteResolver 1118 responses map[string]execResponse 1119 expectedCalls [][]string 1120 expectedErr bool 1121 }{ 1122 { 1123 name: "happy case", 1124 path: "my/changes.patch", 1125 responses: map[string]execResponse{ 1126 "am --3way my/changes.patch": { 1127 out: []byte(`ok`), 1128 }, 1129 }, 1130 expectedCalls: [][]string{ 1131 {"am", "--3way", "my/changes.patch"}, 1132 }, 1133 expectedErr: false, 1134 }, 1135 { 1136 name: "am fails but abort succeeds", 1137 path: "my/changes.patch", 1138 responses: map[string]execResponse{ 1139 "am --3way my/changes.patch": { 1140 err: errors.New("oops"), 1141 }, 1142 "am --abort": { 1143 out: []byte(`ok`), 1144 }, 1145 }, 1146 expectedCalls: [][]string{ 1147 {"am", "--3way", "my/changes.patch"}, 1148 {"am", "--abort"}, 1149 }, 1150 expectedErr: true, 1151 }, 1152 { 1153 name: "am fails and abort fails", 1154 path: "my/changes.patch", 1155 responses: map[string]execResponse{ 1156 "am --3way my/changes.patch": { 1157 err: errors.New("oops"), 1158 }, 1159 "am --abort": { 1160 err: errors.New("oops"), 1161 }, 1162 }, 1163 expectedCalls: [][]string{ 1164 {"am", "--3way", "my/changes.patch"}, 1165 {"am", "--abort"}, 1166 }, 1167 expectedErr: true, 1168 }, 1169 } 1170 1171 for _, testCase := range testCases { 1172 t.Run(testCase.name, func(t *testing.T) { 1173 e := fakeExecutor{ 1174 records: [][]string{}, 1175 responses: testCase.responses, 1176 } 1177 i := interactor{ 1178 executor: &e, 1179 remote: testCase.remote, 1180 logger: logrus.WithField("test", testCase.name), 1181 } 1182 actualErr := i.Am(testCase.path) 1183 if testCase.expectedErr && actualErr == nil { 1184 t.Errorf("%s: expected an error but got none", testCase.name) 1185 } 1186 if !testCase.expectedErr && actualErr != nil { 1187 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1188 } 1189 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1190 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1191 } 1192 }) 1193 } 1194 } 1195 1196 func TestInteractor_RemoteUpdate(t *testing.T) { 1197 var testCases = []struct { 1198 name string 1199 remote RemoteResolver 1200 responses map[string]execResponse 1201 expectedCalls [][]string 1202 expectedErr bool 1203 }{ 1204 { 1205 name: "happy case", 1206 remote: func() (string, error) { 1207 return "someone.com", nil 1208 }, 1209 responses: map[string]execResponse{ 1210 "remote set-url origin someone.com": { 1211 out: []byte(`ok`), 1212 }, 1213 "remote update --prune": { 1214 out: []byte(`ok`), 1215 }, 1216 }, 1217 expectedCalls: [][]string{ 1218 {"remote", "set-url", "origin", "someone.com"}, 1219 {"remote", "update", "--prune"}, 1220 }, 1221 expectedErr: false, 1222 }, 1223 { 1224 name: "remote resolution fails", 1225 remote: func() (string, error) { 1226 return "", errors.New("oops") 1227 }, 1228 responses: map[string]execResponse{}, 1229 expectedCalls: [][]string{}, 1230 expectedErr: true, 1231 }, 1232 { 1233 name: "setting remote URL fails", 1234 remote: func() (string, error) { 1235 return "someone.com", nil 1236 }, 1237 responses: map[string]execResponse{ 1238 "remote set-url origin someone.com": { 1239 err: errors.New("oops"), 1240 }, 1241 }, 1242 expectedCalls: [][]string{ 1243 {"remote", "set-url", "origin", "someone.com"}, 1244 }, 1245 expectedErr: true, 1246 }, 1247 { 1248 name: "update fails", 1249 remote: func() (string, error) { 1250 return "someone.com", nil 1251 }, 1252 responses: map[string]execResponse{ 1253 "remote set-url origin someone.com": { 1254 out: []byte(`ok`), 1255 }, 1256 "remote update --prune": { 1257 err: errors.New("oops"), 1258 }, 1259 }, 1260 expectedCalls: [][]string{ 1261 {"remote", "set-url", "origin", "someone.com"}, 1262 {"remote", "update", "--prune"}, 1263 }, 1264 expectedErr: true, 1265 }, 1266 } 1267 1268 for _, testCase := range testCases { 1269 t.Run(testCase.name, func(t *testing.T) { 1270 e := fakeExecutor{ 1271 records: [][]string{}, 1272 responses: testCase.responses, 1273 } 1274 i := interactor{ 1275 executor: &e, 1276 remote: testCase.remote, 1277 logger: logrus.WithField("test", testCase.name), 1278 } 1279 actualErr := i.RemoteUpdate() 1280 if testCase.expectedErr && actualErr == nil { 1281 t.Errorf("%s: expected an error but got none", testCase.name) 1282 } 1283 if !testCase.expectedErr && actualErr != nil { 1284 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1285 } 1286 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1287 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1288 } 1289 }) 1290 } 1291 } 1292 1293 func TestInteractor_Fetch(t *testing.T) { 1294 var testCases = []struct { 1295 name string 1296 remote RemoteResolver 1297 responses map[string]execResponse 1298 extraArgs []string 1299 expectedCalls [][]string 1300 expectedErr bool 1301 }{ 1302 { 1303 name: "happy case", 1304 remote: func() (string, error) { 1305 return "someone.com", nil 1306 }, 1307 responses: map[string]execResponse{ 1308 "fetch someone.com": { 1309 out: []byte(`ok`), 1310 }, 1311 }, 1312 expectedCalls: [][]string{ 1313 {"fetch", "someone.com"}, 1314 }, 1315 expectedErr: false, 1316 }, 1317 { 1318 name: "with arg", 1319 remote: func() (string, error) { 1320 return "someone.com", nil 1321 }, 1322 responses: map[string]execResponse{ 1323 "fetch someone.com --prune": { 1324 out: []byte(`ok`), 1325 }, 1326 }, 1327 extraArgs: []string{"--prune"}, 1328 expectedCalls: [][]string{ 1329 {"fetch", "someone.com", "--prune"}, 1330 }, 1331 expectedErr: false, 1332 }, 1333 { 1334 name: "remote resolution fails", 1335 remote: func() (string, error) { 1336 return "", errors.New("oops") 1337 }, 1338 responses: map[string]execResponse{}, 1339 expectedCalls: [][]string{}, 1340 expectedErr: true, 1341 }, 1342 { 1343 name: "fetch fails", 1344 remote: func() (string, error) { 1345 return "someone.com", nil 1346 }, 1347 responses: map[string]execResponse{ 1348 "fetch someone.com": { 1349 err: errors.New("oops"), 1350 }, 1351 }, 1352 expectedCalls: [][]string{ 1353 {"fetch", "someone.com"}, 1354 }, 1355 expectedErr: true, 1356 }, 1357 } 1358 1359 for _, testCase := range testCases { 1360 t.Run(testCase.name, func(t *testing.T) { 1361 e := fakeExecutor{ 1362 records: [][]string{}, 1363 responses: testCase.responses, 1364 } 1365 i := interactor{ 1366 executor: &e, 1367 remote: testCase.remote, 1368 logger: logrus.WithField("test", testCase.name), 1369 } 1370 actualErr := i.Fetch(testCase.extraArgs...) 1371 if testCase.expectedErr && actualErr == nil { 1372 t.Errorf("%s: expected an error but got none", testCase.name) 1373 } 1374 if !testCase.expectedErr && actualErr != nil { 1375 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1376 } 1377 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1378 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1379 } 1380 }) 1381 } 1382 } 1383 1384 func TestInteractor_FetchRef(t *testing.T) { 1385 var testCases = []struct { 1386 name string 1387 refspec string 1388 remote RemoteResolver 1389 responses map[string]execResponse 1390 expectedCalls [][]string 1391 expectedErr bool 1392 }{ 1393 { 1394 name: "happy case", 1395 refspec: "shasum", 1396 remote: func() (string, error) { 1397 return "someone.com", nil 1398 }, 1399 responses: map[string]execResponse{ 1400 "fetch someone.com shasum": { 1401 out: []byte(`ok`), 1402 }, 1403 }, 1404 expectedCalls: [][]string{ 1405 {"fetch", "someone.com", "shasum"}, 1406 }, 1407 expectedErr: false, 1408 }, 1409 { 1410 name: "remote resolution fails", 1411 refspec: "shasum", 1412 remote: func() (string, error) { 1413 return "", errors.New("oops") 1414 }, 1415 responses: map[string]execResponse{}, 1416 expectedCalls: [][]string{}, 1417 expectedErr: true, 1418 }, 1419 { 1420 name: "fetch fails", 1421 refspec: "shasum", 1422 remote: func() (string, error) { 1423 return "someone.com", nil 1424 }, 1425 responses: map[string]execResponse{ 1426 "fetch someone.com shasum": { 1427 err: errors.New("oops"), 1428 }, 1429 }, 1430 expectedCalls: [][]string{ 1431 {"fetch", "someone.com", "shasum"}, 1432 }, 1433 expectedErr: true, 1434 }, 1435 } 1436 1437 for _, testCase := range testCases { 1438 t.Run(testCase.name, func(t *testing.T) { 1439 e := fakeExecutor{ 1440 records: [][]string{}, 1441 responses: testCase.responses, 1442 } 1443 i := interactor{ 1444 executor: &e, 1445 remote: testCase.remote, 1446 logger: logrus.WithField("test", testCase.name), 1447 } 1448 actualErr := i.FetchRef(testCase.refspec) 1449 if testCase.expectedErr && actualErr == nil { 1450 t.Errorf("%s: expected an error but got none", testCase.name) 1451 } 1452 if !testCase.expectedErr && actualErr != nil { 1453 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1454 } 1455 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1456 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1457 } 1458 }) 1459 } 1460 } 1461 1462 func TestInteractor_FetchFromRemote(t *testing.T) { 1463 var testCases = []struct { 1464 name string 1465 remote RemoteResolver 1466 toRemote RemoteResolver 1467 branch string 1468 responses map[string]execResponse 1469 expectedCalls [][]string 1470 expectedErr bool 1471 }{ 1472 { 1473 name: "fetch from different remote without token", 1474 remote: func() (string, error) { 1475 return "someone.com", nil 1476 }, 1477 toRemote: func() (string, error) { 1478 return "https://github.com/kubernetes/test-infra-fork", nil 1479 }, 1480 branch: "test-branch", 1481 responses: map[string]execResponse{ 1482 "fetch https://github.com/kubernetes/test-infra-fork test-branch": { 1483 out: []byte(`ok`), 1484 }, 1485 }, 1486 expectedCalls: [][]string{ 1487 {"fetch", "https://github.com/kubernetes/test-infra-fork", "test-branch"}, 1488 }, 1489 expectedErr: false, 1490 }, 1491 { 1492 name: "fetch from different remote with token", 1493 remote: func() (string, error) { 1494 return "someone.com", nil 1495 }, 1496 toRemote: func() (string, error) { 1497 return "https://user:pass@github.com/kubernetes/test-infra-fork", nil 1498 }, 1499 branch: "test-branch", 1500 responses: map[string]execResponse{ 1501 "fetch https://user:pass@github.com/kubernetes/test-infra-fork test-branch": { 1502 out: []byte(`ok`), 1503 }, 1504 }, 1505 expectedCalls: [][]string{ 1506 {"fetch", "https://user:pass@github.com/kubernetes/test-infra-fork", "test-branch"}, 1507 }, 1508 expectedErr: false, 1509 }, 1510 { 1511 name: "passing non-valid remote", 1512 remote: func() (string, error) { 1513 return "someone.com", nil 1514 }, 1515 toRemote: func() (string, error) { 1516 return "", fmt.Errorf("non-valid URL") 1517 }, 1518 branch: "test-branch", 1519 expectedCalls: [][]string{}, 1520 expectedErr: true, 1521 }, 1522 } 1523 1524 for _, testCase := range testCases { 1525 t.Run(testCase.name, func(t *testing.T) { 1526 e := fakeExecutor{ 1527 records: [][]string{}, 1528 responses: testCase.responses, 1529 } 1530 i := interactor{ 1531 executor: &e, 1532 remote: testCase.remote, 1533 logger: logrus.WithField("test", testCase.name), 1534 } 1535 1536 actualErr := i.FetchFromRemote(testCase.toRemote, testCase.branch) 1537 if testCase.expectedErr && actualErr == nil { 1538 t.Errorf("%s: expected an error but got none", testCase.name) 1539 } 1540 if !testCase.expectedErr && actualErr != nil { 1541 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1542 } 1543 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1544 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1545 } 1546 }) 1547 } 1548 } 1549 1550 func TestInteractor_CheckoutPullRequest(t *testing.T) { 1551 var testCases = []struct { 1552 name string 1553 number int 1554 remote RemoteResolver 1555 responses map[string]execResponse 1556 expectedCalls [][]string 1557 expectedErr bool 1558 }{ 1559 { 1560 name: "happy case", 1561 number: 1, 1562 remote: func() (string, error) { 1563 return "someone.com", nil 1564 }, 1565 responses: map[string]execResponse{ 1566 "fetch someone.com pull/1/head": { 1567 out: []byte(`ok`), 1568 }, 1569 "checkout FETCH_HEAD": { 1570 out: []byte(`ok`), 1571 }, 1572 "checkout -b pull1": { 1573 out: []byte(`ok`), 1574 }, 1575 }, 1576 expectedCalls: [][]string{ 1577 {"fetch", "someone.com", "pull/1/head"}, 1578 {"checkout", "FETCH_HEAD"}, 1579 {"checkout", "-b", "pull1"}, 1580 }, 1581 expectedErr: false, 1582 }, 1583 { 1584 name: "remote resolution fails", 1585 number: 1, 1586 remote: func() (string, error) { 1587 return "", errors.New("oops") 1588 }, 1589 responses: map[string]execResponse{}, 1590 expectedCalls: [][]string{}, 1591 expectedErr: true, 1592 }, 1593 { 1594 name: "fetch fails", 1595 number: 1, 1596 remote: func() (string, error) { 1597 return "someone.com", nil 1598 }, 1599 responses: map[string]execResponse{ 1600 "fetch someone.com pull/1/head": { 1601 err: errors.New("oops"), 1602 }, 1603 }, 1604 expectedCalls: [][]string{ 1605 {"fetch", "someone.com", "pull/1/head"}, 1606 }, 1607 expectedErr: true, 1608 }, 1609 { 1610 name: "checkout fails", 1611 number: 1, 1612 remote: func() (string, error) { 1613 return "someone.com", nil 1614 }, 1615 responses: map[string]execResponse{ 1616 "fetch someone.com pull/1/head": { 1617 out: []byte(`ok`), 1618 }, 1619 "checkout FETCH_HEAD": { 1620 err: errors.New("oops"), 1621 }, 1622 }, 1623 expectedCalls: [][]string{ 1624 {"fetch", "someone.com", "pull/1/head"}, 1625 {"checkout", "FETCH_HEAD"}, 1626 }, 1627 expectedErr: true, 1628 }, 1629 { 1630 name: "branch fails", 1631 number: 1, 1632 remote: func() (string, error) { 1633 return "someone.com", nil 1634 }, 1635 responses: map[string]execResponse{ 1636 "fetch someone.com pull/1/head": { 1637 out: []byte(`ok`), 1638 }, 1639 "checkout FETCH_HEAD": { 1640 out: []byte(`ok`), 1641 }, 1642 "checkout -b pull1": { 1643 err: errors.New("oops"), 1644 }, 1645 }, 1646 expectedCalls: [][]string{ 1647 {"fetch", "someone.com", "pull/1/head"}, 1648 {"checkout", "FETCH_HEAD"}, 1649 {"checkout", "-b", "pull1"}, 1650 }, 1651 expectedErr: true, 1652 }, 1653 } 1654 1655 for _, testCase := range testCases { 1656 t.Run(testCase.name, func(t *testing.T) { 1657 e := fakeExecutor{ 1658 records: [][]string{}, 1659 responses: testCase.responses, 1660 } 1661 i := interactor{ 1662 executor: &e, 1663 remote: testCase.remote, 1664 logger: logrus.WithField("test", testCase.name), 1665 } 1666 actualErr := i.CheckoutPullRequest(testCase.number) 1667 if testCase.expectedErr && actualErr == nil { 1668 t.Errorf("%s: expected an error but got none", testCase.name) 1669 } 1670 if !testCase.expectedErr && actualErr != nil { 1671 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1672 } 1673 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1674 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1675 } 1676 }) 1677 } 1678 } 1679 1680 func TestInteractor_Config(t *testing.T) { 1681 var testCases = []struct { 1682 name string 1683 key, value string 1684 remote RemoteResolver 1685 responses map[string]execResponse 1686 expectedCalls [][]string 1687 expectedErr bool 1688 }{ 1689 { 1690 name: "happy case", 1691 key: "key", 1692 value: "value", 1693 responses: map[string]execResponse{ 1694 "config key value": { 1695 out: []byte(`ok`), 1696 }, 1697 }, 1698 expectedCalls: [][]string{ 1699 {"config", "key", "value"}, 1700 }, 1701 expectedErr: false, 1702 }, 1703 { 1704 name: "config fails", 1705 key: "key", 1706 value: "value", 1707 responses: map[string]execResponse{ 1708 "config key value": { 1709 err: errors.New("oops"), 1710 }, 1711 }, 1712 expectedCalls: [][]string{ 1713 {"config", "key", "value"}, 1714 }, 1715 expectedErr: true, 1716 }, 1717 } 1718 1719 for _, testCase := range testCases { 1720 t.Run(testCase.name, func(t *testing.T) { 1721 e := fakeExecutor{ 1722 records: [][]string{}, 1723 responses: testCase.responses, 1724 } 1725 i := interactor{ 1726 executor: &e, 1727 remote: testCase.remote, 1728 logger: logrus.WithField("test", testCase.name), 1729 } 1730 actualErr := i.Config(testCase.key, testCase.value) 1731 if testCase.expectedErr && actualErr == nil { 1732 t.Errorf("%s: expected an error but got none", testCase.name) 1733 } 1734 if !testCase.expectedErr && actualErr != nil { 1735 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1736 } 1737 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1738 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1739 } 1740 }) 1741 } 1742 } 1743 1744 func TestInteractor_Diff(t *testing.T) { 1745 var testCases = []struct { 1746 name string 1747 head, sha string 1748 remote RemoteResolver 1749 responses map[string]execResponse 1750 expectedCalls [][]string 1751 expectedOut []string 1752 expectedErr bool 1753 }{ 1754 { 1755 name: "happy case", 1756 head: "head", 1757 sha: "sha", 1758 responses: map[string]execResponse{ 1759 "diff head sha --name-only": { 1760 out: []byte(`prow/git/v2/client_factory.go 1761 prow/git/v2/executor.go 1762 prow/git/v2/executor_test.go 1763 prow/git/v2/fakes.go 1764 prow/git/v2/interactor.go 1765 prow/git/v2/publisher.go 1766 prow/git/v2/publisher_test.go 1767 prow/git/v2/remote.go 1768 prow/git/v2/remote_test.go`), 1769 }, 1770 }, 1771 expectedCalls: [][]string{ 1772 {"diff", "head", "sha", "--name-only"}, 1773 }, 1774 expectedOut: []string{ 1775 "prow/git/v2/client_factory.go", 1776 "prow/git/v2/executor.go", 1777 "prow/git/v2/executor_test.go", 1778 "prow/git/v2/fakes.go", 1779 "prow/git/v2/interactor.go", 1780 "prow/git/v2/publisher.go", 1781 "prow/git/v2/publisher_test.go", 1782 "prow/git/v2/remote.go", 1783 "prow/git/v2/remote_test.go", 1784 }, 1785 expectedErr: false, 1786 }, 1787 { 1788 name: "config fails", 1789 head: "head", 1790 sha: "sha", 1791 responses: map[string]execResponse{ 1792 "diff head sha --name-only": { 1793 err: errors.New("oops"), 1794 }, 1795 }, 1796 expectedCalls: [][]string{ 1797 {"diff", "head", "sha", "--name-only"}, 1798 }, 1799 expectedErr: true, 1800 }, 1801 } 1802 1803 for _, testCase := range testCases { 1804 t.Run(testCase.name, func(t *testing.T) { 1805 e := fakeExecutor{ 1806 records: [][]string{}, 1807 responses: testCase.responses, 1808 } 1809 i := interactor{ 1810 executor: &e, 1811 remote: testCase.remote, 1812 logger: logrus.WithField("test", testCase.name), 1813 } 1814 actualOut, actualErr := i.Diff(testCase.head, testCase.sha) 1815 if !reflect.DeepEqual(actualOut, testCase.expectedOut) { 1816 t.Errorf("%s: got incorrect output: %v", testCase.name, diff.ObjectReflectDiff(actualOut, testCase.expectedOut)) 1817 } 1818 if testCase.expectedErr && actualErr == nil { 1819 t.Errorf("%s: expected an error but got none", testCase.name) 1820 } 1821 if !testCase.expectedErr && actualErr != nil { 1822 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1823 } 1824 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1825 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1826 } 1827 }) 1828 } 1829 } 1830 1831 func TestInteractor_MergeCommitsExistBetween(t *testing.T) { 1832 var testCases = []struct { 1833 name string 1834 target, head string 1835 responses map[string]execResponse 1836 expectedCalls [][]string 1837 expectedOut bool 1838 expectedErr bool 1839 }{ 1840 { 1841 name: "happy case and merges exist", 1842 target: "target", 1843 head: "head", 1844 responses: map[string]execResponse{ 1845 "log target..head --oneline --merges": { 1846 out: []byte(`8df5654e6 Merge pull request #14911 from mborsz/etcd 1847 96cbeee23 Merge pull request #14755 from justinsb/the_life_changing_magic_of_tidying_up`), 1848 }, 1849 }, 1850 expectedCalls: [][]string{ 1851 {"log", "target..head", "--oneline", "--merges"}, 1852 }, 1853 expectedOut: true, 1854 expectedErr: false, 1855 }, 1856 { 1857 name: "happy case and merges don't exist", 1858 target: "target", 1859 head: "head", 1860 responses: map[string]execResponse{ 1861 "log target..head --oneline --merges": { 1862 out: []byte(``), 1863 }, 1864 }, 1865 expectedCalls: [][]string{ 1866 {"log", "target..head", "--oneline", "--merges"}, 1867 }, 1868 expectedOut: false, 1869 expectedErr: false, 1870 }, 1871 { 1872 name: "log fails", 1873 target: "target", 1874 head: "head", 1875 responses: map[string]execResponse{ 1876 "log target..head --oneline --merges": { 1877 err: errors.New("oops"), 1878 }, 1879 }, 1880 expectedCalls: [][]string{ 1881 {"log", "target..head", "--oneline", "--merges"}, 1882 }, 1883 expectedOut: false, 1884 expectedErr: true, 1885 }, 1886 } 1887 1888 for _, testCase := range testCases { 1889 t.Run(testCase.name, func(t *testing.T) { 1890 e := fakeExecutor{ 1891 records: [][]string{}, 1892 responses: testCase.responses, 1893 } 1894 i := interactor{ 1895 executor: &e, 1896 logger: logrus.WithField("test", testCase.name), 1897 } 1898 actualOut, actualErr := i.MergeCommitsExistBetween(testCase.target, testCase.head) 1899 if testCase.expectedOut != actualOut { 1900 t.Errorf("%s: got incorrect output: expected %v, got %v", testCase.name, testCase.expectedOut, actualOut) 1901 } 1902 if testCase.expectedErr && actualErr == nil { 1903 t.Errorf("%s: expected an error but got none", testCase.name) 1904 } 1905 if !testCase.expectedErr && actualErr != nil { 1906 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1907 } 1908 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1909 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1910 } 1911 }) 1912 } 1913 } 1914 1915 func TestInteractor_ShowRef(t *testing.T) { 1916 const target = "some-branch" 1917 var testCases = []struct { 1918 name string 1919 responses map[string]execResponse 1920 expectedCalls [][]string 1921 expectedErr bool 1922 }{ 1923 { 1924 name: "happy case", 1925 responses: map[string]execResponse{ 1926 "show-ref -s some-branch": {out: []byte("32d3f5a6826109c625527f18a59f2e7144a330b6\n")}, 1927 }, 1928 expectedCalls: [][]string{ 1929 {"show-ref", "-s", target}, 1930 }, 1931 expectedErr: false, 1932 }, 1933 { 1934 name: "unhappy case", 1935 responses: map[string]execResponse{ 1936 "git show-ref -s some-undef-branch": {err: errors.New("some-err")}, 1937 }, 1938 expectedCalls: [][]string{ 1939 {"show-ref", "-s", target}, 1940 }, 1941 expectedErr: true, 1942 }, 1943 } 1944 for _, testCase := range testCases { 1945 t.Run(testCase.name, func(t *testing.T) { 1946 e := fakeExecutor{ 1947 records: [][]string{}, 1948 responses: testCase.responses, 1949 } 1950 i := interactor{ 1951 executor: &e, 1952 logger: logrus.WithField("test", testCase.name), 1953 } 1954 _, actualErr := i.ShowRef(target) 1955 if testCase.expectedErr && actualErr == nil { 1956 t.Errorf("%s: expected an error but got none", testCase.name) 1957 } 1958 if !testCase.expectedErr && actualErr != nil { 1959 t.Errorf("%s: expected no error but got one: %v", testCase.name, actualErr) 1960 } 1961 if actual, expected := e.records, testCase.expectedCalls; !reflect.DeepEqual(actual, expected) { 1962 t.Errorf("%s: got incorrect git calls: %v", testCase.name, diff.ObjectReflectDiff(actual, expected)) 1963 } 1964 }) 1965 } 1966 }