github.com/abayer/test-infra@v0.0.5/mungegithub/mungers/submit-queue_test.go (about) 1 /* 2 Copyright 2015 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 "net/http/httptest" 24 "runtime" 25 "strconv" 26 "strings" 27 "sync" 28 "testing" 29 "time" 30 31 utilclock "k8s.io/apimachinery/pkg/util/clock" 32 33 "k8s.io/contrib/test-utils/utils" 34 "k8s.io/test-infra/mungegithub/features" 35 github_util "k8s.io/test-infra/mungegithub/github" 36 github_test "k8s.io/test-infra/mungegithub/github/testing" 37 "k8s.io/test-infra/mungegithub/mungeopts" 38 "k8s.io/test-infra/mungegithub/mungers/e2e" 39 fake_e2e "k8s.io/test-infra/mungegithub/mungers/e2e/fake" 40 "k8s.io/test-infra/mungegithub/mungers/mungerutil" 41 "k8s.io/test-infra/mungegithub/options" 42 "k8s.io/test-infra/mungegithub/sharedmux" 43 44 "github.com/google/go-github/github" 45 ) 46 47 func stringPtr(val string) *string { return &val } 48 func boolPtr(val bool) *bool { return &val } 49 func intPtr(val int) *int { return &val } 50 func slicePtr(val []string) *[]string { return &val } 51 52 const ( 53 someUserName = "someUserName" 54 doNotMergeMilestone = "some-milestone-you-should-not-merge" 55 56 notRequiredReTestContext1 = "someNotRequiredForRetest1" 57 notRequiredReTestContext2 = "someNotRequiredForRetest2" 58 requiredReTestContext1 = "someRequiredRetestContext1" 59 requiredReTestContext2 = "someRequiredRetestContext2" 60 ) 61 62 var ( 63 someJobNames = []string{"foo", "bar"} 64 ) 65 66 func ValidPR() *github.PullRequest { 67 return github_test.PullRequest(someUserName, false, true, true) 68 } 69 70 func UnMergeablePR() *github.PullRequest { 71 return github_test.PullRequest(someUserName, false, true, false) 72 } 73 74 func UndeterminedMergeablePR() *github.PullRequest { 75 return github_test.PullRequest(someUserName, false, false, false) 76 } 77 78 func MasterCommit() *github.RepositoryCommit { 79 masterSHA := "mastersha" 80 return &github.RepositoryCommit{ 81 SHA: &masterSHA, 82 } 83 } 84 85 func BareIssue() *github.Issue { 86 return github_test.Issue(someUserName, 1, []string{}, true) 87 } 88 89 func LGTMIssue() *github.Issue { 90 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel}, true) 91 } 92 93 func LGTMApprovedIssue() *github.Issue { 94 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel}, true) 95 } 96 97 func LGTMApprovedCLAHumanApprovedIssue() *github.Issue { 98 return github_test.Issue(someUserName, 1, []string{claHumanLabel, lgtmLabel, approvedLabel}, true) 99 } 100 101 func CriticalFixLGTMApprovedIssue() *github.Issue { 102 return github_test.Issue(someUserName, 1, []string{criticalFixLabel, cncfClaYesLabel, lgtmLabel, approvedLabel}, true) 103 } 104 105 func OnlyApprovedIssue() *github.Issue { 106 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, approvedLabel}, true) 107 } 108 109 func DoNotMergeIssue() *github.Issue { 110 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, doNotMergeLabel}, true) 111 } 112 113 func CherrypickUnapprovedIssue() *github.Issue { 114 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, cherrypickUnapprovedLabel}, true) 115 } 116 117 func BlockedPathsIssue() *github.Issue { 118 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, blockedPathsLabel}, true) 119 } 120 121 func MissingReleaseNoteIssue() *github.Issue { 122 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, releaseNoteLabelNeeded}, true) 123 } 124 125 func WorkInProgressIssue() *github.Issue { 126 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, wipLabel}, true) 127 } 128 129 func HoldLabelIssue() *github.Issue { 130 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, holdLabel}, true) 131 } 132 133 func AdditionalLabelIssue(label string) *github.Issue { 134 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, label}, true) 135 } 136 137 func DoNotMergeMilestoneIssue() *github.Issue { 138 issue := github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel}, true) 139 milestone := &github.Milestone{ 140 Title: stringPtr(doNotMergeMilestone), 141 } 142 issue.Milestone = milestone 143 return issue 144 } 145 146 func NoCLAIssue() *github.Issue { 147 return github_test.Issue(someUserName, 1, []string{lgtmLabel}, true) 148 } 149 150 func NoLGTMIssue() *github.Issue { 151 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel}, true) 152 } 153 154 func NoRetestIssue() *github.Issue { 155 return github_test.Issue(someUserName, 1, []string{cncfClaYesLabel, lgtmLabel, approvedLabel, retestNotRequiredLabel}, true) 156 } 157 158 func OldLGTMEvents() []*github.IssueEvent { 159 return github_test.Events([]github_test.LabelTime{ 160 {User: "bob", Label: approvedLabel, Time: 20}, 161 {User: "bob", Label: lgtmLabel, Time: 6}, 162 {User: "bob", Label: lgtmLabel, Time: 7}, 163 {User: "bob", Label: lgtmLabel, Time: 8}, 164 }) 165 } 166 167 func NewLGTMEvents() []*github.IssueEvent { 168 return github_test.Events([]github_test.LabelTime{ 169 {User: "bob", Label: approvedLabel, Time: 20}, 170 {User: "bob", Label: lgtmLabel, Time: 10}, 171 {User: "bob", Label: lgtmLabel, Time: 11}, 172 {User: "bob", Label: lgtmLabel, Time: 12}, 173 }) 174 } 175 176 func OverlappingLGTMEvents() []*github.IssueEvent { 177 return github_test.Events([]github_test.LabelTime{ 178 {User: "bob", Label: approvedLabel, Time: 20}, 179 {User: "bob", Label: lgtmLabel, Time: 8}, 180 {User: "bob", Label: lgtmLabel, Time: 9}, 181 {User: "bob", Label: lgtmLabel, Time: 10}, 182 }) 183 } 184 185 func OldApprovedEvents() []*github.IssueEvent { 186 return github_test.Events([]github_test.LabelTime{ 187 {User: "bob", Label: approvedLabel, Time: 6}, 188 {User: "bob", Label: lgtmLabel, Time: 10}, 189 {User: "bob", Label: lgtmLabel, Time: 11}, 190 {User: "bob", Label: lgtmLabel, Time: 12}, 191 }) 192 } 193 194 // Commits returns a slice of github.RepositoryCommit of len==3 which 195 // happened at times 7, 8, 9 196 func Commits() []*github.RepositoryCommit { 197 return github_test.Commits(3, 7) 198 } 199 200 func SuccessStatus() *github.CombinedStatus { 201 return github_test.Status("mysha", []string{requiredReTestContext1, requiredReTestContext2, notRequiredReTestContext1, notRequiredReTestContext2}, nil, nil, nil) 202 } 203 204 func RetestFailStatus() *github.CombinedStatus { 205 return github_test.Status("mysha", []string{requiredReTestContext1, notRequiredReTestContext1, notRequiredReTestContext2}, []string{requiredReTestContext2}, nil, nil) 206 } 207 208 func NoRetestFailStatus() *github.CombinedStatus { 209 return github_test.Status("mysha", []string{requiredReTestContext1, requiredReTestContext2, notRequiredReTestContext1}, []string{notRequiredReTestContext2}, nil, nil) 210 } 211 212 func LastBuildNumber() int { 213 return 42 214 } 215 216 func SuccessGCS() utils.FinishedFile { 217 return utils.FinishedFile{ 218 Result: "SUCCESS", 219 Timestamp: uint64(time.Now().Unix()), 220 } 221 } 222 223 func FailGCS() utils.FinishedFile { 224 return utils.FinishedFile{ 225 Result: "FAILURE", 226 Timestamp: uint64(time.Now().Unix()), 227 } 228 } 229 230 func getJUnit(testsNo int, failuresNo int) []byte { 231 return []byte(fmt.Sprintf("%v\n<testsuite tests=\"%v\" failures=\"%v\" time=\"1234\">\n</testsuite>", 232 e2e.ExpectedXMLHeader, testsNo, failuresNo)) 233 } 234 235 func getTestSQ(startThreads bool, config *github_util.Config, server *httptest.Server) *SubmitQueue { 236 // TODO: Remove this line when we fix the plumbing regarding the fake/real e2e tester. 237 sharedmux.Admin = sharedmux.NewConcurrentMux(http.NewServeMux()) 238 sq := new(SubmitQueue) 239 sq.opts = options.New() 240 241 feats := &features.Features{ 242 Server: &features.ServerFeature{ 243 Enabled: false, 244 }, 245 } 246 247 sq.GateApproved = true 248 sq.GateCLA = true 249 sq.NonBlockingJobNames = someJobNames 250 sq.DoNotMergeMilestones = []string{doNotMergeMilestone} 251 sq.ClaYesLabels = []string{cncfClaYesLabel, claHumanLabel} 252 253 mungeopts.RequiredContexts.Merge = []string{notRequiredReTestContext1, notRequiredReTestContext2} 254 mungeopts.RequiredContexts.Retest = []string{requiredReTestContext1, requiredReTestContext2} 255 mungeopts.PRMaxWaitTime = 2 * time.Hour 256 257 sq.githubE2EQueue = map[int]*github_util.MungeObject{} 258 sq.githubE2EPollTime = time.Millisecond 259 260 sq.clock = utilclock.NewFakeClock(time.Time{}) 261 sq.lastMergeTime = sq.clock.Now() 262 sq.lastE2EStable = true 263 sq.prStatus = map[string]submitStatus{} 264 sq.lgtmTimeCache = mungerutil.NewLabelTimeCache(lgtmLabel) 265 266 sq.startTime = sq.clock.Now() 267 sq.healthHistory = make([]healthRecord, 0) 268 269 sq.e2e = &fake_e2e.FakeE2ETester{JobNames: sq.NonBlockingJobNames} 270 271 if startThreads { 272 sq.internalInitialize(config, feats, server.URL) 273 sq.EachLoop() 274 } 275 return sq 276 } 277 278 func TestQueueOrder(t *testing.T) { 279 timeBase := time.Now() 280 time2 := timeBase.Add(6 * time.Minute).Unix() 281 time3 := timeBase.Add(5 * time.Minute).Unix() 282 time4 := timeBase.Add(4 * time.Minute).Unix() 283 time5 := timeBase.Add(3 * time.Minute).Unix() 284 time6 := timeBase.Add(2 * time.Minute).Unix() 285 labelEvents := map[int][]github_test.LabelTime{ 286 2: {{User: "me", Label: lgtmLabel, Time: time2}}, 287 3: {{User: "me", Label: lgtmLabel, Time: time3}}, 288 4: {{User: "me", Label: lgtmLabel, Time: time4}}, 289 5: {{User: "me", Label: lgtmLabel, Time: time5}}, 290 6: {{User: "me", Label: lgtmLabel, Time: time6}}, 291 } 292 293 tests := []struct { 294 name string 295 issues []*github.Issue 296 issueToEvents map[int][]github_test.LabelTime 297 expected []int 298 }{ 299 { 300 name: "Just prNum", 301 issues: []*github.Issue{ 302 github_test.Issue(someUserName, 2, nil, true), 303 github_test.Issue(someUserName, 3, nil, true), 304 github_test.Issue(someUserName, 4, nil, true), 305 github_test.Issue(someUserName, 5, nil, true), 306 }, 307 issueToEvents: labelEvents, 308 expected: []int{5, 4, 3, 2}, 309 }, 310 { 311 name: "With a priority label", 312 issues: []*github.Issue{ 313 github_test.Issue(someUserName, 2, []string{multirebaseLabel}, true), 314 github_test.Issue(someUserName, 3, []string{multirebaseLabel}, true), 315 github_test.Issue(someUserName, 4, []string{criticalFixLabel}, true), 316 github_test.Issue(someUserName, 5, nil, true), 317 }, 318 issueToEvents: labelEvents, 319 expected: []int{4, 3, 2, 5}, 320 }, 321 { 322 name: "With two priority labels", 323 issues: []*github.Issue{ 324 github_test.Issue(someUserName, 2, []string{fixLabel, criticalFixLabel}, true), 325 github_test.Issue(someUserName, 3, []string{fixLabel}, true), 326 github_test.Issue(someUserName, 4, []string{criticalFixLabel}, true), 327 github_test.Issue(someUserName, 5, nil, true), 328 }, 329 issueToEvents: labelEvents, 330 expected: []int{4, 2, 3, 5}, 331 }, 332 { 333 name: "With unrelated labels", 334 issues: []*github.Issue{ 335 github_test.Issue(someUserName, 2, []string{fixLabel, criticalFixLabel}, true), 336 github_test.Issue(someUserName, 3, []string{fixLabel, "kind/design"}, true), 337 github_test.Issue(someUserName, 4, []string{criticalFixLabel}, true), 338 github_test.Issue(someUserName, 5, []string{lgtmLabel, "kind/new-api"}, true), 339 }, 340 issueToEvents: labelEvents, 341 expected: []int{4, 2, 3, 5}, 342 }, 343 { 344 name: "With invalid priority label", 345 issues: []*github.Issue{ 346 github_test.Issue(someUserName, 2, []string{fixLabel, criticalFixLabel}, true), 347 github_test.Issue(someUserName, 3, []string{fixLabel, "kind/design", "priority/high"}, true), 348 github_test.Issue(someUserName, 4, []string{criticalFixLabel, "priorty/bob"}, true), 349 github_test.Issue(someUserName, 5, nil, true), 350 }, 351 issueToEvents: labelEvents, 352 expected: []int{4, 2, 3, 5}, 353 }, 354 { 355 name: "Unlabeled counts as below", 356 issues: []*github.Issue{ 357 github_test.Issue(someUserName, 2, nil, true), 358 github_test.Issue(someUserName, 3, []string{blocksOthersLabel}, true), 359 github_test.Issue(someUserName, 4, []string{multirebaseLabel}, true), 360 github_test.Issue(someUserName, 5, nil, true), 361 }, 362 issueToEvents: labelEvents, 363 expected: []int{4, 3, 5, 2}, 364 }, 365 { 366 name: "retestNotRequiredLabel counts above everything except criticalFixLabel", 367 issues: []*github.Issue{ 368 github_test.Issue(someUserName, 2, nil, true), 369 github_test.Issue(someUserName, 3, []string{blocksOthersLabel}, true), 370 github_test.Issue(someUserName, 4, []string{fixLabel}, true), 371 github_test.Issue(someUserName, 5, []string{criticalFixLabel}, true), 372 github_test.Issue(someUserName, 6, []string{blocksOthersLabel, retestNotRequiredLabel}, true), 373 }, 374 issueToEvents: labelEvents, 375 expected: []int{5, 6, 4, 3, 2}, 376 }, 377 } 378 for testNum, test := range tests { 379 config := &github_util.Config{Org: "o", Project: "r"} 380 client, server, mux := github_test.InitServer(t, nil, nil, github_test.MultiIssueEvents(test.issueToEvents, "labeled"), nil, nil, nil, nil) 381 config.SetClient(client) 382 sq := getTestSQ(false, config, server) 383 for i := range test.issues { 384 issue := test.issues[i] 385 github_test.ServeIssue(t, mux, issue) 386 387 issueNum := *issue.Number 388 obj, err := config.GetObject(issueNum) 389 if err != nil { 390 t.Fatalf("%d:%q unable to get issue: %v", testNum, test.name, err) 391 } 392 sq.githubE2EQueue[issueNum] = obj 393 } 394 actual := sq.orderedE2EQueue() 395 if len(actual) != len(test.expected) { 396 t.Fatalf("%d:%q len(actual):%v != len(expected):%v", testNum, test.name, actual, test.expected) 397 } 398 for i, a := range actual { 399 e := test.expected[i] 400 if a != e { 401 t.Errorf("%d:%q a[%d]:%d != e[%d]:%d", testNum, test.name, i, a, i, e) 402 } 403 } 404 server.Close() 405 } 406 } 407 408 func TestValidateLGTMAfterPush(t *testing.T) { 409 tests := []struct { 410 issueEvents []*github.IssueEvent 411 commits []*github.RepositoryCommit 412 shouldPass bool 413 }{ 414 { 415 issueEvents: NewLGTMEvents(), // Label >= time.Unix(10) 416 commits: Commits(), // Modified at time.Unix(7), 8, and 9 417 shouldPass: true, 418 }, 419 { 420 issueEvents: OldLGTMEvents(), // Label <= time.Unix(8) 421 commits: Commits(), // Modified at time.Unix(7), 8, and 9 422 shouldPass: false, 423 }, 424 { 425 issueEvents: OverlappingLGTMEvents(), // Labeled at 8, 9, and 10 426 commits: Commits(), // Modified at time.Unix(7), 8, and 9 427 shouldPass: true, 428 }, 429 } 430 for testNum, test := range tests { 431 config := &github_util.Config{Org: "o", Project: "r"} 432 client, server, _ := github_test.InitServer(t, nil, nil, test.issueEvents, test.commits, nil, nil, nil) 433 config.SetClient(client) 434 435 obj := github_util.NewTestObject(config, BareIssue(), nil, nil, nil) 436 437 if _, ok := obj.GetCommits(); !ok { 438 t.Errorf("Unexpected error getting filled commits") 439 } 440 441 if _, ok := obj.GetEvents(); !ok { 442 t.Errorf("Unexpected error getting events commits") 443 } 444 445 lastModifiedTime, ok1 := obj.LastModifiedTime() 446 lgtmTime, ok2 := obj.LabelTime(lgtmLabel) 447 448 if !ok1 || !ok2 || lastModifiedTime == nil || lgtmTime == nil { 449 t.Errorf("unexpected lastModifiedTime or lgtmTime == nil") 450 } 451 452 ok := !lastModifiedTime.After(*lgtmTime) 453 454 if ok != test.shouldPass { 455 t.Errorf("%d: expected: %v, saw: %v", testNum, test.shouldPass, ok) 456 } 457 server.Close() 458 } 459 } 460 461 func setStatus(status *github.RepoStatus, success bool) { 462 if success { 463 status.State = stringPtr("success") 464 } else { 465 status.State = stringPtr("failure") 466 } 467 } 468 469 func addStatus(context string, success bool, ciStatus *github.CombinedStatus) { 470 status := github.RepoStatus{ 471 Context: stringPtr(context), 472 } 473 setStatus(&status, success) 474 ciStatus.Statuses = append(ciStatus.Statuses, status) 475 } 476 477 // fakeRunGithubE2ESuccess imitates jenkins running 478 func fakeRunGithubE2ESuccess(ciStatus *github.CombinedStatus, context1Pass, context2Pass bool) { 479 ciStatus.State = stringPtr("pending") 480 for id := range ciStatus.Statuses { 481 status := &ciStatus.Statuses[id] 482 if *status.Context == requiredReTestContext1 || *status.Context == requiredReTestContext2 { 483 status.State = stringPtr("pending") 484 } 485 } 486 // short sleep like the test is running 487 time.Sleep(500 * time.Millisecond) 488 if context1Pass && context2Pass { 489 ciStatus.State = stringPtr("success") 490 } 491 foundContext1 := false 492 foundContext2 := false 493 for id := range ciStatus.Statuses { 494 status := &ciStatus.Statuses[id] 495 if *status.Context == requiredReTestContext1 { 496 setStatus(status, context1Pass) 497 foundContext1 = true 498 } 499 if *status.Context == requiredReTestContext2 { 500 setStatus(status, context2Pass) 501 foundContext2 = true 502 } 503 } 504 if !foundContext1 { 505 addStatus(jenkinsE2EContext, context1Pass, ciStatus) 506 } 507 if !foundContext2 { 508 addStatus(jenkinsUnitContext, context2Pass, ciStatus) 509 } 510 } 511 512 func TestSubmitQueue(t *testing.T) { 513 runtime.GOMAXPROCS(runtime.NumCPU()) 514 515 // Since we testing, don't rateLimit api calls. Go hog wild 516 github_util.SetCombinedStatusLifetime(1) 517 518 tests := []struct { 519 name string // because when the fail, counting is hard 520 pr *github.PullRequest 521 issue *github.Issue 522 commits []*github.RepositoryCommit 523 events []*github.IssueEvent 524 additionalLabels []string 525 blockingLabels []string 526 ciStatus *github.CombinedStatus 527 lastBuildNumber int 528 gcsResult utils.FinishedFile 529 retest1Pass bool 530 retest2Pass bool 531 mergeAfterQueued bool 532 reason string 533 state string // what the github status context should be for the PR HEAD 534 535 emergencyMergeStop bool 536 isMerged bool 537 538 imHeadSHA string 539 imBaseSHA string 540 masterCommit *github.RepositoryCommit 541 retestsAvoided int // desired output 542 }{ 543 // Should pass because the entire thing was run and good (with cncf-cla: yes) 544 { 545 name: "Test1", 546 pr: ValidPR(), 547 issue: LGTMApprovedIssue(), 548 events: NewLGTMEvents(), 549 commits: Commits(), // Modified at time.Unix(7), 8, and 9 550 ciStatus: SuccessStatus(), 551 lastBuildNumber: LastBuildNumber(), 552 gcsResult: SuccessGCS(), 553 retest1Pass: true, 554 retest2Pass: true, 555 reason: merged, 556 state: "success", 557 isMerged: true, 558 }, 559 // Should pass because the entire thing was run and good (with cla: human-approved) 560 { 561 name: "Test1", 562 pr: ValidPR(), 563 issue: LGTMApprovedCLAHumanApprovedIssue(), 564 events: NewLGTMEvents(), 565 commits: Commits(), // Modified at time.Unix(7), 8, and 9 566 ciStatus: SuccessStatus(), 567 lastBuildNumber: LastBuildNumber(), 568 gcsResult: SuccessGCS(), 569 retest1Pass: true, 570 retest2Pass: true, 571 reason: merged, 572 state: "success", 573 isMerged: true, 574 }, 575 { 576 name: "Test1+NoLgtm", 577 pr: ValidPR(), 578 issue: OnlyApprovedIssue(), 579 events: NewLGTMEvents(), 580 commits: Commits(), // Modified at time.Unix(7), 8, and 9 581 ciStatus: SuccessStatus(), 582 lastBuildNumber: LastBuildNumber(), 583 gcsResult: SuccessGCS(), 584 retest1Pass: true, 585 retest2Pass: true, 586 reason: noLGTM, 587 state: "pending", 588 isMerged: false, 589 }, 590 // Entire thing was run and good, but emergency merge stop in progress 591 { 592 name: "Test1+emergencyStop", 593 pr: ValidPR(), 594 issue: LGTMApprovedIssue(), 595 events: NewLGTMEvents(), 596 commits: Commits(), // Modified at time.Unix(7), 8, and 9 597 ciStatus: SuccessStatus(), 598 lastBuildNumber: LastBuildNumber(), 599 gcsResult: SuccessGCS(), 600 retest1Pass: true, 601 retest2Pass: true, 602 emergencyMergeStop: true, 603 isMerged: false, 604 reason: e2eFailure, 605 state: "success", 606 }, 607 // Should pass without running tests because we had a previous run. 608 // TODO: Add a proper test to make sure we don't shuffle queue when we can just merge a PR 609 { 610 name: "Test1+prevsuccess", 611 pr: ValidPR(), 612 issue: LGTMApprovedIssue(), 613 events: NewLGTMEvents(), 614 commits: Commits(), // Modified at time.Unix(7), 8, and 9 615 ciStatus: SuccessStatus(), 616 lastBuildNumber: LastBuildNumber(), 617 gcsResult: SuccessGCS(), 618 retest1Pass: true, 619 retest2Pass: true, 620 reason: merged, 621 state: "success", 622 isMerged: true, 623 retestsAvoided: 1, 624 imHeadSHA: "mysha", // Set by ValidPR 625 imBaseSHA: "mastersha", 626 masterCommit: MasterCommit(), 627 }, 628 // Should list as 'merged' but the merge should happen before it gets e2e tested 629 // and we should bail early instead of waiting for a test that will never come. 630 { 631 name: "Test2", 632 pr: ValidPR(), 633 issue: LGTMApprovedIssue(), 634 events: NewLGTMEvents(), 635 commits: Commits(), 636 ciStatus: SuccessStatus(), 637 lastBuildNumber: LastBuildNumber(), 638 gcsResult: SuccessGCS(), 639 // The test should never run, but if it does, make sure it fails 640 mergeAfterQueued: true, 641 reason: mergedByHand, 642 state: "success", 643 }, 644 // Should merge even though retest1Pass would have failed before of `retestNotRequiredLabel` 645 { 646 name: "merge because of retestNotRequired", 647 pr: ValidPR(), 648 issue: NoRetestIssue(), 649 events: NewLGTMEvents(), 650 commits: Commits(), // Modified at time.Unix(7), 8, and 9 651 ciStatus: SuccessStatus(), 652 lastBuildNumber: LastBuildNumber(), 653 gcsResult: SuccessGCS(), 654 retest1Pass: false, 655 retest2Pass: false, 656 reason: mergedSkippedRetest, 657 state: "success", 658 isMerged: true, 659 }, 660 // Fail because PR can't automatically merge 661 { 662 name: "Test5", 663 pr: UnMergeablePR(), 664 issue: LGTMApprovedIssue(), 665 reason: unmergeable, 666 state: "pending", 667 // To avoid false errors in logs 668 lastBuildNumber: LastBuildNumber(), 669 gcsResult: SuccessGCS(), 670 }, 671 // Fail because we don't know if PR can automatically merge 672 { 673 name: "Test6", 674 pr: UndeterminedMergeablePR(), 675 issue: LGTMApprovedIssue(), 676 reason: undeterminedMergability, 677 state: "pending", 678 // To avoid false errors in logs 679 lastBuildNumber: LastBuildNumber(), 680 gcsResult: SuccessGCS(), 681 }, 682 // Fail because the cncfClaYesLabel or claHumanLabel label was not applied 683 { 684 name: "Test7", 685 pr: ValidPR(), 686 issue: NoCLAIssue(), 687 reason: fmt.Sprintf("%s %q", noCLA, []string{cncfClaYesLabel, claHumanLabel}), 688 state: "pending", 689 // To avoid false errors in logs 690 lastBuildNumber: LastBuildNumber(), 691 gcsResult: SuccessGCS(), 692 }, 693 // Fail because github CI tests have failed (or at least are not success) 694 { 695 name: "Test8", 696 pr: ValidPR(), 697 issue: LGTMApprovedIssue(), 698 reason: fmt.Sprintf(ciFailureFmt, notRequiredReTestContext1), 699 state: "pending", 700 // To avoid false errors in logs 701 lastBuildNumber: LastBuildNumber(), 702 gcsResult: SuccessGCS(), 703 }, 704 // Fail because missing LGTM label 705 { 706 name: "Test10", 707 pr: ValidPR(), 708 issue: NoLGTMIssue(), 709 ciStatus: SuccessStatus(), 710 reason: noLGTM, 711 state: "pending", 712 // To avoid false errors in logs 713 lastBuildNumber: LastBuildNumber(), 714 gcsResult: SuccessGCS(), 715 }, 716 // Fail because we can't tell if LGTM was added before the last change 717 { 718 name: "Test11", 719 pr: ValidPR(), 720 issue: LGTMApprovedIssue(), 721 ciStatus: SuccessStatus(), 722 reason: unknown, 723 state: "failure", 724 // To avoid false errors in logs 725 lastBuildNumber: LastBuildNumber(), 726 gcsResult: SuccessGCS(), 727 }, 728 // Fail because LGTM was added before the last change 729 { 730 name: "Test12", 731 pr: ValidPR(), 732 issue: LGTMApprovedIssue(), 733 ciStatus: SuccessStatus(), 734 events: OldLGTMEvents(), 735 commits: Commits(), // Modified at time.Unix(7), 8, and 9 736 reason: lgtmEarly, 737 state: "pending", 738 }, 739 // Fail because jenkins instances are failing (whole submit queue blocks) 740 { 741 name: "Test13", 742 pr: ValidPR(), 743 issue: LGTMApprovedIssue(), 744 ciStatus: SuccessStatus(), 745 events: NewLGTMEvents(), 746 commits: Commits(), // Modified at time.Unix(7), 8, and 9 747 lastBuildNumber: LastBuildNumber(), 748 gcsResult: FailGCS(), 749 reason: ghE2EQueued, 750 state: "success", 751 }, 752 // Fail because the second run of github e2e tests failed 753 { 754 name: "Test14", 755 pr: ValidPR(), 756 issue: LGTMApprovedIssue(), 757 ciStatus: SuccessStatus(), 758 events: NewLGTMEvents(), 759 commits: Commits(), 760 lastBuildNumber: LastBuildNumber(), 761 gcsResult: SuccessGCS(), 762 reason: ghE2EFailed, 763 state: "pending", 764 }, 765 // When we check the reason it may be queued or it may already have failed. 766 { 767 name: "Test15", 768 pr: ValidPR(), 769 issue: LGTMApprovedIssue(), 770 ciStatus: SuccessStatus(), 771 events: NewLGTMEvents(), 772 commits: Commits(), // Modified at time.Unix(7), 8, and 9 773 lastBuildNumber: LastBuildNumber(), 774 gcsResult: SuccessGCS(), 775 reason: ghE2EQueued, 776 // The state is unpredictable. When it goes on the queue it is success. 777 // When it fails the build it is pending. So state depends on how far along 778 // this were when we checked. Thus just don't check it... 779 state: "", 780 }, 781 // Fail because the second run of github e2e tests failed 782 { 783 name: "Test16", 784 pr: ValidPR(), 785 issue: LGTMApprovedIssue(), 786 ciStatus: SuccessStatus(), 787 events: NewLGTMEvents(), 788 commits: Commits(), // Modified at time.Unix(7), 8, and 9 789 lastBuildNumber: LastBuildNumber(), 790 gcsResult: SuccessGCS(), 791 reason: ghE2EFailed, 792 state: "pending", 793 }, 794 { 795 name: "Fail because E2E pass, but unit test fail", 796 pr: ValidPR(), 797 issue: LGTMApprovedIssue(), 798 events: NewLGTMEvents(), 799 commits: Commits(), // Modified at time.Unix(7), 8, and 9 800 ciStatus: SuccessStatus(), 801 lastBuildNumber: LastBuildNumber(), 802 gcsResult: SuccessGCS(), 803 retest1Pass: true, 804 retest2Pass: false, 805 reason: ghE2EFailed, 806 state: "pending", 807 }, 808 { 809 name: "Fail because E2E fail, but unit test pass", 810 pr: ValidPR(), 811 issue: LGTMApprovedIssue(), 812 events: NewLGTMEvents(), 813 commits: Commits(), // Modified at time.Unix(7), 8, and 9 814 ciStatus: SuccessStatus(), 815 lastBuildNumber: LastBuildNumber(), 816 gcsResult: SuccessGCS(), 817 retest1Pass: false, 818 retest2Pass: true, 819 reason: ghE2EFailed, 820 state: "pending", 821 }, 822 { 823 name: "Fail because missing release note label is present", 824 pr: ValidPR(), 825 issue: MissingReleaseNoteIssue(), 826 events: NewLGTMEvents(), 827 commits: Commits(), // Modified at time.Unix(7), 8, and 9 828 ciStatus: SuccessStatus(), 829 lastBuildNumber: LastBuildNumber(), 830 gcsResult: SuccessGCS(), 831 retest1Pass: true, 832 retest2Pass: true, 833 reason: noMergeMessage(releaseNoteLabelNeeded), 834 state: "pending", 835 }, 836 { 837 name: "Fail because hold label is present", 838 pr: ValidPR(), 839 issue: HoldLabelIssue(), 840 events: NewLGTMEvents(), 841 commits: Commits(), // Modified at time.Unix(7), 8, and 9 842 ciStatus: SuccessStatus(), 843 lastBuildNumber: LastBuildNumber(), 844 gcsResult: SuccessGCS(), 845 retest1Pass: true, 846 retest2Pass: true, 847 reason: noMergeMessage(holdLabel), 848 state: "pending", 849 }, 850 { 851 name: "Fail because kind/blocker label is required but missing", 852 pr: ValidPR(), 853 issue: LGTMApprovedIssue(), 854 additionalLabels: []string{"kind/blocker"}, 855 events: NewLGTMEvents(), 856 commits: Commits(), // Modified at time.Unix(7), 8, and 9 857 ciStatus: SuccessStatus(), 858 lastBuildNumber: LastBuildNumber(), 859 gcsResult: SuccessGCS(), 860 retest1Pass: true, 861 retest2Pass: true, 862 reason: noAdditionalLabelMessage("kind/blocker"), 863 state: "pending", 864 }, 865 { 866 name: "Merge kind/blocker PR", 867 pr: ValidPR(), 868 issue: AdditionalLabelIssue("kind/blocker"), 869 additionalLabels: []string{"kind/blocker"}, 870 events: NewLGTMEvents(), 871 commits: Commits(), // Modified at time.Unix(7), 8, and 9 872 ciStatus: SuccessStatus(), 873 lastBuildNumber: LastBuildNumber(), 874 gcsResult: SuccessGCS(), 875 retest1Pass: true, 876 retest2Pass: true, 877 reason: merged, 878 state: "success", 879 isMerged: true, 880 }, 881 { 882 name: "Fail because vendor-update label is required to be missing but exists", 883 pr: ValidPR(), 884 issue: AdditionalLabelIssue("vendor-update"), 885 blockingLabels: []string{"vendor-update"}, 886 events: NewLGTMEvents(), 887 commits: Commits(), // Modified at time.Unix(7), 8, and 9 888 ciStatus: SuccessStatus(), 889 lastBuildNumber: LastBuildNumber(), 890 gcsResult: SuccessGCS(), 891 retest1Pass: true, 892 retest2Pass: true, 893 reason: noMergeMessage("vendor-update"), 894 state: "pending", 895 }, 896 { 897 name: "Fail because do not merge label is present", 898 pr: ValidPR(), 899 issue: DoNotMergeIssue(), 900 events: NewLGTMEvents(), 901 commits: Commits(), // Modified at time.Unix(7), 8, and 9 902 ciStatus: SuccessStatus(), 903 lastBuildNumber: LastBuildNumber(), 904 gcsResult: SuccessGCS(), 905 retest1Pass: true, 906 retest2Pass: true, 907 reason: noMergeMessage(doNotMergeLabel), 908 state: "pending", 909 }, 910 { 911 name: "Fail because cherrypick unapproved label is present", 912 pr: ValidPR(), 913 issue: CherrypickUnapprovedIssue(), 914 events: NewLGTMEvents(), 915 commits: Commits(), // Modified at time.Unix(7), 8, and 9 916 ciStatus: SuccessStatus(), 917 lastBuildNumber: LastBuildNumber(), 918 gcsResult: SuccessGCS(), 919 retest1Pass: true, 920 retest2Pass: true, 921 reason: noMergeMessage(cherrypickUnapprovedLabel), 922 state: "pending", 923 }, 924 { 925 name: "Fail because blocked paths label is present", 926 pr: ValidPR(), 927 issue: BlockedPathsIssue(), 928 events: NewLGTMEvents(), 929 commits: Commits(), // Modified at time.Unix(7), 8, and 9 930 ciStatus: SuccessStatus(), 931 lastBuildNumber: LastBuildNumber(), 932 gcsResult: SuccessGCS(), 933 retest1Pass: true, 934 retest2Pass: true, 935 reason: noMergeMessage(blockedPathsLabel), 936 state: "pending", 937 }, 938 { 939 name: "Fail because work-in-progress label is present", 940 pr: ValidPR(), 941 issue: WorkInProgressIssue(), 942 events: NewLGTMEvents(), 943 commits: Commits(), // Modified at time.Unix(7), 8, and 9 944 ciStatus: SuccessStatus(), 945 lastBuildNumber: LastBuildNumber(), 946 gcsResult: SuccessGCS(), 947 retest1Pass: true, 948 retest2Pass: true, 949 reason: noMergeMessage(wipLabel), 950 state: "pending", 951 }, 952 // Should fail because the 'do-not-merge-milestone' is set. 953 { 954 name: "Do Not Merge Milestone Set", 955 pr: ValidPR(), 956 issue: DoNotMergeMilestoneIssue(), 957 events: NewLGTMEvents(), 958 commits: Commits(), // Modified at time.Unix(7), 8, and 9 959 ciStatus: SuccessStatus(), 960 lastBuildNumber: LastBuildNumber(), 961 gcsResult: SuccessGCS(), 962 retest1Pass: true, 963 retest2Pass: true, 964 reason: unmergeableMilestone, 965 state: "pending", 966 }, 967 { 968 name: "Fail because retest status fail", 969 pr: ValidPR(), 970 issue: LGTMApprovedIssue(), 971 events: NewLGTMEvents(), 972 commits: Commits(), // Modified at time.Unix(7), 8, and 9 973 ciStatus: RetestFailStatus(), 974 lastBuildNumber: LastBuildNumber(), 975 gcsResult: SuccessGCS(), 976 retest1Pass: true, 977 retest2Pass: true, 978 reason: fmt.Sprintf(ciFailureFmt, requiredReTestContext2), 979 state: "pending", 980 }, 981 { 982 name: "Fail because noretest status fail", 983 pr: ValidPR(), 984 issue: LGTMApprovedIssue(), 985 events: NewLGTMEvents(), 986 commits: Commits(), // Modified at time.Unix(7), 8, and 9 987 ciStatus: NoRetestFailStatus(), 988 lastBuildNumber: LastBuildNumber(), 989 gcsResult: SuccessGCS(), 990 retest1Pass: true, 991 retest2Pass: true, 992 reason: fmt.Sprintf(ciFailureFmt, notRequiredReTestContext2), 993 state: "pending", 994 }, 995 { 996 name: "Approval Can Happen Before Code Changes", 997 pr: ValidPR(), 998 issue: LGTMApprovedIssue(), 999 events: OldApprovedEvents(), 1000 commits: Commits(), // Modified at time.Unix(7), 8, and 9 1001 ciStatus: SuccessStatus(), 1002 lastBuildNumber: LastBuildNumber(), 1003 gcsResult: SuccessGCS(), 1004 retest1Pass: true, 1005 retest2Pass: true, 1006 reason: merged, 1007 state: "success", 1008 isMerged: true, 1009 retestsAvoided: 1, 1010 imHeadSHA: "mysha", // Set by ValidPR 1011 imBaseSHA: "mastersha", 1012 masterCommit: MasterCommit(), 1013 }, 1014 { 1015 name: "criticalFixLabel should merge even though jenkins GCS fail", 1016 pr: ValidPR(), 1017 issue: CriticalFixLGTMApprovedIssue(), 1018 events: NewLGTMEvents(), 1019 commits: Commits(), // Modified at time.Unix(7), 8, and 9 1020 ciStatus: SuccessStatus(), 1021 lastBuildNumber: LastBuildNumber(), 1022 gcsResult: FailGCS(), 1023 retest1Pass: true, 1024 retest2Pass: true, 1025 reason: merged, 1026 state: "success", 1027 isMerged: true, 1028 }, 1029 { 1030 name: "criticalFixLabel but should fail if e2e's fail", 1031 pr: ValidPR(), 1032 issue: CriticalFixLGTMApprovedIssue(), 1033 events: NewLGTMEvents(), 1034 commits: Commits(), // Modified at time.Unix(7), 8, and 9 1035 ciStatus: SuccessStatus(), 1036 lastBuildNumber: LastBuildNumber(), 1037 gcsResult: FailGCS(), 1038 retest1Pass: false, 1039 retest2Pass: true, 1040 reason: ghE2EFailed, 1041 state: "pending", 1042 }, 1043 } 1044 for testNum := range tests { 1045 test := &tests[testNum] 1046 t.Logf("---------Starting test %v (%v)---------------------", testNum, test.name) 1047 issueNum := testNum + 1 1048 issueNumStr := strconv.Itoa(issueNum) 1049 1050 test.issue.Number = &issueNum 1051 client, server, mux := github_test.InitServer(t, test.issue, test.pr, test.events, test.commits, test.ciStatus, test.masterCommit, nil) 1052 1053 config := &github_util.Config{Org: "o", Project: "r"} 1054 config.SetClient(client) 1055 // Don't wait so long for retries (pending, mergeability) 1056 config.BaseWaitTime = time.Millisecond 1057 1058 stateSet := "" 1059 wasMerged := false 1060 1061 for _, job := range someJobNames { 1062 numTestChecks := 0 1063 var testChecksLock sync.Mutex 1064 path := fmt.Sprintf("/bucket/logs/%s/latest-build.txt", job) 1065 mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 1066 if r.Method != "GET" { 1067 t.Errorf("Unexpected method: %s", r.Method) 1068 } 1069 w.WriteHeader(http.StatusOK) 1070 w.Write([]byte(strconv.Itoa(test.lastBuildNumber))) 1071 1072 // There is no good spot for this, but this gets called 1073 // before we queue the PR. So mark the PR as "merged". 1074 // When the sq initializes, it will check the Jenkins status, 1075 // so we don't want to modify the PR there. Instead we need 1076 // to wait until the second time we check Jenkins, which happens 1077 // we did the IsMerged() check. 1078 testChecksLock.Lock() 1079 defer testChecksLock.Unlock() 1080 numTestChecks = numTestChecks + 1 1081 if numTestChecks == 2 && test.mergeAfterQueued { 1082 test.pr.Merged = boolPtr(true) 1083 test.pr.Mergeable = nil 1084 } 1085 }) 1086 path = fmt.Sprintf("/bucket/logs/%s/%v/finished.json", job, test.lastBuildNumber) 1087 mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 1088 if r.Method != "GET" { 1089 t.Errorf("Unexpected method: %s", r.Method) 1090 } 1091 w.WriteHeader(http.StatusOK) 1092 data, err := json.Marshal(test.gcsResult) 1093 if err != nil { 1094 t.Errorf("Unexpected error: %v", err) 1095 } 1096 w.Write(data) 1097 }) 1098 } 1099 1100 path := fmt.Sprintf("/repos/o/r/issues/%d/comments", issueNum) 1101 mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 1102 if r.Method == "POST" { 1103 c := new(github.IssueComment) 1104 json.NewDecoder(r.Body).Decode(c) 1105 msg := *c.Body 1106 if strings.HasPrefix(msg, "/test all") { 1107 go fakeRunGithubE2ESuccess(test.ciStatus, test.retest1Pass, test.retest2Pass) 1108 } 1109 w.WriteHeader(http.StatusOK) 1110 data, err := json.Marshal(github.IssueComment{}) 1111 if err != nil { 1112 t.Errorf("Unexpected error: %v", err) 1113 } 1114 w.Write(data) 1115 return 1116 } 1117 if r.Method == "GET" { 1118 w.WriteHeader(http.StatusOK) 1119 data, err := json.Marshal([]github.IssueComment{}) 1120 if err != nil { 1121 t.Errorf("Unexpected error: %v", err) 1122 } 1123 w.Write(data) 1124 return 1125 } 1126 t.Errorf("Unexpected method: %s", r.Method) 1127 }) 1128 path = fmt.Sprintf("/repos/o/r/pulls/%d/merge", issueNum) 1129 mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 1130 if r.Method != "PUT" { 1131 t.Errorf("Unexpected method: %s", r.Method) 1132 } 1133 w.WriteHeader(http.StatusOK) 1134 data, err := json.Marshal(github.PullRequestMergeResult{}) 1135 if err != nil { 1136 t.Errorf("Unexpected error: %v", err) 1137 } 1138 w.Write(data) 1139 test.pr.Merged = boolPtr(true) 1140 wasMerged = true 1141 }) 1142 path = fmt.Sprintf("/repos/o/r/statuses/%s", *test.pr.Head.SHA) 1143 mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 1144 if r.Method != "POST" { 1145 t.Errorf("Unexpected method: %s", r.Method) 1146 } 1147 decoder := json.NewDecoder(r.Body) 1148 var status github.RepoStatus 1149 err := decoder.Decode(&status) 1150 if err != nil { 1151 t.Errorf("Unable to decode status: %v", err) 1152 } 1153 1154 stateSet = *status.State 1155 1156 data, err := json.Marshal(status) 1157 if err != nil { 1158 t.Errorf("Unexpected error: %v", err) 1159 } 1160 w.WriteHeader(http.StatusOK) 1161 w.Write(data) 1162 }) 1163 1164 sq := getTestSQ(true, config, server) 1165 sq.setEmergencyMergeStop(test.emergencyMergeStop) 1166 sq.AdditionalRequiredLabels = test.additionalLabels 1167 sq.BlockingLabels = test.blockingLabels 1168 1169 obj := github_util.NewTestObject(config, test.issue, test.pr, test.commits, test.events) 1170 if test.imBaseSHA != "" && test.imHeadSHA != "" { 1171 sq.interruptedObj = &submitQueueInterruptedObject{obj, test.imHeadSHA, test.imBaseSHA} 1172 } 1173 sq.Munge(obj) 1174 done := make(chan bool, 1) 1175 go func(done chan bool) { 1176 for { 1177 defer func() { 1178 if r := recover(); r != nil { 1179 t.Errorf("%d:%q panic'd likely writing to 'done' channel", testNum, test.name) 1180 } 1181 }() 1182 1183 reason := func() string { 1184 sq.Mutex.Lock() 1185 defer sq.Mutex.Unlock() 1186 return sq.prStatus[issueNumStr].Reason 1187 } 1188 1189 if reason() == test.reason { 1190 done <- true 1191 return 1192 } 1193 found := false 1194 sq.Lock() 1195 for _, status := range sq.statusHistory { 1196 if status.Reason == test.reason { 1197 found = true 1198 break 1199 } 1200 } 1201 sq.Unlock() 1202 if found { 1203 done <- true 1204 return 1205 } 1206 time.Sleep(1 * time.Millisecond) 1207 } 1208 }(done) 1209 select { 1210 case <-done: 1211 case <-time.After(2 * time.Second): 1212 t.Errorf("%d:%q timed out waiting expected reason=%q but got prStatus:%q history:%v", testNum, test.name, test.reason, sq.prStatus[issueNumStr].Reason, sq.statusHistory) 1213 } 1214 close(done) 1215 server.Close() 1216 1217 if test.state != "" && test.state != stateSet { 1218 t.Errorf("%d:%q state set to %q but expected %q", testNum, test.name, stateSet, test.state) 1219 } 1220 if test.isMerged != wasMerged { 1221 t.Errorf("%d:%q PR merged = %v but wanted %v", testNum, test.name, wasMerged, test.isMerged) 1222 } 1223 if e, a := test.retestsAvoided, int(sq.retestsAvoided); e != a { 1224 t.Errorf("%d:%q expected %v tests avoided but got %v", testNum, test.name, e, a) 1225 } 1226 } 1227 } 1228 1229 func TestCalcMergeRate(t *testing.T) { 1230 runtime.GOMAXPROCS(runtime.NumCPU()) 1231 1232 tests := []struct { 1233 name string // because when it fails, counting is hard 1234 preRate float64 1235 interval time.Duration 1236 expected func(float64) bool 1237 }{ 1238 { 1239 name: "0One", 1240 preRate: 0, 1241 interval: time.Duration(time.Hour), 1242 expected: func(rate float64) bool { 1243 return rate > 10 && rate < 11 1244 }, 1245 }, 1246 { 1247 name: "24One", 1248 preRate: 24, 1249 interval: time.Duration(time.Hour), 1250 expected: func(rate float64) bool { 1251 return rate == float64(24) 1252 }, 1253 }, 1254 { 1255 name: "24Two", 1256 preRate: 24, 1257 interval: time.Duration(2 * time.Hour), 1258 expected: func(rate float64) bool { 1259 return rate > 17 && rate < 18 1260 }, 1261 }, 1262 { 1263 name: "24HalfHour", 1264 preRate: 24, 1265 interval: time.Duration(time.Hour) / 2, 1266 expected: func(rate float64) bool { 1267 return rate > 31 && rate < 32 1268 }, 1269 }, 1270 { 1271 name: "24Three", 1272 preRate: 24, 1273 interval: time.Duration(3 * time.Hour), 1274 expected: func(rate float64) bool { 1275 return rate > 14 && rate < 15 1276 }, 1277 }, 1278 { 1279 name: "24Then24", 1280 preRate: 24, 1281 interval: time.Duration(24 * time.Hour), 1282 expected: func(rate float64) bool { 1283 return rate > 2 && rate < 3 1284 }, 1285 }, 1286 { 1287 name: "24fast", 1288 preRate: 24, 1289 interval: time.Duration(4 * time.Minute), 1290 expected: func(rate float64) bool { 1291 // Should be no change 1292 return rate == 24 1293 }, 1294 }, 1295 } 1296 for testNum, test := range tests { 1297 sq := getTestSQ(false, nil, nil) 1298 clock := sq.clock.(*utilclock.FakeClock) 1299 sq.mergeRate = test.preRate 1300 clock.Step(test.interval) 1301 sq.updateMergeRate() 1302 if !test.expected(sq.mergeRate) { 1303 t.Errorf("%d:%s: expected() failed: rate:%v", testNum, test.name, sq.mergeRate) 1304 } 1305 } 1306 } 1307 1308 func TestCalcMergeRateWithTail(t *testing.T) { 1309 runtime.GOMAXPROCS(runtime.NumCPU()) 1310 1311 tests := []struct { 1312 name string // because when it fails, counting is hard 1313 preRate float64 1314 interval time.Duration 1315 expected func(float64) bool 1316 }{ 1317 { 1318 name: "ZeroPlusZero", 1319 preRate: 0, 1320 interval: time.Duration(0), 1321 expected: func(rate float64) bool { 1322 return rate == float64(0) 1323 }, 1324 }, 1325 { 1326 name: "0OneHour", 1327 preRate: 0, 1328 interval: time.Duration(time.Hour), 1329 expected: func(rate float64) bool { 1330 return rate == 0 1331 }, 1332 }, 1333 { 1334 name: "TinyOneHour", 1335 preRate: .001, 1336 interval: time.Duration(time.Hour), 1337 expected: func(rate float64) bool { 1338 return rate == .001 1339 }, 1340 }, 1341 { 1342 name: "TwentyFourPlusHalfHour", 1343 preRate: 24, 1344 interval: time.Duration(time.Hour) / 2, 1345 expected: func(rate float64) bool { 1346 return rate == 24 1347 }, 1348 }, 1349 { 1350 name: "TwentyFourPlusOneHour", 1351 preRate: 24, 1352 interval: time.Duration(time.Hour), 1353 expected: func(rate float64) bool { 1354 return rate == 24 1355 }, 1356 }, 1357 { 1358 name: "TwentyFourPlusTwoHour", 1359 preRate: 24, 1360 interval: time.Duration(2 * time.Hour), 1361 expected: func(rate float64) bool { 1362 return rate > 17 && rate < 18 1363 }, 1364 }, 1365 { 1366 name: "TwentyFourPlusFourHour", 1367 preRate: 24, 1368 interval: time.Duration(4 * time.Hour), 1369 expected: func(rate float64) bool { 1370 return rate > 12 && rate < 13 1371 }, 1372 }, 1373 { 1374 name: "TwentyFourPlusTwentyFourHour", 1375 preRate: 24, 1376 interval: time.Duration(24 * time.Hour), 1377 expected: func(rate float64) bool { 1378 return rate > 2 && rate < 3 1379 }, 1380 }, 1381 { 1382 name: "TwentyFourPlusTiny", 1383 preRate: 24, 1384 interval: time.Duration(time.Nanosecond), 1385 expected: func(rate float64) bool { 1386 return rate == 24 1387 }, 1388 }, 1389 { 1390 name: "TwentyFourPlusHuge", 1391 preRate: 24, 1392 interval: time.Duration(1024 * time.Hour), 1393 expected: func(rate float64) bool { 1394 return rate > 0 && rate < 1 1395 }, 1396 }, 1397 } 1398 for testNum, test := range tests { 1399 sq := getTestSQ(false, nil, nil) 1400 sq.mergeRate = test.preRate 1401 clock := sq.clock.(*utilclock.FakeClock) 1402 clock.Step(test.interval) 1403 rate := sq.calcMergeRateWithTail() 1404 if !test.expected(rate) { 1405 t.Errorf("%d:%s: %v", testNum, test.name, rate) 1406 } 1407 } 1408 } 1409 1410 func TestHealth(t *testing.T) { 1411 sq := getTestSQ(false, nil, nil) 1412 sq.updateHealth() 1413 sq.updateHealth() 1414 if len(sq.healthHistory) != 2 { 1415 t.Errorf("Wrong length healthHistory after calling updateHealth: %v", sq.healthHistory) 1416 } 1417 if sq.health.TotalLoops != 2 || sq.health.NumStable != 2 || len(sq.health.NumStablePerJob) != 2 { 1418 t.Errorf("Wrong number of stable loops after calling updateHealth: %v", sq.health) 1419 } 1420 for _, stable := range sq.health.NumStablePerJob { 1421 if stable != 2 { 1422 t.Errorf("Wrong number of stable loops for a job: %v", sq.health.NumStablePerJob) 1423 } 1424 } 1425 sq.healthHistory[0].Time = time.Now().AddDate(0, 0, -3) 1426 sq.healthHistory[1].Time = time.Now().AddDate(0, 0, -2) 1427 sq.updateHealth() 1428 if len(sq.healthHistory) != 1 { 1429 t.Errorf("updateHealth didn't truncate old entries: %v", sq.healthHistory) 1430 } 1431 } 1432 1433 func TestHealthSVG(t *testing.T) { 1434 sq := getTestSQ(false, nil, nil) 1435 e2e := sq.e2e.(*fake_e2e.FakeE2ETester) 1436 1437 for _, state := range []struct { 1438 mergePossible bool 1439 expected string 1440 notStable []string 1441 }{ 1442 {true, "running", nil}, 1443 {false, "blocked</text>", nil}, 1444 {false, "blocked by kubemark-500", []string{"kubernetes-kubemark-500"}}, 1445 {false, "blocked by a, b, c, ...", []string{"a", "b", "c", "d"}}, 1446 } { 1447 sq.health.MergePossibleNow = state.mergePossible 1448 e2e.NotStableJobNames = state.notStable 1449 res := string(sq.getHealthSVG()) 1450 if !strings.Contains(res, state.expected) { 1451 t.Errorf("SVG doesn't contain `%s`: %v", state.expected, res) 1452 } 1453 } 1454 }