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