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  }