github.com/abayer/test-infra@v0.0.5/mungegithub/mungers/milestone-maintainer_test.go (about) 1 /* 2 Copyright 2017 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 mungers 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "net/http" 23 "reflect" 24 "strings" 25 "testing" 26 "time" 27 28 "k8s.io/apimachinery/pkg/util/sets" 29 "k8s.io/test-infra/mungegithub/github" 30 github_test "k8s.io/test-infra/mungegithub/github/testing" 31 c "k8s.io/test-infra/mungegithub/mungers/matchers/comment" 32 33 githubapi "github.com/google/go-github/github" 34 ) 35 36 const milestoneTestBotName = "test-bot" 37 38 // TestMilestoneMaintainer validates that notification state can be 39 // determined and applied to an issue. Comprehensive testing is left 40 // to TestNotificationState. 41 // 42 // TODO(marun) Enable testing of comment deletion 43 func TestMilestoneMaintainer(t *testing.T) { 44 activeMilestone := "v1.10" 45 milestone := &githubapi.Milestone{Title: &activeMilestone, Number: intPtr(1)} 46 m := MilestoneMaintainer{ 47 milestoneModeMap: map[string]string{activeMilestone: milestoneModeDev}, 48 approvalGracePeriod: 72 * time.Hour, 49 labelGracePeriod: 72 * time.Hour, 50 warningInterval: 24 * time.Hour, 51 } 52 53 issue := github_test.Issue("user", 1, []string{"kind/bug", "sig/foo", "priority/important-soon"}, false) 54 issue.Milestone = milestone 55 56 config := &github.Config{Org: "o", Project: "r"} 57 client, server, mux := github_test.InitServer(t, issue, nil, nil, nil, nil, nil, nil) 58 config.SetClient(client) 59 60 path := fmt.Sprintf("/repos/%s/%s/issues/%d", config.Org, config.Project, *issue.Number) 61 62 mux.HandleFunc(fmt.Sprintf("%s/labels", path), func(w http.ResponseWriter, r *http.Request) { 63 w.WriteHeader(http.StatusOK) 64 out := []githubapi.Label{{}} 65 data, err := json.Marshal(out) 66 if err != nil { 67 t.Errorf("Unexpected error: %v", err) 68 } 69 w.Write(data) 70 }) 71 72 var comments []githubapi.IssueComment 73 mux.HandleFunc(fmt.Sprintf("%s/comments", path), func(w http.ResponseWriter, r *http.Request) { 74 if r.Method == "POST" { 75 c := new(githubapi.IssueComment) 76 json.NewDecoder(r.Body).Decode(c) 77 comments = append(comments, *c) 78 w.WriteHeader(http.StatusOK) 79 data, err := json.Marshal(githubapi.IssueComment{}) 80 if err != nil { 81 t.Errorf("Unexpected error: %v", err) 82 } 83 w.Write(data) 84 return 85 } 86 if r.Method == "GET" { 87 w.WriteHeader(http.StatusOK) 88 data, err := json.Marshal([]githubapi.IssueComment{}) 89 if err != nil { 90 t.Errorf("Unexpected error: %v", err) 91 } 92 w.Write(data) 93 return 94 } 95 t.Fatalf("Unexpected method: %s", r.Method) 96 }) 97 98 obj, err := config.GetObject(*issue.Number) 99 if err != nil { 100 t.Fatal(err) 101 } 102 103 m.Munge(obj) 104 105 expectedLabel := milestoneNeedsApprovalLabel 106 if !obj.HasLabel(expectedLabel) { 107 t.Fatalf("Issue labels do not include '%s'", expectedLabel) 108 } 109 110 if len(comments) != 1 { 111 t.Fatalf("Expected comment count of %d, got %d", 1, len(comments)) 112 } 113 114 expectedBody := `[MILESTONENOTIFIER] Milestone Issue **Needs Approval** 115 116 @user @kubernetes/sig-foo-misc 117 118 119 **Action required**: This issue must have the ` + "`status/approved-for-milestone`" + ` label applied by a SIG maintainer. If the label is not applied within 3 days, the issue will be moved out of the v1.10 milestone. 120 <details> 121 <summary>Issue Labels</summary> 122 123 - ` + "`sig/foo`" + `: Issue will be escalated to these SIGs if needed. 124 - ` + "`priority/important-soon`" + `: Escalate to the issue owners and SIG owner; move out of milestone after several unsuccessful escalation attempts. 125 - ` + "`kind/bug`" + `: Fixes a bug discovered during the current release. 126 </details> 127 <details> 128 <summary>Help</summary> 129 <ul> 130 <li><a href="https://git.k8s.io/sig-release/ephemera/issues.md">Additional instructions</a></li> 131 <li><a href="https://go.k8s.io/bot-commands">Commands for setting labels</a></li> 132 </ul> 133 </details>` 134 if comments[0].Body == nil || expectedBody != *comments[0].Body { 135 t.Fatalf("Expected Body:\n\n%s\n\nGot:\n\n%s", expectedBody, *comments[0].Body) 136 } 137 138 server.Close() 139 } 140 141 // TestNewIssueChangeConfig validates the creation of an IssueChange 142 // for a given issue state. 143 func TestNewIssueChangeConfig(t *testing.T) { 144 const incompleteLabels = ` 145 _**kind**_: Must specify exactly one of ` + "`kind/bug`, `kind/cleanup` or `kind/feature`." + ` 146 _**sig owner**_: Must specify at least one label prefixed with ` + "`sig/`." + ` 147 ` 148 const blockerCompleteLabels = ` 149 <summary>Issue Labels</summary> 150 151 - ` + "`sig/foo`: Issue will be escalated to these SIGs if needed." + ` 152 - ` + "`priority/critical-urgent`: Never automatically move issue out of a release milestone; continually escalate to contributor and SIG through all available channels." + ` 153 - ` + "`kind/bug`: Fixes a bug discovered during the current release." + ` 154 </details> 155 ` 156 157 const nonBlockerCompleteLabels = ` 158 <summary>Issue Labels</summary> 159 160 - ` + "`sig/foo`: Issue will be escalated to these SIGs if needed." + ` 161 - ` + "`priority/important-soon`: Escalate to the issue owners and SIG owner; move out of milestone after several unsuccessful escalation attempts." + ` 162 - ` + "`kind/bug`: Fixes a bug discovered during the current release." + ` 163 </details> 164 ` 165 166 milestoneString := "v1.8" 167 168 munger := &MilestoneMaintainer{ 169 botName: milestoneTestBotName, 170 labelGracePeriod: 3 * day, 171 approvalGracePeriod: 7 * day, 172 warningInterval: day, 173 slushUpdateInterval: 3 * day, 174 freezeDate: "the time heck freezes over", 175 } 176 177 createdNow := time.Now() 178 createdPastLabelGracePeriod := createdNow.Add(-(munger.labelGracePeriod + time.Hour)) 179 createdPastApprovalGracePeriod := createdNow.Add(-(munger.approvalGracePeriod + time.Hour)) 180 createdPastSlushUpdateInterval := createdNow.Add(-(munger.slushUpdateInterval + time.Hour)) 181 182 tests := map[string]struct { 183 // The mode of the munger 184 mode string 185 // Labels to add to the test issue 186 labels []string 187 // Whether the test issues milestone labels should be complete 188 labelsComplete bool 189 // Whether the test issue should be a blocker 190 isBlocker bool 191 // Whether the test issue should be approved for the milestone 192 isApproved bool 193 // Events to add to the test issue 194 events []*githubapi.IssueEvent 195 // Comments to add to the test issue 196 comments []*githubapi.IssueComment 197 // Sections expected to be enabled 198 expectedSections sets.String 199 // Expected milestone state 200 expectedState milestoneState 201 // Expected message body 202 expectedBody string 203 }{ 204 "Incomplete labels within grace period": { 205 expectedSections: sets.NewString("warnIncompleteLabels"), 206 expectedState: milestoneNeedsLabeling, 207 expectedBody: ` 208 **Action required**: This issue requires label changes. If the required changes are not made within 3 days, the issue will be moved out of the v1.8 milestone. 209 ` + incompleteLabels, 210 }, 211 "Incomplete labels outside of grace period": { 212 labels: []string{milestoneLabelsIncompleteLabel}, 213 events: milestoneLabelEvents(milestoneLabelsIncompleteLabel, createdPastLabelGracePeriod), 214 expectedSections: sets.NewString("removeIncompleteLabels"), 215 expectedState: milestoneNeedsRemoval, 216 expectedBody: ` 217 **Important**: This issue was missing labels required for the v1.8 milestone for more than 3 days: 218 219 _**kind**_: Must specify exactly one of ` + "`kind/bug`, `kind/cleanup` or `kind/feature`." + ` 220 _**sig owner**_: Must specify at least one label prefixed with ` + "`sig/`.", 221 }, 222 "Incomplete labels outside of grace period, blocker": { 223 labels: []string{milestoneLabelsIncompleteLabel}, 224 isBlocker: true, 225 events: milestoneLabelEvents(milestoneLabelsIncompleteLabel, createdPastLabelGracePeriod), 226 expectedSections: sets.NewString("warnIncompleteLabels"), 227 expectedState: milestoneNeedsLabeling, 228 expectedBody: ` 229 **Action required**: This issue requires label changes. 230 231 _**kind**_: Must specify exactly one of ` + "`kind/bug`, `kind/cleanup` or `kind/feature`." + ` 232 _**sig owner**_: Must specify at least one label prefixed with ` + "`sig/`." + ` 233 `, 234 }, 235 "Complete labels, not approved, blocker": { 236 labelsComplete: true, 237 isBlocker: true, 238 expectedSections: sets.NewString("summarizeLabels", "warnUnapproved"), 239 expectedState: milestoneNeedsApproval, 240 expectedBody: ` 241 **Action required**: This issue must have the ` + "`status/approved-for-milestone`" + ` label applied by a SIG maintainer. 242 <details>` + blockerCompleteLabels, 243 }, 244 "Complete labels, not approved, non-blocker, within grace period": { 245 labelsComplete: true, 246 expectedSections: sets.NewString("summarizeLabels", "warnUnapproved"), 247 expectedState: milestoneNeedsApproval, 248 expectedBody: ` 249 **Action required**: This issue must have the ` + "`status/approved-for-milestone`" + ` label applied by a SIG maintainer. If the label is not applied within 7 days, the issue will be moved out of the v1.8 milestone. 250 <details>` + nonBlockerCompleteLabels, 251 }, 252 "Complete labels, not approved, non-blocker, outside of grace period": { 253 labels: []string{milestoneNeedsApprovalLabel}, 254 labelsComplete: true, 255 events: milestoneLabelEvents(milestoneNeedsApprovalLabel, createdPastApprovalGracePeriod), 256 expectedSections: sets.NewString("summarizeLabels", "removeUnapproved"), 257 expectedState: milestoneNeedsRemoval, 258 expectedBody: "**Important**: This issue was missing the `status/approved-for-milestone` label for more than 7 days.", 259 }, 260 "dev - Complete labels and approved": { 261 labelsComplete: true, 262 isApproved: true, 263 expectedSections: sets.NewString("summarizeLabels"), 264 expectedState: milestoneCurrent, 265 expectedBody: "<details open>" + nonBlockerCompleteLabels, 266 }, 267 "slush - Complete labels, approved, non-blocker, missing in-progress": { 268 mode: milestoneModeSlush, 269 labelsComplete: true, 270 isApproved: true, 271 expectedSections: sets.NewString("summarizeLabels", "warnMissingInProgress", "warnNonBlockerRemoval"), 272 expectedState: milestoneNeedsAttention, 273 expectedBody: ` 274 **Action required**: During code slush, issues in the milestone should be in progress. 275 If this issue is not being actively worked on, please remove it from the milestone. 276 If it is being worked on, please add the ` + "`status/in-progress`" + ` label so it can be tracked with other in-flight issues. 277 278 **Note**: If this issue is not resolved or labeled as ` + "`priority/critical-urgent`" + ` by the time heck freezes over it will be moved out of the v1.8 milestone. 279 <details>` + nonBlockerCompleteLabels, 280 }, 281 "slush - Complete labels, approved, non-blocker": { 282 mode: milestoneModeSlush, 283 labels: []string{"status/in-progress"}, 284 labelsComplete: true, 285 isApproved: true, 286 expectedSections: sets.NewString("summarizeLabels", "warnNonBlockerRemoval"), 287 expectedState: milestoneCurrent, 288 expectedBody: ` 289 **Note**: If this issue is not resolved or labeled as ` + "`priority/critical-urgent`" + ` by the time heck freezes over it will be moved out of the v1.8 milestone. 290 <details open>` + nonBlockerCompleteLabels, 291 }, 292 "slush - Complete labels, approved, blocker, missing in-progress, update not due": { 293 mode: milestoneModeSlush, 294 labelsComplete: true, 295 isApproved: true, 296 isBlocker: true, 297 events: milestoneLabelEvents(statusApprovedLabel, createdNow), 298 comments: milestoneIssueComments(createdNow), 299 expectedSections: sets.NewString("summarizeLabels", "warnMissingInProgress", "warnUpdateInterval"), 300 expectedState: milestoneNeedsAttention, 301 expectedBody: ` 302 **Action required**: During code slush, issues in the milestone should be in progress. 303 If this issue is not being actively worked on, please remove it from the milestone. 304 If it is being worked on, please add the ` + "`status/in-progress`" + ` label so it can be tracked with other in-flight issues. 305 306 **Note**: This issue is marked as ` + "`priority/critical-urgent`" + `, and must be updated every 3 days during code slush. 307 308 Example update: 309 310 ` + "```" + ` 311 ACK. In progress 312 ETA: DD/MM/YYYY 313 Risks: Complicated fix required 314 ` + "```" + ` 315 <details>` + blockerCompleteLabels, 316 }, 317 "slush - Complete labels, approved, blocker, update not due": { 318 mode: milestoneModeSlush, 319 labels: []string{"status/in-progress"}, 320 labelsComplete: true, 321 isApproved: true, 322 isBlocker: true, 323 events: milestoneLabelEvents(statusApprovedLabel, createdNow), 324 comments: milestoneIssueComments(createdNow), 325 expectedSections: sets.NewString("summarizeLabels", "warnUpdateInterval"), 326 expectedState: milestoneCurrent, 327 expectedBody: ` 328 **Note**: This issue is marked as ` + "`priority/critical-urgent`" + `, and must be updated every 3 days during code slush. 329 330 Example update: 331 332 ` + "```" + ` 333 ACK. In progress 334 ETA: DD/MM/YYYY 335 Risks: Complicated fix required 336 ` + "```" + ` 337 <details open>` + blockerCompleteLabels, 338 }, 339 "slush - Complete labels, approved, blocker, update due": { 340 mode: milestoneModeSlush, 341 labels: []string{"status/in-progress"}, 342 labelsComplete: true, 343 isApproved: true, 344 isBlocker: true, 345 events: milestoneLabelEvents(statusApprovedLabel, createdNow), 346 comments: milestoneIssueComments(createdPastSlushUpdateInterval), 347 expectedSections: sets.NewString("summarizeLabels", "warnUpdateInterval", "warnUpdateRequired"), 348 expectedState: milestoneNeedsAttention, 349 expectedBody: ` 350 **Action Required**: This issue has not been updated since ` + createdPastSlushUpdateInterval.Format("Jan 2") + `. Please provide an update. 351 352 **Note**: This issue is marked as ` + "`priority/critical-urgent`" + `, and must be updated every 3 days during code slush. 353 354 Example update: 355 356 ` + "```" + ` 357 ACK. In progress 358 ETA: DD/MM/YYYY 359 Risks: Complicated fix required 360 ` + "```" + ` 361 <details>` + blockerCompleteLabels, 362 }, 363 "freeze - Complete labels, approved, non-blocker": { 364 mode: milestoneModeFreeze, 365 labelsComplete: true, 366 isApproved: true, 367 expectedSections: sets.NewString("summarizeLabels", "removeNonBlocker"), 368 expectedState: milestoneNeedsRemoval, 369 expectedBody: "**Important**: Code freeze is in effect and only issues with `priority/critical-urgent` may remain in the v1.8 milestone.", 370 }, 371 } 372 for testName, test := range tests { 373 t.Run(testName, func(t *testing.T) { 374 mode := milestoneModeDev 375 if len(test.mode) > 0 { 376 mode = test.mode 377 } 378 munger.milestoneModeMap = map[string]string{milestoneString: mode} 379 380 labels := test.labels 381 if test.isBlocker { 382 labels = append(labels, blockerLabel) 383 } else { 384 labels = append(labels, "priority/important-soon") 385 } 386 if test.labelsComplete { 387 labels = append(labels, "kind/bug") 388 labels = append(labels, "sig/foo") 389 } 390 if test.isApproved { 391 labels = append(labels, statusApprovedLabel) 392 } 393 394 issue := github_test.Issue("user", 1, labels, false) 395 // Ensure issue was created before any comments or events 396 createdLongAgo := createdNow.Add(-28 * day) 397 issue.CreatedAt = &createdLongAgo 398 milestone := &githubapi.Milestone{Title: stringPtr(milestoneString), Number: intPtr(1)} 399 issue.Milestone = milestone 400 401 client, server, mux := github_test.InitServer(t, issue, nil, test.events, nil, nil, nil, nil) 402 defer server.Close() 403 404 config := &github.Config{Org: "o", Project: "r"} 405 406 path := fmt.Sprintf("/repos/%s/%s/issues/%d", config.Org, config.Project, *issue.Number) 407 mux.HandleFunc(fmt.Sprintf("%s/comments", path), func(w http.ResponseWriter, r *http.Request) { 408 if r.Method == "GET" { 409 w.WriteHeader(http.StatusOK) 410 data, err := json.Marshal(test.comments) 411 if err != nil { 412 t.Errorf("Unexpected error: %v", err) 413 } 414 w.Write(data) 415 return 416 } 417 t.Fatalf("Unexpected method: %s", r.Method) 418 }) 419 420 config.SetClient(client) 421 obj, err := config.GetObject(*issue.Number) 422 if err != nil { 423 t.Fatal(err) 424 } 425 426 icc := munger.issueChangeConfig(obj) 427 if icc == nil { 428 t.Fatalf("%s: Expected non-nil issue change config", testName) 429 } 430 431 if !test.expectedSections.Equal(icc.enabledSections) { 432 t.Fatalf("%s: Expected sections %v, got %v", testName, test.expectedSections, icc.enabledSections) 433 } 434 435 if test.expectedState != icc.state { 436 t.Fatalf("%s: Expected state %v, got %v", testName, test.expectedState, icc.state) 437 } 438 439 messageBody := icc.messageBody() 440 if messageBody == nil { 441 t.Fatalf("%s: Expected non-nil message body", testName) 442 } 443 expectedBody := strings.TrimSpace(test.expectedBody) 444 trimmedBody := strings.TrimSpace(*messageBody) 445 if expectedBody != trimmedBody { 446 t.Fatalf("%s: Expected message body:\n\n%s\nGot:\n\n%s", testName, expectedBody, trimmedBody) 447 } 448 }) 449 } 450 } 451 452 func milestoneTestComment(title string, context string, createdAt time.Time) *c.Comment { 453 n := &c.Notification{ 454 Name: milestoneNotifierName, 455 Arguments: title, 456 Context: context, 457 } 458 return &c.Comment{ 459 Body: stringPtr(n.String()), 460 CreatedAt: &createdAt, 461 } 462 } 463 464 func milestoneLabelEvents(label string, createdAt time.Time) []*githubapi.IssueEvent { 465 return []*githubapi.IssueEvent{ 466 { 467 Event: stringPtr("labeled"), 468 Label: &githubapi.Label{ 469 Name: &label, 470 }, 471 CreatedAt: &createdAt, 472 Actor: &githubapi.User{ 473 Login: stringPtr(milestoneTestBotName), 474 }, 475 }, 476 } 477 } 478 479 func milestoneIssueComments(createdAt time.Time) []*githubapi.IssueComment { 480 return []*githubapi.IssueComment{ 481 { 482 Body: stringPtr("foo"), 483 UpdatedAt: &createdAt, 484 CreatedAt: &createdAt, 485 User: &githubapi.User{ 486 Login: githubapi.String("bar"), 487 }, 488 }, 489 } 490 } 491 492 func TestNotificationIsCurrent(t *testing.T) { 493 createdNow := time.Now() 494 warningInterval := day 495 createdYesterday := createdNow.Add(-(warningInterval + time.Hour)) 496 497 realSample := "@foo @bar @baz\n\n**Action required**: This issue requires label changes. If the required changes are not made within 6 days, the issue will be moved out of the v1.8 milestone.\n\n_**kind**_: Must specify at most one of [`kind/bug`, `kind/cleanup`, `kind/feature`].\n_**priority**_: Must specify at most one of [`priority/critical-urgent`, `priority/important-longterm`, `priority/important-soon`].\n_**sig owner**_: Must specify at least one label prefixed with `sig/`.\n\n<details>\nAdditional instructions available <a href=\"https://git.k8s.io/sig-release/ephemera/issues.md\">here</a>\n</details>" 498 499 tests := map[string]struct { 500 message string 501 newMessage string 502 createdAt time.Time 503 nilCommentInterval bool 504 expectedIsCurrent bool 505 }{ 506 "Not current if no notification exists": {}, 507 "Not current if the message is different": { 508 message: "foo", 509 newMessage: "bar", 510 }, 511 "Not current if the warning interval has elapsed": { 512 message: "foo", 513 newMessage: "foo", 514 createdAt: createdYesterday, 515 }, 516 "Not current if the message is different and the comment interval is nil": { 517 message: "foo", 518 newMessage: "bar", 519 nilCommentInterval: true, 520 }, 521 "Notification is current, real sample": { 522 message: realSample, 523 newMessage: realSample, 524 createdAt: createdNow, 525 expectedIsCurrent: true, 526 }, 527 } 528 for testName, test := range tests { 529 t.Run(testName, func(t *testing.T) { 530 var oldComment *c.Comment 531 if len(test.message) > 0 { 532 oldComment = milestoneTestComment("foo", test.message, test.createdAt) 533 } 534 newComment := milestoneTestComment("foo", test.newMessage, createdNow) 535 notification := c.ParseNotification(newComment) 536 var commentInterval *time.Duration 537 if !test.nilCommentInterval { 538 commentInterval = &warningInterval 539 } 540 isCurrent := notificationIsCurrent(notification, oldComment, commentInterval) 541 if test.expectedIsCurrent != isCurrent { 542 t.Logf("notification %#v\n", notification) 543 t.Fatalf("%s: expected warningIsCurrent to be %t, but got %t", testName, test.expectedIsCurrent, isCurrent) 544 } 545 }) 546 } 547 } 548 549 func TestIgnoreObject(t *testing.T) { 550 tests := map[string]struct { 551 isClosed bool 552 milestone string 553 activeMilestone string 554 expectedIgnore bool 555 }{ 556 "Ignore closed issue": { 557 isClosed: true, 558 expectedIgnore: true, 559 }, 560 "Do not ignore open issue": {}, 561 } 562 for testName, test := range tests { 563 t.Run(testName, func(t *testing.T) { 564 issue := github_test.Issue("user", 1, nil, false) 565 issue.Milestone = &githubapi.Milestone{Title: stringPtr(test.milestone), Number: intPtr(1)} 566 if test.isClosed { 567 issue.State = stringPtr("closed") 568 } 569 obj := &github.MungeObject{Issue: issue} 570 571 ignore := ignoreObject(obj) 572 573 if ignore != test.expectedIgnore { 574 t.Fatalf("%s: Expected ignore to be %t, got %t", testName, test.expectedIgnore, ignore) 575 } 576 }) 577 578 } 579 } 580 581 func TestUniqueLabelName(t *testing.T) { 582 labelMap := map[string]string{ 583 "foo": "", 584 "bar": "", 585 } 586 tests := map[string]struct { 587 labelNames []string 588 expectedLabel string 589 expectedErr bool 590 }{ 591 "Unmatched label set returns empty string": {}, 592 "Single label match returned": { 593 labelNames: []string{"foo"}, 594 expectedLabel: "foo", 595 }, 596 "Multiple label matches returns error": { 597 labelNames: []string{"foo", "bar"}, 598 expectedErr: true, 599 }, 600 } 601 for testName, test := range tests { 602 t.Run(testName, func(t *testing.T) { 603 labels := github_test.StringsToLabels(test.labelNames) 604 605 label, err := uniqueLabelName(labels, labelMap) 606 607 if label != test.expectedLabel { 608 t.Fatalf("%s: Expected label '%s', got '%s'", testName, test.expectedLabel, label) 609 } 610 if test.expectedErr && err == nil { 611 t.Fatalf("%s: Err expected but did not occur", testName) 612 } 613 if !test.expectedErr && err != nil { 614 t.Fatalf("%s: Unexpected error occurred", testName) 615 } 616 }) 617 } 618 } 619 620 func TestSigLabelNames(t *testing.T) { 621 labels := github_test.StringsToLabels([]string{"sig/foo", "sig/bar", "baz"}) 622 labelNames := sigLabelNames(labels) 623 // Expect labels without sig/ prefix to be filtered out 624 expectedLabelNames := []string{"sig/foo", "sig/bar"} 625 if len(expectedLabelNames) != len(labelNames) { 626 t.Fatalf("Wrong number of labels. Got %v, wanted %v.", labelNames, expectedLabelNames) 627 } 628 for _, ln1 := range expectedLabelNames { 629 var found bool 630 for _, ln2 := range labelNames { 631 if ln1 == ln2 { 632 found = true 633 } 634 } 635 if !found { 636 t.Errorf("Label %s not found in %v", ln1, labelNames) 637 } 638 } 639 } 640 641 func TestParseMilestoneModes(t *testing.T) { 642 tests := map[string]struct { 643 input string 644 output map[string]string 645 errExpected bool 646 }{ 647 "Empty string": { 648 errExpected: true, 649 }, 650 "Too many = separators": { 651 input: "v1.8==dev", 652 errExpected: true, 653 }, 654 "Too many , separators": { 655 input: "v1.8=dev,,v1.9=dev", 656 errExpected: true, 657 }, 658 "Missing milestone": { 659 input: "=dev", 660 errExpected: true, 661 }, 662 "Missing mode": { 663 input: "v1.8=", 664 errExpected: true, 665 }, 666 "Invalid mode": { 667 input: "v1.8=foo", 668 errExpected: true, 669 }, 670 "Duplicated milestone": { 671 input: "v1.8=dev,v1.8=slush", 672 errExpected: true, 673 }, 674 "Single milestone": { 675 input: "v1.8=dev", 676 output: map[string]string{"v1.8": "dev"}, 677 }, 678 "Multiple milestones": { 679 input: "v1.8=dev,v1.9=slush", 680 output: map[string]string{"v1.8": "dev", "v1.9": "slush"}, 681 }, 682 } 683 for testName, test := range tests { 684 output, err := parseMilestoneModes(test.input) 685 if test.errExpected && err == nil { 686 t.Fatalf("%s: Expected an error to have occurred", testName) 687 } 688 if !test.errExpected && err != nil { 689 t.Fatalf("%s: Expected no error but got: %v", testName, err) 690 } 691 692 if !reflect.DeepEqual(test.output, output) { 693 t.Fatalf("%s: Expected output %v, got %v", testName, test.output, output) 694 } 695 } 696 }