github.com/abayer/test-infra@v0.0.5/prow/plugins/lgtm/lgtm_test.go (about) 1 /* 2 Copyright 2016 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 lgtm 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/sirupsen/logrus" 24 "k8s.io/apimachinery/pkg/api/equality" 25 "k8s.io/apimachinery/pkg/util/sets" 26 27 "k8s.io/test-infra/prow/github" 28 "k8s.io/test-infra/prow/github/fakegithub" 29 "k8s.io/test-infra/prow/plugins" 30 "k8s.io/test-infra/prow/repoowners" 31 ) 32 33 type fakeOwnersClient struct { 34 approvers map[string]sets.String 35 reviewers map[string]sets.String 36 } 37 38 var _ repoowners.Interface = &fakeOwnersClient{} 39 40 func (f *fakeOwnersClient) LoadRepoAliases(org, repo, base string) (repoowners.RepoAliases, error) { 41 return nil, nil 42 } 43 44 func (f *fakeOwnersClient) LoadRepoOwners(org, repo, base string) (repoowners.RepoOwnerInterface, error) { 45 return &fakeRepoOwners{approvers: f.approvers, reviewers: f.reviewers}, nil 46 } 47 48 type fakeRepoOwners struct { 49 approvers map[string]sets.String 50 reviewers map[string]sets.String 51 } 52 53 var _ repoowners.RepoOwnerInterface = &fakeRepoOwners{} 54 55 func (f *fakeRepoOwners) FindApproverOwnersForFile(path string) string { return "" } 56 func (f *fakeRepoOwners) FindReviewersOwnersForFile(path string) string { return "" } 57 func (f *fakeRepoOwners) FindLabelsForFile(path string) sets.String { return nil } 58 func (f *fakeRepoOwners) IsNoParentOwners(path string) bool { return false } 59 func (f *fakeRepoOwners) LeafApprovers(path string) sets.String { return nil } 60 func (f *fakeRepoOwners) Approvers(path string) sets.String { return f.approvers[path] } 61 func (f *fakeRepoOwners) LeafReviewers(path string) sets.String { return nil } 62 func (f *fakeRepoOwners) Reviewers(path string) sets.String { return f.reviewers[path] } 63 func (f *fakeRepoOwners) RequiredReviewers(path string) sets.String { return nil } 64 65 var approvers = map[string]sets.String{ 66 "doc/README.md": { 67 "cjwagner": {}, 68 "jessica": {}, 69 }, 70 } 71 72 var reviewers = map[string]sets.String{ 73 "doc/README.md": { 74 "alice": {}, 75 "bob": {}, 76 "mark": {}, 77 "sam": {}, 78 }, 79 } 80 81 func TestLGTMComment(t *testing.T) { 82 var testcases = []struct { 83 name string 84 body string 85 commenter string 86 hasLGTM bool 87 shouldToggle bool 88 shouldComment bool 89 shouldAssign bool 90 skipCollab bool 91 prs map[int]*github.PullRequest 92 changes map[int][]github.PullRequestChange 93 }{ 94 { 95 name: "non-lgtm comment", 96 body: "uh oh", 97 commenter: "o", 98 hasLGTM: false, 99 shouldToggle: false, 100 }, 101 { 102 name: "lgtm comment by reviewer, no lgtm on pr", 103 body: "/lgtm", 104 commenter: "reviewer1", 105 hasLGTM: false, 106 shouldToggle: true, 107 }, 108 { 109 name: "LGTM comment by reviewer, no lgtm on pr", 110 body: "/LGTM", 111 commenter: "reviewer1", 112 hasLGTM: false, 113 shouldToggle: true, 114 }, 115 { 116 name: "lgtm comment by reviewer, lgtm on pr", 117 body: "/lgtm", 118 commenter: "reviewer1", 119 hasLGTM: true, 120 shouldToggle: false, 121 }, 122 { 123 name: "lgtm comment by author", 124 body: "/lgtm", 125 commenter: "author", 126 hasLGTM: false, 127 shouldToggle: false, 128 shouldComment: true, 129 }, 130 { 131 name: "lgtm cancel by author", 132 body: "/lgtm cancel", 133 commenter: "author", 134 hasLGTM: true, 135 shouldToggle: true, 136 shouldAssign: false, 137 shouldComment: false, 138 }, 139 { 140 name: "lgtm comment by non-reviewer", 141 body: "/lgtm", 142 commenter: "o", 143 hasLGTM: false, 144 shouldToggle: true, 145 shouldComment: false, 146 shouldAssign: true, 147 }, 148 { 149 name: "lgtm comment by non-reviewer, with trailing space", 150 body: "/lgtm ", 151 commenter: "o", 152 hasLGTM: false, 153 shouldToggle: true, 154 shouldComment: false, 155 shouldAssign: true, 156 }, 157 { 158 name: "lgtm comment by non-reviewer, with no-issue", 159 body: "/lgtm no-issue", 160 commenter: "o", 161 hasLGTM: false, 162 shouldToggle: true, 163 shouldComment: false, 164 shouldAssign: true, 165 }, 166 { 167 name: "lgtm comment by non-reviewer, with no-issue and trailing space", 168 body: "/lgtm no-issue \r", 169 commenter: "o", 170 hasLGTM: false, 171 shouldToggle: true, 172 shouldComment: false, 173 shouldAssign: true, 174 }, 175 { 176 name: "lgtm comment by rando", 177 body: "/lgtm", 178 commenter: "not-in-the-org", 179 hasLGTM: false, 180 shouldToggle: false, 181 shouldComment: true, 182 shouldAssign: false, 183 }, 184 { 185 name: "lgtm cancel by non-reviewer", 186 body: "/lgtm cancel", 187 commenter: "o", 188 hasLGTM: true, 189 shouldToggle: true, 190 shouldComment: false, 191 shouldAssign: true, 192 }, 193 { 194 name: "lgtm cancel by rando", 195 body: "/lgtm cancel", 196 commenter: "not-in-the-org", 197 hasLGTM: true, 198 shouldToggle: false, 199 shouldComment: true, 200 shouldAssign: false, 201 }, 202 { 203 name: "lgtm cancel comment by reviewer", 204 body: "/lgtm cancel", 205 commenter: "reviewer1", 206 hasLGTM: true, 207 shouldToggle: true, 208 }, 209 { 210 name: "lgtm cancel comment by reviewer, with trailing space", 211 body: "/lgtm cancel \r", 212 commenter: "reviewer1", 213 hasLGTM: true, 214 shouldToggle: true, 215 }, 216 { 217 name: "lgtm cancel comment by reviewer, no lgtm", 218 body: "/lgtm cancel", 219 commenter: "reviewer1", 220 hasLGTM: false, 221 shouldToggle: false, 222 }, 223 { 224 name: "lgtm comment, based off OWNERS only", 225 body: "/lgtm", 226 commenter: "sam", 227 hasLGTM: false, 228 shouldToggle: true, 229 skipCollab: true, 230 prs: map[int]*github.PullRequest{ 231 5: { 232 Base: github.PullRequestBranch{ 233 Ref: "master", 234 }, 235 }, 236 }, 237 changes: map[int][]github.PullRequestChange{ 238 5: { 239 {Filename: "doc/README.md"}, 240 }, 241 }, 242 }, 243 } 244 for _, tc := range testcases { 245 t.Logf("Running scenario %q", tc.name) 246 fc := &fakegithub.FakeClient{ 247 IssueComments: make(map[int][]github.IssueComment), 248 PullRequests: tc.prs, 249 PullRequestChanges: tc.changes, 250 } 251 e := &github.GenericCommentEvent{ 252 Action: github.GenericCommentActionCreated, 253 IssueState: "open", 254 IsPR: true, 255 Body: tc.body, 256 User: github.User{Login: tc.commenter}, 257 IssueAuthor: github.User{Login: "author"}, 258 Number: 5, 259 Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, 260 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 261 HTMLURL: "<url>", 262 } 263 if tc.hasLGTM { 264 fc.LabelsAdded = []string{"org/repo#5:" + lgtmLabel} 265 } 266 oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers} 267 pc := &plugins.Configuration{} 268 if tc.skipCollab { 269 pc.Owners.SkipCollaborators = []string{"org/repo"} 270 } 271 if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", pluginName), *e); err != nil { 272 t.Errorf("didn't expect error from lgtmComment: %v", err) 273 continue 274 } 275 if tc.shouldAssign { 276 found := false 277 for _, a := range fc.AssigneesAdded { 278 if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.commenter) { 279 found = true 280 break 281 } 282 } 283 if !found || len(fc.AssigneesAdded) != 1 { 284 t.Errorf("should have assigned %s but added assignees are %s", tc.commenter, fc.AssigneesAdded) 285 } 286 } else if len(fc.AssigneesAdded) != 0 { 287 t.Errorf("should not have assigned anyone but assigned %s", fc.AssigneesAdded) 288 } 289 if tc.shouldToggle { 290 if tc.hasLGTM { 291 if len(fc.LabelsRemoved) == 0 { 292 t.Errorf("should have removed LGTM.") 293 } else if len(fc.LabelsAdded) > 1 { 294 t.Errorf("should not have added LGTM.") 295 } 296 } else { 297 if len(fc.LabelsAdded) == 0 { 298 t.Errorf("should have added LGTM.") 299 } else if len(fc.LabelsRemoved) > 0 { 300 t.Errorf("should not have removed LGTM.") 301 } 302 } 303 } else if len(fc.LabelsRemoved) > 0 { 304 t.Errorf("should not have removed LGTM.") 305 } else if (tc.hasLGTM && len(fc.LabelsAdded) > 1) || (!tc.hasLGTM && len(fc.LabelsAdded) > 0) { 306 t.Errorf("should not have added LGTM.") 307 } 308 if tc.shouldComment && len(fc.IssueComments[5]) != 1 { 309 t.Errorf("should have commented.") 310 } else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 { 311 t.Errorf("should not have commented.") 312 } 313 } 314 } 315 316 func TestLGTMCommentWithLGTMNoti(t *testing.T) { 317 var testcases = []struct { 318 name string 319 body string 320 commenter string 321 shouldDelete bool 322 }{ 323 { 324 name: "non-lgtm comment", 325 body: "uh oh", 326 commenter: "o", 327 shouldDelete: false, 328 }, 329 { 330 name: "lgtm comment by reviewer, no lgtm on pr", 331 body: "/lgtm", 332 commenter: "reviewer1", 333 shouldDelete: true, 334 }, 335 { 336 name: "LGTM comment by reviewer, no lgtm on pr", 337 body: "/LGTM", 338 commenter: "reviewer1", 339 shouldDelete: true, 340 }, 341 { 342 name: "lgtm comment by author", 343 body: "/lgtm", 344 commenter: "author", 345 shouldDelete: false, 346 }, 347 { 348 name: "lgtm comment by non-reviewer", 349 body: "/lgtm", 350 commenter: "o", 351 shouldDelete: true, 352 }, 353 { 354 name: "lgtm comment by non-reviewer, with trailing space", 355 body: "/lgtm ", 356 commenter: "o", 357 shouldDelete: true, 358 }, 359 { 360 name: "lgtm comment by non-reviewer, with no-issue", 361 body: "/lgtm no-issue", 362 commenter: "o", 363 shouldDelete: true, 364 }, 365 { 366 name: "lgtm comment by non-reviewer, with no-issue and trailing space", 367 body: "/lgtm no-issue \r", 368 commenter: "o", 369 shouldDelete: true, 370 }, 371 { 372 name: "lgtm comment by rando", 373 body: "/lgtm", 374 commenter: "not-in-the-org", 375 shouldDelete: false, 376 }, 377 { 378 name: "lgtm cancel comment by reviewer, no lgtm", 379 body: "/lgtm cancel", 380 commenter: "reviewer1", 381 shouldDelete: false, 382 }, 383 } 384 for _, tc := range testcases { 385 fc := &fakegithub.FakeClient{ 386 IssueComments: make(map[int][]github.IssueComment), 387 } 388 e := &github.GenericCommentEvent{ 389 Action: github.GenericCommentActionCreated, 390 IssueState: "open", 391 IsPR: true, 392 Body: tc.body, 393 User: github.User{Login: tc.commenter}, 394 IssueAuthor: github.User{Login: "author"}, 395 Number: 5, 396 Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, 397 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 398 HTMLURL: "<url>", 399 } 400 botName, err := fc.BotName() 401 if err != nil { 402 t.Fatalf("For case %s, could not get Bot nam", tc.name) 403 } 404 ic := github.IssueComment{ 405 User: github.User{ 406 Login: botName, 407 }, 408 Body: removeLGTMLabelNoti, 409 } 410 fc.IssueComments[5] = append(fc.IssueComments[5], ic) 411 oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers} 412 pc := &plugins.Configuration{} 413 if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", pluginName), *e); err != nil { 414 t.Errorf("For case %s, didn't expect error from lgtmComment: %v", tc.name, err) 415 continue 416 } 417 found := false 418 for _, v := range fc.IssueComments[5] { 419 if v.User.Login == botName && v.Body == removeLGTMLabelNoti { 420 found = true 421 break 422 } 423 } 424 if tc.shouldDelete { 425 if found { 426 t.Errorf("For case %s, LGTM removed notification should have been deleted", tc.name) 427 } 428 } else { 429 if !found { 430 t.Errorf("For case %s, LGTM removed notification should not have been deleted", tc.name) 431 } 432 } 433 } 434 } 435 436 func TestLGTMFromApproveReview(t *testing.T) { 437 var testcases = []struct { 438 name string 439 state github.ReviewState 440 body string 441 reviewer string 442 hasLGTM bool 443 shouldToggle bool 444 shouldComment bool 445 shouldAssign bool 446 }{ 447 { 448 name: "Request changes review by reviewer, no lgtm on pr", 449 state: github.ReviewStateChangesRequested, 450 reviewer: "reviewer1", 451 hasLGTM: false, 452 shouldToggle: false, 453 shouldAssign: false, 454 shouldComment: false, 455 }, 456 { 457 name: "Request changes review by reviewer, lgtm on pr", 458 state: github.ReviewStateChangesRequested, 459 reviewer: "reviewer1", 460 hasLGTM: true, 461 shouldToggle: true, 462 shouldAssign: false, 463 }, 464 { 465 name: "Approve review by reviewer, no lgtm on pr", 466 state: github.ReviewStateApproved, 467 reviewer: "reviewer1", 468 hasLGTM: false, 469 shouldToggle: true, 470 }, 471 { 472 name: "Approve review by reviewer, lgtm on pr", 473 state: github.ReviewStateApproved, 474 reviewer: "reviewer1", 475 hasLGTM: true, 476 shouldToggle: false, 477 shouldAssign: false, 478 }, 479 { 480 name: "Approve review by non-reviewer, no lgtm on pr", 481 state: github.ReviewStateApproved, 482 reviewer: "o", 483 hasLGTM: false, 484 shouldToggle: true, 485 shouldComment: false, 486 shouldAssign: true, 487 }, 488 { 489 name: "Request changes review by non-reviewer, no lgtm on pr", 490 state: github.ReviewStateChangesRequested, 491 reviewer: "o", 492 hasLGTM: false, 493 shouldToggle: false, 494 shouldComment: false, 495 shouldAssign: true, 496 }, 497 { 498 name: "Approve review by rando", 499 state: github.ReviewStateApproved, 500 reviewer: "not-in-the-org", 501 hasLGTM: false, 502 shouldToggle: false, 503 shouldComment: true, 504 shouldAssign: false, 505 }, 506 { 507 name: "Comment review by issue author, no lgtm on pr", 508 state: github.ReviewStateCommented, 509 reviewer: "author", 510 hasLGTM: false, 511 shouldToggle: false, 512 shouldComment: false, 513 shouldAssign: false, 514 }, 515 { 516 name: "Comment body has /lgtm on Comment Review ", 517 state: github.ReviewStateCommented, 518 reviewer: "reviewer1", 519 body: "/lgtm", 520 hasLGTM: false, 521 shouldToggle: false, 522 shouldComment: false, 523 shouldAssign: false, 524 }, 525 { 526 name: "Comment body has /lgtm cancel on Approve Review", 527 state: github.ReviewStateApproved, 528 reviewer: "reviewer1", 529 body: "/lgtm cancel", 530 hasLGTM: false, 531 shouldToggle: false, 532 shouldComment: false, 533 shouldAssign: false, 534 }, 535 } 536 for _, tc := range testcases { 537 fc := &fakegithub.FakeClient{ 538 IssueComments: make(map[int][]github.IssueComment), 539 } 540 e := &github.ReviewEvent{ 541 Review: github.Review{Body: tc.body, State: tc.state, HTMLURL: "<url>", User: github.User{Login: tc.reviewer}}, 542 PullRequest: github.PullRequest{User: github.User{Login: "author"}, Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, Number: 5}, 543 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 544 } 545 if tc.hasLGTM { 546 fc.LabelsAdded = []string{"org/repo#5:" + lgtmLabel} 547 } 548 oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers} 549 pc := &plugins.Configuration{} 550 if err := handlePullRequestReview(fc, pc, oc, logrus.WithField("plugin", pluginName), *e); err != nil { 551 t.Errorf("For case %s, didn't expect error from pull request review: %v", tc.name, err) 552 continue 553 } 554 if tc.shouldAssign { 555 found := false 556 for _, a := range fc.AssigneesAdded { 557 if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.reviewer) { 558 found = true 559 break 560 } 561 } 562 if !found || len(fc.AssigneesAdded) != 1 { 563 t.Errorf("For case %s, should have assigned %s but added assignees are %s", tc.name, tc.reviewer, fc.AssigneesAdded) 564 } 565 } else if len(fc.AssigneesAdded) != 0 { 566 t.Errorf("For case %s, should not have assigned anyone but assigned %s", tc.name, fc.AssigneesAdded) 567 } 568 if tc.shouldToggle { 569 if tc.hasLGTM { 570 if len(fc.LabelsRemoved) == 0 { 571 t.Errorf("For case %s, should have removed LGTM.", tc.name) 572 } else if len(fc.LabelsAdded) > 1 { 573 t.Errorf("For case %s, should not have added LGTM.", tc.name) 574 } 575 } else { 576 if len(fc.LabelsAdded) == 0 { 577 t.Errorf("For case %s, should have added LGTM.", tc.name) 578 } else if len(fc.LabelsRemoved) > 0 { 579 t.Errorf("For case %s, should not have removed LGTM.", tc.name) 580 } 581 } 582 } else if len(fc.LabelsRemoved) > 0 { 583 t.Errorf("For case %s, should not have removed LGTM.", tc.name) 584 } else if (tc.hasLGTM && len(fc.LabelsAdded) > 1) || (!tc.hasLGTM && len(fc.LabelsAdded) > 0) { 585 t.Errorf("For case %s, should not have added LGTM.", tc.name) 586 } 587 if tc.shouldComment && len(fc.IssueComments[5]) != 1 { 588 t.Errorf("For case %s, should have commented.", tc.name) 589 } else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 { 590 t.Errorf("For case %s, should not have commented.", tc.name) 591 } 592 } 593 } 594 595 type fakeIssueComment struct { 596 Owner string 597 Repo string 598 Number int 599 Comment string 600 } 601 602 type githubUnlabeler struct { 603 labelsRemoved []string 604 issueComments []fakeIssueComment 605 removeLabelErr error 606 createCommentErr error 607 } 608 609 func (c *githubUnlabeler) RemoveLabel(owner, repo string, pr int, label string) error { 610 c.labelsRemoved = append(c.labelsRemoved, label) 611 return c.removeLabelErr 612 } 613 614 func (c *githubUnlabeler) CreateComment(owner, repo string, number int, comment string) error { 615 ic := fakeIssueComment{ 616 Owner: owner, 617 Repo: repo, 618 Number: number, 619 Comment: comment, 620 } 621 c.issueComments = append(c.issueComments, ic) 622 return c.createCommentErr 623 } 624 625 func TestHandlePullRequest(t *testing.T) { 626 cases := []struct { 627 name string 628 event github.PullRequestEvent 629 removeLabelErr error 630 createCommentErr error 631 632 err error 633 labelsRemoved []string 634 issueComments []fakeIssueComment 635 636 expectNoComments bool 637 }{ 638 { 639 name: "pr_synchronize, no RemoveLabel error", 640 event: github.PullRequestEvent{ 641 Action: github.PullRequestActionSynchronize, 642 PullRequest: github.PullRequest{ 643 Number: 101, 644 Base: github.PullRequestBranch{ 645 Repo: github.Repo{ 646 Owner: github.User{ 647 Login: "kubernetes", 648 }, 649 Name: "kubernetes", 650 }, 651 }, 652 }, 653 }, 654 labelsRemoved: []string{lgtmLabel}, 655 issueComments: []fakeIssueComment{ 656 { 657 Owner: "kubernetes", 658 Repo: "kubernetes", 659 Number: 101, 660 Comment: removeLGTMLabelNoti, 661 }, 662 }, 663 expectNoComments: false, 664 }, 665 { 666 name: "pr_assigned", 667 event: github.PullRequestEvent{ 668 Action: "assigned", 669 }, 670 expectNoComments: true, 671 }, 672 { 673 name: "pr_synchronize, with RemoveLabel github.LabelNotFound error", 674 event: github.PullRequestEvent{ 675 Action: github.PullRequestActionSynchronize, 676 PullRequest: github.PullRequest{ 677 Number: 101, 678 Base: github.PullRequestBranch{ 679 Repo: github.Repo{ 680 Owner: github.User{ 681 Login: "kubernetes", 682 }, 683 Name: "kubernetes", 684 }, 685 }, 686 }, 687 }, 688 removeLabelErr: &github.LabelNotFound{ 689 Owner: "kubernetes", 690 Repo: "kubernetes", 691 Number: 101, 692 Label: lgtmLabel, 693 }, 694 labelsRemoved: []string{lgtmLabel}, 695 expectNoComments: true, 696 }, 697 } 698 699 for _, c := range cases { 700 t.Run(c.name, func(t *testing.T) { 701 fakeGitHub := &githubUnlabeler{ 702 removeLabelErr: c.removeLabelErr, 703 createCommentErr: c.createCommentErr, 704 } 705 err := handlePullRequest(fakeGitHub, c.event, logrus.WithField("plugin", pluginName)) 706 707 if err != nil && c.err == nil { 708 t.Fatalf("handlePullRequest error: %v", err) 709 } 710 711 if err == nil && c.err != nil { 712 t.Fatalf("handlePullRequest wanted error: %v, got nil", c.err) 713 } 714 715 if got, want := err, c.err; !equality.Semantic.DeepEqual(got, want) { 716 t.Fatalf("handlePullRequest error mismatch: got %v, want %v", got, want) 717 } 718 719 if got, want := len(fakeGitHub.labelsRemoved), len(c.labelsRemoved); got != want { 720 t.Logf("labelsRemoved: got %v, want: %v", fakeGitHub.labelsRemoved, c.labelsRemoved) 721 t.Fatalf("labelsRemoved length mismatch: got %d, want %d", got, want) 722 } 723 724 if got, want := fakeGitHub.issueComments, c.issueComments; !equality.Semantic.DeepEqual(got, want) { 725 t.Fatalf("LGTM revmoved notifications mismatch: got %v, want %v", got, want) 726 } 727 if c.expectNoComments && len(fakeGitHub.issueComments) > 0 { 728 t.Fatalf("expected no comments but got %v", fakeGitHub.issueComments) 729 } 730 if !c.expectNoComments && len(fakeGitHub.issueComments) == 0 { 731 t.Fatalf("expected comments but got none") 732 } 733 }) 734 } 735 }