github.com/abayer/test-infra@v0.0.5/prow/plugins/releasenote/releasenote_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 releasenote 18 19 import ( 20 "fmt" 21 "reflect" 22 "sort" 23 "testing" 24 25 "github.com/sirupsen/logrus" 26 27 "k8s.io/test-infra/prow/github" 28 "k8s.io/test-infra/prow/github/fakegithub" 29 ) 30 31 func TestReleaseNoteComment(t *testing.T) { 32 var testcases = []struct { 33 name string 34 action github.IssueCommentEventAction 35 commentBody string 36 issueBody string 37 isMember bool 38 isAuthor bool 39 currentLabels []string 40 41 deletedLabels []string 42 addedLabel string 43 shouldComment bool 44 }{ 45 { 46 name: "unrelated comment", 47 action: github.IssueCommentActionCreated, 48 commentBody: "oh dear", 49 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 50 }, 51 { 52 name: "author release-note-none with missing block", 53 action: github.IssueCommentActionCreated, 54 isAuthor: true, 55 commentBody: "/release-note-none", 56 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 57 58 deletedLabels: []string{releaseNoteLabelNeeded}, 59 addedLabel: releaseNoteNone, 60 }, 61 { 62 name: "author release-note-none with empty block", 63 action: github.IssueCommentActionCreated, 64 isAuthor: true, 65 commentBody: "/release-note-none", 66 issueBody: "bologna ```release-note \n ```", 67 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 68 69 deletedLabels: []string{releaseNoteLabelNeeded}, 70 addedLabel: releaseNoteNone, 71 }, 72 { 73 name: "author release-note-none with \"none\" block", 74 action: github.IssueCommentActionCreated, 75 isAuthor: true, 76 commentBody: "/release-note-none", 77 issueBody: "bologna ```release-note \nnone \n ```", 78 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 79 80 deletedLabels: []string{releaseNoteLabelNeeded}, 81 addedLabel: releaseNoteNone, 82 }, 83 { 84 name: "author release-note-none, trailing space.", 85 action: github.IssueCommentActionCreated, 86 isAuthor: true, 87 commentBody: "/release-note-none ", 88 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 89 90 deletedLabels: []string{releaseNoteLabelNeeded}, 91 addedLabel: releaseNoteNone, 92 }, 93 { 94 name: "author release-note-none, no op.", 95 action: github.IssueCommentActionCreated, 96 isAuthor: true, 97 commentBody: "/release-note-none", 98 currentLabels: []string{releaseNoteNone, "other"}, 99 }, 100 { 101 name: "member release-note", 102 action: github.IssueCommentActionCreated, 103 isMember: true, 104 commentBody: "/release-note", 105 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 106 107 shouldComment: true, 108 }, 109 { 110 name: "someone else release-note, trailing space.", 111 action: github.IssueCommentActionCreated, 112 commentBody: "/release-note \r", 113 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 114 shouldComment: true, 115 }, 116 { 117 name: "someone else release-note-none", 118 action: github.IssueCommentActionCreated, 119 commentBody: "/release-note-none", 120 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 121 shouldComment: true, 122 }, 123 { 124 name: "author release-note-action-required", 125 action: github.IssueCommentActionCreated, 126 isAuthor: true, 127 commentBody: "/release-note-action-required", 128 currentLabels: []string{releaseNoteLabelNeeded, "other"}, 129 shouldComment: true, 130 }, 131 { 132 name: "release-note-none, delete multiple labels", 133 action: github.IssueCommentActionCreated, 134 isMember: true, 135 commentBody: "/release-note-none", 136 currentLabels: []string{releaseNote, releaseNoteLabelNeeded, releaseNoteActionRequired, releaseNoteNone, "other"}, 137 138 deletedLabels: []string{releaseNoteLabelNeeded, releaseNoteActionRequired, releaseNote}, 139 }, 140 { 141 name: "no label present", 142 action: github.IssueCommentActionCreated, 143 isMember: true, 144 commentBody: "/release-note-none", 145 146 addedLabel: releaseNoteNone, 147 }, 148 } 149 for _, tc := range testcases { 150 fc := &fakegithub.FakeClient{ 151 IssueComments: make(map[int][]github.IssueComment), 152 OrgMembers: map[string][]string{"": {"m"}}, 153 } 154 ice := github.IssueCommentEvent{ 155 Action: tc.action, 156 Comment: github.IssueComment{ 157 Body: tc.commentBody, 158 }, 159 Issue: github.Issue{ 160 Body: tc.issueBody, 161 User: github.User{Login: "a"}, 162 Number: 5, 163 State: "open", 164 PullRequest: &struct{}{}, 165 Assignees: []github.User{{Login: "r"}}, 166 }, 167 } 168 if tc.isAuthor { 169 ice.Comment.User.Login = "a" 170 } else if tc.isMember { 171 ice.Comment.User.Login = "m" 172 } 173 for _, l := range tc.currentLabels { 174 ice.Issue.Labels = append(ice.Issue.Labels, github.Label{Name: l}) 175 } 176 if err := handleComment(fc, logrus.WithField("plugin", pluginName), ice); err != nil { 177 t.Errorf("For case %s, did not expect error: %v", tc.name, err) 178 } 179 if tc.shouldComment && len(fc.IssueComments[5]) == 0 { 180 t.Errorf("For case %s, didn't comment but should have.", tc.name) 181 } 182 if len(fc.LabelsAdded) > 1 { 183 t.Errorf("For case %s, added more than one label: %v", tc.name, fc.LabelsAdded) 184 } else if len(fc.LabelsAdded) == 0 && tc.addedLabel != "" { 185 t.Errorf("For case %s, should have added %s but didn't.", tc.name, tc.addedLabel) 186 } else if len(fc.LabelsAdded) == 1 && fc.LabelsAdded[0] != "/#5:"+tc.addedLabel { 187 t.Errorf("For case %s, added wrong label. Got %s, expected %s", tc.name, fc.LabelsAdded[0], tc.addedLabel) 188 } 189 190 var expectedDeleted []string 191 for _, expect := range tc.deletedLabels { 192 expectedDeleted = append(expectedDeleted, "/#5:"+expect) 193 } 194 sort.Strings(expectedDeleted) 195 sort.Strings(fc.LabelsRemoved) 196 if !reflect.DeepEqual(expectedDeleted, fc.LabelsRemoved) { 197 t.Errorf( 198 "For case %s, expected %q labels to be deleted, but %q were deleted.", 199 tc.name, 200 expectedDeleted, 201 fc.LabelsRemoved, 202 ) 203 } 204 } 205 } 206 207 const lgtmLabel = "lgtm" 208 209 func formatLabels(num int, labels ...string) []string { 210 out := make([]string, 0, len(labels)) 211 for _, l := range labels { 212 out = append(out, fmt.Sprintf("org/repo#%d:%s", num, l)) 213 } 214 return out 215 } 216 217 func newFakeClient(body, branch string, initialLabels, comments []string, parentPRs map[int]string) (*fakegithub.FakeClient, *github.PullRequestEvent) { 218 labels := formatLabels(1, initialLabels...) 219 for parent, l := range parentPRs { 220 labels = append(labels, formatLabels(parent, l)...) 221 } 222 var issueComments []github.IssueComment 223 for _, comment := range comments { 224 issueComments = append(issueComments, github.IssueComment{Body: comment}) 225 } 226 return &fakegithub.FakeClient{ 227 IssueComments: map[int][]github.IssueComment{1: issueComments}, 228 ExistingLabels: []string{ 229 lgtmLabel, 230 releaseNote, 231 releaseNoteLabelNeeded, 232 releaseNoteNone, 233 releaseNoteActionRequired, 234 }, 235 LabelsAdded: labels, 236 LabelsRemoved: []string{}, 237 }, 238 &github.PullRequestEvent{ 239 Action: github.PullRequestActionEdited, 240 Number: 1, 241 PullRequest: github.PullRequest{ 242 Base: github.PullRequestBranch{Ref: branch}, 243 Number: 1, 244 Body: body, 245 User: github.User{Login: "cjwagner"}, 246 }, 247 Repo: github.Repo{ 248 Owner: github.User{Login: "org"}, 249 Name: "repo", 250 }, 251 } 252 } 253 254 func TestReleaseNotePR(t *testing.T) { 255 tests := []struct { 256 name string 257 initialLabels []string 258 body string 259 branch string // Defaults to master 260 parentPRs map[int]string 261 issueComments []string 262 labelsAdded []string 263 labelsRemoved []string 264 }{ 265 { 266 name: "LGTM with release-note", 267 initialLabels: []string{lgtmLabel, releaseNote}, 268 body: "```release-note\n note note note.\n```", 269 }, 270 { 271 name: "LGTM with release-note, arbitrary comment", 272 initialLabels: []string{lgtmLabel, releaseNote}, 273 body: "```release-note\n note note note.\n```", 274 issueComments: []string{"Release notes are great fun."}, 275 }, 276 { 277 name: "LGTM with release-note-none", 278 initialLabels: []string{lgtmLabel, releaseNoteNone}, 279 body: "```release-note\nnone\n```", 280 }, 281 { 282 name: "LGTM with release-note-none, /release-note-none comment, empty block", 283 initialLabels: []string{lgtmLabel, releaseNoteNone}, 284 body: "```release-note\n```", 285 issueComments: []string{"/release-note-none "}, 286 }, 287 { 288 name: "LGTM with release-note-action-required", 289 initialLabels: []string{lgtmLabel, releaseNoteActionRequired}, 290 body: "```release-note\n Action required.\n```", 291 }, 292 { 293 name: "LGTM with release-note-action-required, /release-note-none comment", 294 initialLabels: []string{lgtmLabel, releaseNoteActionRequired}, 295 body: "```release-note\n Action required.\n```", 296 issueComments: []string{"Release notes are great fun.", "Especially \n/release-note-none"}, 297 }, 298 { 299 name: "LGTM with do-not-merge/release-note-label-needed", 300 initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded}, 301 }, 302 { 303 name: "LGTM with do-not-merge/release-note-label-needed, /release-note-none comment", 304 initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded}, 305 issueComments: []string{"Release notes are great fun.", "Especially \n/release-note-none"}, 306 labelsAdded: []string{releaseNoteNone}, 307 labelsRemoved: []string{releaseNoteLabelNeeded}, 308 }, 309 { 310 name: "LGTM only", 311 initialLabels: []string{lgtmLabel}, 312 labelsAdded: []string{releaseNoteLabelNeeded}, 313 }, 314 { 315 name: "No labels", 316 initialLabels: []string{}, 317 labelsAdded: []string{releaseNoteLabelNeeded}, 318 }, 319 { 320 name: "release-note", 321 initialLabels: []string{releaseNote}, 322 body: "```release-note normal note.```", 323 }, 324 { 325 name: "release-note, /release-note-none comment", 326 initialLabels: []string{releaseNote}, 327 body: "```release-note normal note.```", 328 issueComments: []string{"/release-note-none "}, 329 }, 330 { 331 name: "release-note-none", 332 initialLabels: []string{releaseNoteNone}, 333 body: "```release-note\nnone\n```", 334 }, 335 { 336 name: "release-note-action-required", 337 initialLabels: []string{releaseNoteActionRequired}, 338 body: "```release-note\n action required```", 339 }, 340 { 341 name: "release-note and do-not-merge/release-note-label-needed with no note", 342 initialLabels: []string{releaseNote, releaseNoteLabelNeeded}, 343 labelsRemoved: []string{releaseNote}, 344 }, 345 { 346 name: "release-note and do-not-merge/release-note-label-needed with note", 347 initialLabels: []string{releaseNote, releaseNoteLabelNeeded}, 348 body: "```release-note note ```", 349 labelsRemoved: []string{releaseNoteLabelNeeded}, 350 }, 351 { 352 name: "release-note-none and do-not-merge/release-note-label-needed", 353 initialLabels: []string{releaseNoteNone, releaseNoteLabelNeeded}, 354 body: "```release-note\nnone\n```", 355 labelsRemoved: []string{releaseNoteLabelNeeded}, 356 }, 357 { 358 name: "release-note-action-required and do-not-merge/release-note-label-needed", 359 initialLabels: []string{releaseNoteActionRequired, releaseNoteLabelNeeded}, 360 body: "```release-note\nSomething something dark side. Something something ACTION REQUIRED.```", 361 labelsRemoved: []string{releaseNoteLabelNeeded}, 362 }, 363 { 364 name: "do not add needs label when parent PR has releaseNote label", 365 branch: "release-1.2", 366 initialLabels: []string{}, 367 body: "Cherry pick of #2 on release-1.2.", 368 parentPRs: map[int]string{2: releaseNote}, 369 }, 370 { 371 name: "do not touch LGTM on non-master when parent PR has releaseNote label, but remove releaseNoteNeeded", 372 branch: "release-1.2", 373 initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded}, 374 body: "Cherry pick of #2 on release-1.2.", 375 parentPRs: map[int]string{2: releaseNote}, 376 labelsRemoved: []string{releaseNoteLabelNeeded}, 377 }, 378 { 379 name: "do nothing when PR has releaseNoteActionRequired, but parent PR does not have releaseNote label", 380 branch: "release-1.2", 381 initialLabels: []string{releaseNoteActionRequired}, 382 body: "Cherry pick of #2 on release-1.2.\n```release-note note action required note\n```", 383 parentPRs: map[int]string{2: releaseNoteNone}, 384 }, 385 { 386 name: "add releaseNoteNeeded on non-master when parent PR has releaseNoteNone label", 387 branch: "release-1.2", 388 initialLabels: []string{lgtmLabel}, 389 body: "Cherry pick of #2 on release-1.2.", 390 parentPRs: map[int]string{2: releaseNoteNone}, 391 labelsAdded: []string{releaseNoteLabelNeeded}, 392 }, 393 { 394 name: "add releaseNoteNeeded on non-master when 1 of 2 parent PRs has releaseNoteNone", 395 branch: "release-1.2", 396 initialLabels: []string{lgtmLabel}, 397 body: "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n", 398 parentPRs: map[int]string{2: releaseNote, 4: releaseNoteNone}, 399 labelsAdded: []string{releaseNoteLabelNeeded}, 400 }, 401 { 402 name: "remove releaseNoteNeeded on non-master when both parent PRs have a release note", 403 branch: "release-1.2", 404 initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded}, 405 body: "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n", 406 parentPRs: map[int]string{2: releaseNote, 4: releaseNoteActionRequired}, 407 labelsRemoved: []string{releaseNoteLabelNeeded}, 408 }, 409 { 410 name: "add releaseNoteActionRequired on non-master when body contains note even though both parent PRs have a release note (non-mandatory RN)", 411 branch: "release-1.2", 412 initialLabels: []string{lgtmLabel, releaseNoteLabelNeeded}, 413 body: "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n```release-note\nSome changes were made but there still is action required.\n```", 414 parentPRs: map[int]string{2: releaseNote, 4: releaseNoteActionRequired}, 415 labelsAdded: []string{releaseNoteActionRequired}, 416 labelsRemoved: []string{releaseNoteLabelNeeded}, 417 }, 418 { 419 name: "add releaseNoteNeeded, remove release-note on non-master when release-note block is removed and parent PR has releaseNoteNone label", 420 branch: "release-1.2", 421 initialLabels: []string{lgtmLabel, releaseNote}, 422 body: "Cherry pick of #2 on release-1.2.\n```release-note\n```\n/cc @cjwagner", 423 parentPRs: map[int]string{2: releaseNoteNone}, 424 labelsAdded: []string{releaseNoteLabelNeeded}, 425 labelsRemoved: []string{releaseNote}, 426 }, 427 { 428 name: "add releaseNoteLabelNeeded, remove release-note on non-master when release-note block is removed and parent PR has releaseNoteNone label", 429 branch: "release-1.2", 430 initialLabels: []string{lgtmLabel, releaseNote}, 431 body: "Cherry pick of #2 on release-1.2.\n```release-note\n```\n/cc @cjwagner", 432 parentPRs: map[int]string{2: releaseNoteNone}, 433 labelsAdded: []string{releaseNoteLabelNeeded}, 434 labelsRemoved: []string{releaseNote}, 435 }, 436 } 437 for _, test := range tests { 438 if test.branch == "" { 439 test.branch = "master" 440 } 441 fc, pr := newFakeClient(test.body, test.branch, test.initialLabels, test.issueComments, test.parentPRs) 442 443 err := handlePR(fc, logrus.WithField("plugin", pluginName), pr) 444 if err != nil { 445 t.Fatalf("Unexpected error from handlePR: %v", err) 446 } 447 448 // Check that all the correct labels (and only the correct labels) were added. 449 expectAdded := formatLabels(1, append(test.initialLabels, test.labelsAdded...)...) 450 for parent, label := range test.parentPRs { 451 expectAdded = append(expectAdded, formatLabels(parent, label)...) 452 } 453 sort.Strings(expectAdded) 454 sort.Strings(fc.LabelsAdded) 455 if !reflect.DeepEqual(expectAdded, fc.LabelsAdded) { 456 t.Errorf("(%s): Expected labels to be added: %q, but got: %q.", test.name, expectAdded, fc.LabelsAdded) 457 } 458 expectRemoved := formatLabels(1, test.labelsRemoved...) 459 sort.Strings(expectRemoved) 460 sort.Strings(fc.LabelsRemoved) 461 if !reflect.DeepEqual(expectRemoved, fc.LabelsRemoved) { 462 t.Errorf("(%s): Expected labels to be removed: %q, but got %q.", test.name, expectRemoved, fc.LabelsRemoved) 463 } 464 } 465 } 466 467 func TestGetReleaseNote(t *testing.T) { 468 tests := []struct { 469 body string 470 expectedReleaseNote string 471 expectedReleaseNoteVariable string 472 }{ 473 { 474 body: "**Release note**: ```NONE```", 475 expectedReleaseNote: "NONE", 476 expectedReleaseNoteVariable: releaseNoteNone, 477 }, 478 { 479 body: "**Release note**:\n\n ```\nNONE\n```", 480 expectedReleaseNote: "NONE", 481 expectedReleaseNoteVariable: releaseNoteNone, 482 }, 483 { 484 body: "**Release note**:\n<!-- Steps to write your release note:\n...\n-->\n```NONE\n```", 485 expectedReleaseNote: "NONE", 486 expectedReleaseNoteVariable: releaseNoteNone, 487 }, 488 { 489 body: "**Release note**:\n\n ```This is a description of my feature```", 490 expectedReleaseNote: "This is a description of my feature", 491 expectedReleaseNoteVariable: releaseNote, 492 }, 493 { 494 body: "**Release note**: ```This is my feature. There is some action required for my feature.```", 495 expectedReleaseNote: "This is my feature. There is some action required for my feature.", 496 expectedReleaseNoteVariable: releaseNoteActionRequired, 497 }, 498 { 499 body: "```release-note\nsomething great.\n```", 500 expectedReleaseNote: "something great.", 501 expectedReleaseNoteVariable: releaseNote, 502 }, 503 { 504 body: "```release-note\nNONE\n```", 505 expectedReleaseNote: "NONE", 506 expectedReleaseNoteVariable: releaseNoteNone, 507 }, 508 { 509 body: "```release-note\n`NONE`\n```", 510 expectedReleaseNote: "`NONE`", 511 expectedReleaseNoteVariable: releaseNoteNone, 512 }, 513 { 514 body: "```release-note\n`\"NONE\"`\n```", 515 expectedReleaseNote: "`\"NONE\"`", 516 expectedReleaseNoteVariable: releaseNoteNone, 517 }, 518 { 519 body: "**Release note**:\n```release-note\nNONE\n```\n", 520 expectedReleaseNote: "NONE", 521 expectedReleaseNoteVariable: releaseNoteNone, 522 }, 523 { 524 body: "", 525 expectedReleaseNote: "", 526 expectedReleaseNoteVariable: releaseNoteLabelNeeded, 527 }, 528 } 529 530 for testNum, test := range tests { 531 calculatedReleaseNote := getReleaseNote(test.body) 532 if test.expectedReleaseNote != calculatedReleaseNote { 533 t.Errorf("Test %v: Expected %v as the release note, got %v", testNum, test.expectedReleaseNote, calculatedReleaseNote) 534 } 535 calculatedLabel := determineReleaseNoteLabel(test.body) 536 if test.expectedReleaseNoteVariable != calculatedLabel { 537 t.Errorf("Test %v: Expected %v as the release note label, got %v", testNum, test.expectedReleaseNoteVariable, calculatedLabel) 538 } 539 } 540 }