Example #1
0
func TestPRGetFixesList(t *testing.T) {
	tests := []struct {
		issue    *github.Issue
		body     string
		expected []int
	}{
		{
			issue: github_test.Issue("", 1, []string{"label1"}, false),
			body: `bla resolve
this pr closes #45545 and also fixes #679
bla, some more bla with close here and there.
some suggest that it resolved #5643`,
			expected: []int{45545, 679, 5643},
		},
		{
			issue: github_test.Issue("", 2, []string{"label1"}, false),
			body: `bla resolve 345
some suggest that it also closes #892`,
			expected: []int{892},
		},
		{
			issue: github_test.Issue("", 3, []string{"label1"}, false),
			body: `bla resolve
this pr closes and fixes nothing`,
			expected: nil,
		},
		{
			issue: github_test.Issue("", 4, []string{"label1"}, false),
			body: `bla bla
this pr Fixes #23 and FIXES #45 but not fixxx #99`,
			expected: []int{23, 45},
		},
	}
	for testNum, test := range tests {
		client, server, _ := github_test.InitServer(t, test.issue, nil, nil, nil, nil, nil, nil)
		config := &Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		obj, err := config.GetObject(*test.issue.Number)
		if err != nil {
			t.Fatalf("%d: unable to get issue: %v", testNum, *test.issue.Number)
		}
		obj.Issue.Body = &test.body
		fixes := obj.GetPRFixesList()
		if len(test.expected) != len(fixes) {
			t.Errorf("%d: len(fixes) not equal, expected: %v but got: %v", testNum, test.expected, fixes)
			return
		}
		for i, n := range test.expected {
			if n != fixes[i] {
				t.Errorf("%d: expected fixes: %v but got fixes: %v", testNum, test.expected, fixes)
			}
		}
		server.Close()
	}
}
Example #2
0
func TestValidateLGTMAfterPush(t *testing.T) {
	tests := []struct {
		issueEvents []github.IssueEvent
		commits     []github.RepositoryCommit
		shouldPass  bool
	}{
		{
			issueEvents: NewLGTMEvents(), // Label >= time.Unix(10)
			commits:     Commits(),       // Modified at time.Unix(7), 8, and 9
			shouldPass:  true,
		},
		{
			issueEvents: OldLGTMEvents(), // Label <= time.Unix(8)
			commits:     Commits(),       // Modified at time.Unix(7), 8, and 9
			shouldPass:  false,
		},
		{
			issueEvents: OverlappingLGTMEvents(), // Labeled at 8, 9, and 10
			commits:     Commits(),               // Modified at time.Unix(7), 8, and 9
			shouldPass:  true,
		},
	}
	for testNum, test := range tests {
		config := &github_util.Config{}
		client, server, _ := github_test.InitServer(t, nil, nil, test.issueEvents, test.commits, nil)
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		obj := github_util.TestObject(config, BareIssue(), nil, nil, nil)

		if _, err := obj.GetCommits(); err != nil {
			t.Errorf("Unexpected error getting filled commits: %v", err)
		}

		if _, err := obj.GetEvents(); err != nil {
			t.Errorf("Unexpected error getting events commits: %v", err)
		}

		lastModifiedTime := obj.LastModifiedTime()
		lgtmTime := obj.LabelTime("lgtm")

		if lastModifiedTime == nil || lgtmTime == nil {
			t.Errorf("unexpected lastModifiedTime or lgtmTime == nil")
		}

		ok := !lastModifiedTime.After(*lgtmTime)

		if ok != test.shouldPass {
			t.Errorf("%d: expected: %v, saw: %v", testNum, test.shouldPass, ok)
		}
		server.Close()
	}
}
Example #3
0
func TestRemoveLabel(t *testing.T) {
	tests := []struct {
		issue    *github.Issue
		remove   string
		expected []string
	}{
		{
			issue:    github_test.Issue("", 1, []string{"label1"}, false),
			remove:   "label1",
			expected: []string{},
		},
		{
			issue:    github_test.Issue("", 1, []string{"label2", "label1"}, false),
			remove:   "label1",
			expected: []string{"label2"},
		},
		{
			issue:    github_test.Issue("", 1, []string{"label2"}, false),
			remove:   "label1",
			expected: []string{"label2"},
		},
		{
			issue:    github_test.Issue("", 1, []string{}, false),
			remove:   "label1",
			expected: []string{},
		},
	}
	for testNum, test := range tests {
		client, server, mux := github_test.InitServer(t, test.issue, nil, nil, nil, nil, nil)
		config := &Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)
		mux.HandleFunc(fmt.Sprintf("/repos/o/r/issues/1/labels/%s", test.remove), func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
		})

		obj, err := config.GetObject(*test.issue.Number)
		if err != nil {
			t.Fatalf("%d: unable to get issue: %v", testNum, *test.issue.Number)
		}
		obj.RemoveLabel(test.remove)
		if len(test.expected) != len(obj.Issue.Labels) {
			t.Errorf("%d: len(labels) not equal, expected labels: %v but got labels: %v", testNum, test.expected, obj.Issue.Labels)
			return
		}
		for i, l := range test.expected {
			if l != *obj.Issue.Labels[i].Name {
				t.Errorf("%d: expected labels: %v but got labels: %v", testNum, test.expected, obj.Issue.Labels)
			}
		}
		server.Close()
	}
}
func TestSubmitQueue(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name             string // because when the fail, counting is hard
		pr               *github.PullRequest
		issue            *github.Issue
		commits          []*github.RepositoryCommit
		events           []*github.IssueEvent
		ciStatus         *github.CombinedStatus
		lastBuildNumber  int
		gcsResult        utils.FinishedFile
		weakResults      map[int]utils.FinishedFile
		gcsJunit         map[string][]byte
		retest1Pass      bool
		retest2Pass      bool
		mergeAfterQueued bool
		reason           string
		state            string // what the github status context should be for the PR HEAD

		emergencyMergeStop bool
		isMerged           bool

		imHeadSHA      string
		imBaseSHA      string
		masterCommit   *github.RepositoryCommit
		retestsAvoided int // desired output
	}{
		// Should pass because the entire thing was run and good
		{
			name:            "Test1",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     true,
			reason:          merged,
			state:           "success",
			isMerged:        true,
		},
		// Entire thing was run and good, but emergency merge stop in progress
		{
			name:               "Test1+emergencyStop",
			pr:                 ValidPR(),
			issue:              LGTMIssue(),
			events:             NewLGTMEvents(),
			commits:            Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:           SuccessStatus(),
			lastBuildNumber:    LastBuildNumber(),
			gcsResult:          SuccessGCS(),
			weakResults:        map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:        true,
			retest2Pass:        true,
			emergencyMergeStop: true,
			isMerged:           false,
			reason:             e2eFailure,
			state:              "success",
		},
		// Should pass without running tests because we had a previous run.
		// TODO: Add a proper test to make sure we don't shuffle queue when we can just merge a PR
		{
			name:            "Test1+prevsuccess",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     true,
			reason:          merged,
			state:           "success",
			isMerged:        true,
			retestsAvoided:  1,
			imHeadSHA:       "mysha", // Set by ValidPR
			imBaseSHA:       "mastersha",
			masterCommit:    MasterCommit(),
		},
		// Should list as 'merged' but the merge should happen before it gets e2e tested
		// and we should bail early instead of waiting for a test that will never come.
		{
			name:            "Test2",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(),
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			// The test should never run, but if it does, make sure it fails
			mergeAfterQueued: true,
			reason:           mergedByHand,
			state:            "success",
		},
		// Should merge even though retest1Pass would have failed before of `retestNotRequiredLabel`
		{
			name:            "merge because of retestNotRequired",
			pr:              ValidPR(),
			issue:           NoRetestIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     false,
			retest2Pass:     false,
			reason:          merged,
			state:           "success",
			isMerged:        true,
		},
		// Fail because PR can't automatically merge
		{
			name:   "Test5",
			pr:     UnMergeablePR(),
			issue:  LGTMIssue(),
			reason: unmergeable,
			state:  "pending",
			// To avoid false errors in logs
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
		},
		// Fail because we don't know if PR can automatically merge
		{
			name:   "Test6",
			pr:     UndeterminedMergeablePR(),
			issue:  LGTMIssue(),
			reason: undeterminedMergability,
			state:  "pending",
			// To avoid false errors in logs
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
		},
		// Fail because the claYesLabel label was not applied
		{
			name:   "Test7",
			pr:     ValidPR(),
			issue:  NoCLAIssue(),
			reason: noCLA,
			state:  "pending",
			// To avoid false errors in logs
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
		},
		// Fail because github CI tests have failed (or at least are not success)
		{
			name:   "Test8",
			pr:     ValidPR(),
			issue:  LGTMIssue(),
			reason: ciFailure,
			state:  "pending",
			// To avoid false errors in logs
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
		},
		// Fail because missing LGTM label
		{
			name:     "Test10",
			pr:       ValidPR(),
			issue:    NoLGTMIssue(),
			ciStatus: SuccessStatus(),
			reason:   noLGTM,
			state:    "pending",
			// To avoid false errors in logs
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
		},
		// Fail because we can't tell if LGTM was added before the last change
		{
			name:     "Test11",
			pr:       ValidPR(),
			issue:    LGTMIssue(),
			ciStatus: SuccessStatus(),
			reason:   unknown,
			state:    "failure",
			// To avoid false errors in logs
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
		},
		// Fail because LGTM was added before the last change
		{
			name:     "Test12",
			pr:       ValidPR(),
			issue:    LGTMIssue(),
			ciStatus: SuccessStatus(),
			events:   OldLGTMEvents(),
			commits:  Commits(), // Modified at time.Unix(7), 8, and 9
			reason:   lgtmEarly,
			state:    "pending",
		},
		// Fail because jenkins instances are failing (whole submit queue blocks)
		{
			name:            "Test13",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			ciStatus:        SuccessStatus(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       FailGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			reason:          ghE2EQueued,
			state:           "success",
		},
		// Fail because the second run of github e2e tests failed
		{
			name:            "Test14",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			ciStatus:        SuccessStatus(),
			events:          NewLGTMEvents(),
			commits:         Commits(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			reason:          ghE2EFailed,
			state:           "pending",
		},
		// When we check the reason it may be queued or it may already have failed.
		{
			name:            "Test15",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			ciStatus:        SuccessStatus(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			reason:          ghE2EQueued,
			// The state is unpredictable. When it goes on the queue it is success.
			// When it fails the build it is pending. So state depends on how far along
			// this were when we checked. Thus just don't check it...
			state: "",
		},
		// Fail because the second run of github e2e tests failed
		{
			name:            "Test16",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			ciStatus:        SuccessStatus(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			reason:          ghE2EFailed,
			state:           "pending",
		},
		{
			name:            "Fail because E2E pass, but unit test fail",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     false,
			reason:          ghE2EFailed,
			state:           "pending",
		},
		{
			name:            "Fail because E2E fail, but unit test pass",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     false,
			retest2Pass:     true,
			reason:          ghE2EFailed,
			state:           "pending",
		},
		{
			name:            "Fail because doNotMerge label is present",
			pr:              ValidPR(),
			issue:           DoNotMergeIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     true,
			reason:          noMerge,
			state:           "pending",
		},
		// Should fail because the 'do-not-merge-milestone' is set.
		{
			name:            "Do Not Merge Milestone Set",
			pr:              ValidPR(),
			issue:           DoNotMergeMilestoneIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        SuccessStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     true,
			reason:          unmergeableMilestone,
			state:           "pending",
		},
		{
			name:            "Fail because retest status fail",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        RetestFailStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     true,
			reason:          ciFailure,
			state:           "pending",
		},
		{
			name:            "Fail because noretest status fail",
			pr:              ValidPR(),
			issue:           LGTMIssue(),
			events:          NewLGTMEvents(),
			commits:         Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:        NoRetestFailStatus(),
			lastBuildNumber: LastBuildNumber(),
			gcsResult:       SuccessGCS(),
			weakResults:     map[int]utils.FinishedFile{LastBuildNumber(): SuccessGCS()},
			retest1Pass:     true,
			retest2Pass:     true,
			reason:          ciFailure,
			state:           "pending",
		},

		// // Should pass even though last 'weakStable' build failed, as it wasn't "strong" failure
		// // and because previous two builds succeeded.
		// {
		// 	name:            "Test20",
		// 	pr:              ValidPR(),
		// 	issue:           LGTMIssue(),
		// 	events:          NewLGTMEvents(),
		// 	commits:         Commits(), // Modified at time.Unix(7), 8, and 9
		// 	ciStatus:        SuccessStatus(),
		// 	lastBuildNumber: LastBuildNumber(),
		// 	gcsResult:       SuccessGCS(),
		// 	weakResults: map[int]utils.FinishedFile{
		// 		LastBuildNumber():     FailGCS(),
		// 		LastBuildNumber() - 1: SuccessGCS(),
		// 		LastBuildNumber() - 2: SuccessGCS(),
		// 	},
		// 	gcsJunit: map[string][]byte{
		// 		"junit_01.xml": getJUnit(5, 0),
		// 		"junit_02.xml": getJUnit(6, 0),
		// 		"junit_03.xml": getJUnit(7, 0),
		// 	},
		// 	retest1Pass:  true,
		// 	retest2Pass: true,
		// 	reason:   merged,
		// 	state:    "success",
		// },
		// // Should fail because the failure of the weakStable job is a strong failure.
		// {
		// 	name:            "Test21",
		// 	pr:              ValidPR(),
		// 	issue:           LGTMIssue(),
		// 	events:          NewLGTMEvents(),
		// 	commits:         Commits(), // Modified at time.Unix(7), 8, and 9
		// 	ciStatus:        SuccessStatus(),
		// 	lastBuildNumber: LastBuildNumber(),
		// 	gcsResult:       SuccessGCS(),
		// 	weakResults: map[int]utils.FinishedFile{
		// 		LastBuildNumber():     FailGCS(),
		// 		LastBuildNumber() - 1: SuccessGCS(),
		// 		LastBuildNumber() - 2: SuccessGCS(),
		// 	},
		// 	gcsJunit: map[string][]byte{
		// 		"junit_01.xml": getJUnit(5, 0),
		// 		"junit_02.xml": getJUnit(6, 1),
		// 		"junit_03.xml": getJUnit(7, 0),
		// 	},
		// 	retest1Pass:  true,
		// 	retest2Pass: true,
		// 	reason:   e2eFailure,
		// 	state:    "success",
		// },
		// // Should fail even though weakStable job weakly failed, because there was another failure in
		// // previous two runs.
		// {
		// 	name:            "Test22",
		// 	pr:              ValidPR(),
		// 	issue:           LGTMIssue(),
		// 	events:          NewLGTMEvents(),
		// 	commits:         Commits(), // Modified at time.Unix(7), 8, and 9
		// 	ciStatus:        SuccessStatus(),
		// 	lastBuildNumber: LastBuildNumber(),
		// 	gcsResult:       SuccessGCS(),
		// 	weakResults: map[int]utils.FinishedFile{
		// 		LastBuildNumber():     FailGCS(),
		// 		LastBuildNumber() - 1: SuccessGCS(),
		// 		LastBuildNumber() - 2: FailGCS(),
		// 	},
		// 	gcsJunit: map[string][]byte{
		// 		"junit_01.xml": getJUnit(5, 0),
		// 		"junit_02.xml": getJUnit(6, 0),
		// 		"junit_03.xml": getJUnit(7, 0),
		// 	},
		// 	retest1Pass:  true,
		// 	retest2Pass: true,
		// 	reason:   e2eFailure,
		// 	state:    "success",
		// },
	}
	for testNum := range tests {
		test := &tests[testNum]
		fmt.Printf("---------Starting test %v (%v)---------------------\n", testNum, test.name)
		issueNum := testNum + 1
		issueNumStr := strconv.Itoa(issueNum)

		test.issue.Number = &issueNum
		client, server, mux := github_test.InitServer(t, test.issue, test.pr, test.events, test.commits, test.ciStatus, test.masterCommit, nil)

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)
		// Don't wait so long for it to go pending or back
		d := 250 * time.Millisecond
		config.PendingWaitTime = &d

		stateSet := ""
		wasMerged := false

		numTestChecks := 0
		path := "/bucket/logs/foo/latest-build.txt"
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(strconv.Itoa(test.lastBuildNumber)))

			// There is no good spot for this, but this gets called
			// before we queue the PR. So mark the PR as "merged".
			// When the sq initializes, it will check the Jenkins status,
			// so we don't want to modify the PR there. Instead we need
			// to wait until the second time we check Jenkins, which happens
			// we did the IsMerged() check.
			numTestChecks = numTestChecks + 1
			if numTestChecks == 2 && test.mergeAfterQueued {
				test.pr.Merged = boolPtr(true)
				test.pr.Mergeable = nil
			}
		})
		path = fmt.Sprintf("/bucket/logs/foo/%v/finished.json", test.lastBuildNumber)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(test.gcsResult)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})
		path = "/bucket/logs/bar/latest-build.txt"
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(strconv.Itoa(test.lastBuildNumber)))
		})
		for buildNumber := range test.weakResults {
			path = fmt.Sprintf("/bucket/logs/bar/%v/finished.json", buildNumber)
			// workaround go for loop semantics
			buildNumberCopy := buildNumber
			mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Unexpected method: %s", r.Method)
				}
				w.WriteHeader(http.StatusOK)
				data, err := json.Marshal(test.weakResults[buildNumberCopy])
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}
				w.Write(data)
			})
		}
		for junitFile, xml := range test.gcsJunit {
			path = fmt.Sprintf("/bucket/logs/bar/%v/artifacts/%v", test.lastBuildNumber, junitFile)
			// workaround go for loop semantics
			xmlCopy := xml
			mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
				if r.Method != "GET" {
					t.Errorf("Unexpected method: %s", r.Method)
				}
				w.WriteHeader(http.StatusOK)
				w.Write(xmlCopy)
			})
		}
		path = fmt.Sprintf("/repos/o/r/issues/%d/comments", issueNum)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method == "POST" {
				c := new(github.IssueComment)
				json.NewDecoder(r.Body).Decode(c)
				msg := *c.Body
				if strings.HasPrefix(msg, "@"+jenkinsBotName+" test this") {
					go fakeRunGithubE2ESuccess(test.ciStatus, test.retest1Pass, test.retest2Pass)
				}
				w.WriteHeader(http.StatusOK)
				data, err := json.Marshal(github.IssueComment{})
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}
				w.Write(data)
				return
			}
			if r.Method == "GET" {
				w.WriteHeader(http.StatusOK)
				data, err := json.Marshal([]github.IssueComment{})
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}
				w.Write(data)
				return
			}
			t.Errorf("Unexpected method: %s", r.Method)
		})
		path = fmt.Sprintf("/repos/o/r/pulls/%d/merge", issueNum)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "PUT" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(github.PullRequestMergeResult{})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
			test.pr.Merged = boolPtr(true)
			wasMerged = true
		})
		path = fmt.Sprintf("/repos/o/r/statuses/%s", *test.pr.Head.SHA)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "POST" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			decoder := json.NewDecoder(r.Body)
			var status github.RepoStatus
			err := decoder.Decode(&status)
			if err != nil {
				t.Errorf("Unable to decode status: %v", err)
			}

			stateSet = *status.State

			data, err := json.Marshal(status)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.WriteHeader(http.StatusOK)
			w.Write(data)
		})

		sq := getTestSQ(true, config, server)
		sq.setEmergencyMergeStop(test.emergencyMergeStop)

		obj := github_util.TestObject(config, test.issue, test.pr, test.commits, test.events)
		if test.imBaseSHA != "" && test.imHeadSHA != "" {
			sq.interruptedObj = &submitQueueInterruptedObject{obj, test.imHeadSHA, test.imBaseSHA}
		}
		sq.Munge(obj)
		done := make(chan bool, 1)
		go func(done chan bool) {
			for {
				defer func() {
					if r := recover(); r != nil {
						t.Errorf("%d:%q panic'd likely writing to 'done' channel", testNum, test.name)
					}
				}()

				reason := func() string {
					sq.Mutex.Lock()
					defer sq.Mutex.Unlock()
					return sq.prStatus[issueNumStr].Reason
				}

				if reason() == test.reason {
					done <- true
					return
				}
				found := false
				for _, status := range sq.statusHistory {
					if status.Reason == test.reason {
						found = true
						break
					}
				}
				if found {
					done <- true
					return
				}
				time.Sleep(1 * time.Millisecond)
			}
		}(done)
		select {
		case <-done:
		case <-time.After(10 * time.Second):
			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)
		}
		close(done)
		server.Close()

		if test.state != "" && test.state != stateSet {
			t.Errorf("%d:%q state set to %q but expected %q", testNum, test.name, stateSet, test.state)
		}
		if test.isMerged != wasMerged {
			t.Errorf("%d:%q PR merged = %v but wanted %v", testNum, test.name, wasMerged, test.isMerged)
		}
		if e, a := test.retestsAvoided, int(sq.retestsAvoided); e != a {
			t.Errorf("%d:%q expected %v tests avoided but got %v", testNum, test.name, e, a)
		}
	}
}
func TestQueueOrder(t *testing.T) {
	timeBase := time.Now()
	time2 := timeBase.Add(6 * time.Minute).Unix()
	time3 := timeBase.Add(5 * time.Minute).Unix()
	time4 := timeBase.Add(4 * time.Minute).Unix()
	time5 := timeBase.Add(3 * time.Minute).Unix()
	time6 := timeBase.Add(2 * time.Minute).Unix()
	labelEvents := map[int][]github_test.LabelTime{
		2: {{"me", lgtmLabel, time2}},
		3: {{"me", lgtmLabel, time3}},
		4: {{"me", lgtmLabel, time4}},
		5: {{"me", lgtmLabel, time5}},
		6: {{"me", lgtmLabel, time6}},
	}

	tests := []struct {
		name          string
		issues        []*github.Issue
		issueToEvents map[int][]github_test.LabelTime
		expected      []int
	}{
		{
			name: "Just prNum",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, nil, true),
				github_test.Issue(someUserName, 3, nil, true),
				github_test.Issue(someUserName, 4, nil, true),
				github_test.Issue(someUserName, 5, nil, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{5, 4, 3, 2},
		},
		{
			name: "With a priority label",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, []string{"priority/P1"}, true),
				github_test.Issue(someUserName, 3, []string{"priority/P1"}, true),
				github_test.Issue(someUserName, 4, []string{"priority/P0"}, true),
				github_test.Issue(someUserName, 5, nil, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{4, 3, 2, 5},
		},
		{
			name: "With two priority labels",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, []string{"priority/P1", "priority/P0"}, true),
				github_test.Issue(someUserName, 3, []string{"priority/P1"}, true),
				github_test.Issue(someUserName, 4, []string{"priority/P0"}, true),
				github_test.Issue(someUserName, 5, nil, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{4, 2, 3, 5},
		},
		{
			name: "With unrelated labels",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, []string{"priority/P1", "priority/P0"}, true),
				github_test.Issue(someUserName, 3, []string{"priority/P1", "kind/design"}, true),
				github_test.Issue(someUserName, 4, []string{"priority/P0"}, true),
				github_test.Issue(someUserName, 5, []string{lgtmLabel, "kind/new-api"}, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{4, 2, 3, 5},
		},
		{
			name: "With invalid priority label",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, []string{"priority/P1", "priority/P0"}, true),
				github_test.Issue(someUserName, 3, []string{"priority/P1", "kind/design", "priority/high"}, true),
				github_test.Issue(someUserName, 4, []string{"priority/P0", "priorty/bob"}, true),
				github_test.Issue(someUserName, 5, nil, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{4, 2, 3, 5},
		},
		{
			name: "Unlabeled counts as P3",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, nil, true),
				github_test.Issue(someUserName, 3, []string{"priority/P3"}, true),
				github_test.Issue(someUserName, 4, []string{"priority/P2"}, true),
				github_test.Issue(someUserName, 5, nil, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{4, 5, 3, 2},
		},
		{
			name: "retestNotRequiredLabel counts as P-negative 1",
			issues: []*github.Issue{
				github_test.Issue(someUserName, 2, nil, true),
				github_test.Issue(someUserName, 3, []string{"priority/P3"}, true),
				github_test.Issue(someUserName, 4, []string{"priority/P0"}, true),
				github_test.Issue(someUserName, 5, nil, true),
				github_test.Issue(someUserName, 6, []string{"priority/P3", retestNotRequiredLabel}, true),
			},
			issueToEvents: labelEvents,
			expected:      []int{6, 4, 5, 3, 2},
		},
	}
	for testNum, test := range tests {
		config := &github_util.Config{}
		client, server, mux := github_test.InitServer(t, nil, nil, github_test.MultiIssueEvents(test.issueToEvents), nil, nil, nil, nil)
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)
		sq := getTestSQ(false, config, server)
		for i := range test.issues {
			issue := test.issues[i]
			github_test.ServeIssue(t, mux, issue)

			issueNum := *issue.Number
			obj, err := config.GetObject(issueNum)
			if err != nil {
				t.Fatalf("%d:%q unable to get issue: %v", testNum, test.name, err)
			}
			sq.githubE2EQueue[issueNum] = obj
		}
		actual := sq.orderedE2EQueue()
		if len(actual) != len(test.expected) {
			t.Fatalf("%d:%q len(actual):%v != len(expected):%v", testNum, test.name, actual, test.expected)
		}
		for i, a := range actual {
			e := test.expected[i]
			if a != e {
				t.Errorf("%d:%q a[%d]:%d != e[%d]:%d", testNum, test.name, i, a, i, e)
			}
		}
		server.Close()
	}
}
Example #6
0
func TestAssignFixes(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name       string
		assignee   string
		pr         *github.PullRequest
		prIssue    *github.Issue
		prBody     string
		fixesIssue *github.Issue
	}{
		{
			name:       "fixes an issue",
			assignee:   "dev45",
			pr:         github_test.PullRequest("dev45", false, true, true),
			prIssue:    github_test.Issue("fred", 7779, []string{}, true),
			prBody:     "does stuff and fixes #8889.",
			fixesIssue: github_test.Issue("jill", 8889, []string{}, true),
		},
	}
	for _, test := range tests {
		test.prIssue.Body = &test.prBody
		client, server, mux := github_test.InitServer(t, test.prIssue, test.pr, nil, nil, nil, nil)
		path := fmt.Sprintf("/repos/o/r/issues/%d", *test.fixesIssue.Number)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			data, err := json.Marshal(test.fixesIssue)
			if err != nil {
				t.Errorf("%v", err)
			}
			if r.Method != "PATCH" && r.Method != "GET" {
				t.Errorf("Unexpected method: expected: GET/PATCH got: %s", r.Method)
			}
			if r.Method == "PATCH" {
				body, _ := ioutil.ReadAll(r.Body)

				type IssuePatch struct {
					Assignee string
				}
				var ip IssuePatch
				err := json.Unmarshal(body, &ip)
				if err != nil {
					fmt.Println("error:", err)
				}
				if ip.Assignee != test.assignee {
					t.Errorf("Patching the incorrect Assignee %v instead of %v", ip.Assignee, test.assignee)
				}
			}
			w.WriteHeader(http.StatusOK)
			w.Write(data)
		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		c := AssignFixesMunger{}
		err := c.Initialize(config, nil)
		if err != nil {
			t.Fatalf("%v", err)
		}

		err = c.EachLoop()
		if err != nil {
			t.Fatalf("%v", err)
		}

		obj, err := config.GetObject(*test.prIssue.Number)
		if err != nil {
			t.Fatalf("%v", err)
		}

		c.Munge(obj)
		server.Close()
	}
}
func TestReleaseNoteLabel(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name        string
		issue       *github.Issue
		body        string
		branch      string
		secondIssue *github.Issue
		mustHave    []string
		mustNotHave []string
	}{
		{
			name:        "LGTM with release-note",
			issue:       github_test.Issue(botName, 1, []string{"lgtm", releaseNote}, true),
			mustHave:    []string{"lgtm", releaseNote},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "LGTM with release-note-none",
			issue:       github_test.Issue(botName, 1, []string{"lgtm", releaseNoteNone}, true),
			mustHave:    []string{"lgtm", releaseNoteNone},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "LGTM with release-note-action-required",
			issue:       github_test.Issue(botName, 1, []string{"lgtm", releaseNoteActionRequired}, true),
			mustHave:    []string{"lgtm", releaseNoteActionRequired},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "LGTM with release-note-label-needed",
			issue:       github_test.Issue(botName, 1, []string{"lgtm", releaseNoteLabelNeeded}, true),
			mustHave:    []string{releaseNoteLabelNeeded},
			mustNotHave: []string{"lgtm"},
		},
		{
			name:        "LGTM only",
			issue:       github_test.Issue(botName, 1, []string{"lgtm"}, true),
			mustHave:    []string{releaseNoteLabelNeeded},
			mustNotHave: []string{"lgtm"},
		},
		{
			name:     "No labels",
			issue:    github_test.Issue(botName, 1, []string{}, true),
			mustHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:     "release-note",
			issue:    github_test.Issue(botName, 1, []string{releaseNote}, true),
			mustHave: []string{releaseNote},
		},
		{
			name:     "release-note-none",
			issue:    github_test.Issue(botName, 1, []string{releaseNoteNone}, true),
			mustHave: []string{releaseNoteNone},
		},
		{
			name:     "release-note-action-required",
			issue:    github_test.Issue(botName, 1, []string{releaseNoteActionRequired}, true),
			mustHave: []string{releaseNoteActionRequired},
		},
		{
			name:        "release-note and release-note-label-needed",
			issue:       github_test.Issue(botName, 1, []string{releaseNote, releaseNoteLabelNeeded}, true),
			mustHave:    []string{releaseNote},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "release-note-none and release-note-label-needed",
			issue:       github_test.Issue(botName, 1, []string{releaseNoteNone, releaseNoteLabelNeeded}, true),
			mustHave:    []string{releaseNoteNone},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "release-note-action-required and release-note-label-needed",
			issue:       github_test.Issue(botName, 1, []string{releaseNoteActionRequired, releaseNoteLabelNeeded}, true),
			mustHave:    []string{releaseNoteActionRequired},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "do not add needs label when parent PR has releaseNote label",
			branch:      "release-1.2",
			issue:       github_test.Issue(botName, 1, []string{}, true),
			body:        "Cherry pick of #2 on release-1.2.",
			secondIssue: github_test.Issue(botName, 2, []string{releaseNote}, true),
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "do not touch LGTM on non-master when parent PR has releaseNote label",
			branch:      "release-1.2",
			issue:       github_test.Issue(botName, 1, []string{"lgtm"}, true),
			body:        "Cherry pick of #2 on release-1.2.",
			secondIssue: github_test.Issue(botName, 2, []string{releaseNote}, true),
			mustHave:    []string{"lgtm"},
			mustNotHave: []string{releaseNoteLabelNeeded},
		},
		{
			name:        "add needs label when parent PR does not have releaseNote label",
			branch:      "release-1.2",
			issue:       github_test.Issue(botName, 1, []string{}, true),
			body:        "Cherry pick of #2 on release-1.2.",
			secondIssue: github_test.Issue(botName, 2, []string{releaseNoteNone}, true),
			mustHave:    []string{releaseNoteLabelNeeded},
		},
		{
			name:        "remove LGTM on non-master when parent PR has releaseNote label",
			branch:      "release-1.2",
			issue:       github_test.Issue(botName, 1, []string{"lgtm"}, true),
			body:        "Cherry pick of #2 on release-1.2.",
			secondIssue: github_test.Issue(botName, 2, []string{releaseNoteNone}, true),
			mustHave:    []string{releaseNoteLabelNeeded},
			mustNotHave: []string{"lgtm"},
		},
	}
	for testNum, test := range tests {
		pr := ValidPR()
		if test.branch != "" {
			pr.Base.Ref = &test.branch
		}
		test.issue.Body = &test.body
		client, server, mux := github_test.InitServer(t, test.issue, pr, nil, nil, nil)
		path := fmt.Sprintf("/repos/o/r/issue/%s/labels", *test.issue.Number)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := []github.Label{{}}
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})
		if test.secondIssue != nil {
			path = fmt.Sprintf("/repos/o/r/issues/%d", *test.secondIssue.Number)
			mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
				data, err := json.Marshal(test.secondIssue)
				if err != nil {
					t.Errorf("%v", err)
				}
				if r.Method != "GET" {
					t.Errorf("Unexpected method: expected: GET got: %s", r.Method)
				}
				w.WriteHeader(http.StatusOK)
				w.Write(data)
			})
		}

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		r := ReleaseNoteLabel{}
		err := r.Initialize(config, nil)
		if err != nil {
			t.Fatalf("%v", err)
		}

		err = r.EachLoop()
		if err != nil {
			t.Fatalf("%v", err)
		}

		obj, err := config.GetObject(*test.issue.Number)
		if err != nil {
			t.Fatalf("%v", err)
		}

		r.Munge(obj)

		for _, l := range test.mustHave {
			if !obj.HasLabel(l) {
				t.Errorf("%s:%d: Did not find label %q, labels: %v", test.name, testNum, l, obj.Issue.Labels)
			}
		}
		for _, l := range test.mustNotHave {
			if obj.HasLabel(l) {
				t.Errorf("%s:%d: Found label %q and should not have, labels: %v", test.name, testNum, l, obj.Issue.Labels)
			}
		}
		server.Close()
	}
}
Example #8
0
func TestCLAMunger(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name        string
		issue       *github.Issue
		status      *github.CombinedStatus
		mustHave    []string
		mustNotHave []string
	}{
		{
			name:  "CLA status success should add cncf/cla:yes label and remove cncf/cla:no label",
			issue: github_test.Issue("user1", 1, []string{cncfClaNoLabel}, true),
			status: &github.CombinedStatus{
				Statuses: []github.RepoStatus{
					{
						Context: stringPtr(claContext),
						State:   stringPtr(contextSuccess),
					},
				},
			},
			mustHave:    []string{cncfClaYesLabel},
			mustNotHave: []string{cncfClaNoLabel},
		},
		{
			name:  "CLA status failure should add cncf/cla:no label and remove cncf/cla:yes label",
			issue: github_test.Issue("user1", 1, []string{cncfClaYesLabel}, true),
			status: &github.CombinedStatus{
				Statuses: []github.RepoStatus{
					{
						Context: stringPtr(claContext),
						State:   stringPtr(contextFailure),
					},
				},
			},
			mustHave:    []string{cncfClaNoLabel},
			mustNotHave: []string{cncfClaYesLabel},
		},
		{
			name:  "CLA status error should apply cncf/cla:no label.",
			issue: github_test.Issue("user1", 1, []string{}, true),
			status: &github.CombinedStatus{
				Statuses: []github.RepoStatus{
					{
						Context: stringPtr(claContext),
						State:   stringPtr(contextError),
					},
				},
			},
			mustHave:    []string{cncfClaNoLabel},
			mustNotHave: []string{cncfClaYesLabel},
		},
		{
			name:  "CLA status pending should not apply labels.",
			issue: github_test.Issue("user1", 1, []string{}, true),
			status: &github.CombinedStatus{
				Statuses: []github.RepoStatus{
					{
						Context: stringPtr(claContext),
						State:   stringPtr(contextPending),
					},
				},
			},
			mustHave:    []string{},
			mustNotHave: []string{cncfClaYesLabel, cncfClaNoLabel},
		},
	}

	for testNum, test := range tests {
		pr := ValidPR()
		pr.Head = &github.PullRequestBranch{}
		pr.Head.SHA = stringPtr("0")
		client, server, mux := github_test.InitServer(t, test.issue, pr, nil, nil, nil, nil, nil)
		setUpMockFunctions(mux, t, test.issue)

		path := fmt.Sprintf("/repos/o/r/commits/%s/status", *pr.Head.SHA)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := test.status
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		cla := ClaMunger{
			CLAStatusContext: claContext,
			pinger:           c.NewPinger("[fake-ping]").SetDescription(""),
		}
		obj, err := config.GetObject(*test.issue.Number)
		if err != nil {
			t.Fatalf("%v", err)
		}
		cla.Munge(obj)

		for _, lab := range test.mustHave {
			if !obj.HasLabel(lab) {
				t.Errorf("%s:%d: Did not find label %q, labels: %v", test.name, testNum, lab, obj.Issue.Labels)
			}
		}
		for _, lab := range test.mustNotHave {
			if obj.HasLabel(lab) {
				t.Errorf("%s:%d: Found label %q and should not have, labels: %v", test.name, testNum, lab, obj.Issue.Labels)
			}
		}
		server.Close()
	}
}
func TestPathLabelMunge(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		commits     []github.RepositoryCommit
		mustHave    []string
		mustNotHave []string
	}{
		{
			commits:     pathCommits("docs/proposals"),
			mustHave:    []string{"kind/design"},
			mustNotHave: []string{"kind/api-change", "kind/new-api"},
		},
		{
			commits:     pathCommits("docs/my/proposals"),
			mustHave:    []string{},
			mustNotHave: []string{"kind/design", "kind/api-change", "kind/new-api"},
		},
		{
			commits:     pathCommits("pkg/api/types.go"),
			mustHave:    []string{"kind/api-change"},
			mustNotHave: []string{"kind/design", "kind/new-api"},
		},
		{
			commits:     pathCommits("pkg/api/v1/types.go"),
			mustHave:    []string{"kind/api-change"},
			mustNotHave: []string{"kind/design", "kind/new-api"},
		},
		{
			commits:     pathCommits("pkg/api/v1/duh/types.go"),
			mustHave:    []string{},
			mustNotHave: []string{"kind/design", "kind/api-change", "kind/new-api"},
		},
		{
			commits:     pathCommits("pkg/apis/experimental/register.go"),
			mustHave:    []string{"kind/new-api"},
			mustNotHave: []string{"kind/api-change", "kind/design"},
		},
		{
			commits:     pathCommits("pkg/apis/experimental/v1beta1/register.go"),
			mustHave:    []string{"kind/new-api"},
			mustNotHave: []string{"kind/api-change", "kind/design"},
		},
		{
			commits:     pathCommits("pkg/apis/experiments/v1beta1/duh/register.go"),
			mustHave:    []string{},
			mustNotHave: []string{"kind/design", "kind/api-change", "kind/new-api"},
		},
	}
	for testNum, test := range tests {
		client, server, mux := github_test.InitServer(t, NoOKToMergeIssue(), ValidPR(), nil, test.commits, nil)
		mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := []github.Label{
				{
					// TODO figure out the label name from the request...
					Name: stringPtr("label"),
				},
			}
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)

		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		p := PathLabelMunger{}
		p.pathLabelFile = "../path-label.txt"
		err := p.Initialize(config)
		if err != nil {
			t.Fatalf("%v", err)
		}

		obj, err := config.GetObject(1)
		if err != nil {
			t.Fatalf("%v", err)
		}

		p.Munge(obj)

		for _, l := range test.mustHave {
			if !obj.HasLabel(l) {
				t.Errorf("%d: Did not find label %q, labels: %v", testNum, l, obj.Issue.Labels)
			}
		}
		for _, l := range test.mustNotHave {
			if obj.HasLabel(l) {
				t.Errorf("%d: Found label %q and should not have, labels: %v", testNum, l, obj.Issue.Labels)
			}
		}
		server.Close()
	}
}
Example #10
0
func TestGetLastModified(t *testing.T) {
	tests := []struct {
		commits      []github.RepositoryCommit
		expectedTime *time.Time
	}{
		{
			commits:      github_test.Commits(1, 10),
			expectedTime: timePtr(time.Unix(10, 0)),
		},
		{
			// remember the order of github_test.Commits() is non-deterministic
			commits:      github_test.Commits(3, 10),
			expectedTime: timePtr(time.Unix(12, 0)),
		},
		{
			// so this is probably not quite the same test...
			commits:      github_test.Commits(3, 8),
			expectedTime: timePtr(time.Unix(10, 0)),
		},
		{
			//  We can't represent the same time in 2 commits using github_test.Commits()
			commits: []github.RepositoryCommit{
				{
					SHA: stringPtr("mysha1"),
					Commit: &github.Commit{
						SHA: stringPtr("mysha1"),
						Committer: &github.CommitAuthor{
							Date: timePtr(time.Unix(9, 0)),
						},
					},
				},
				{
					SHA: stringPtr("mysha2"),
					Commit: &github.Commit{
						SHA: stringPtr("mysha2"),
						Committer: &github.CommitAuthor{
							Date: timePtr(time.Unix(10, 0)),
						},
					},
				},
				{
					SHA: stringPtr("mysha3"),
					Commit: &github.Commit{
						SHA: stringPtr("mysha3"),
						Committer: &github.CommitAuthor{
							Date: timePtr(time.Unix(9, 0)),
						},
					},
				},
			},
			expectedTime: timePtr(time.Unix(10, 0)),
		},
	}
	for _, test := range tests {
		client, server, _ := github_test.InitServer(t, nil, nil, nil, test.commits, nil, nil)
		config := &Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		obj := &MungeObject{
			config: config,
			Issue:  github_test.Issue("bob", 1, nil, true),
		}
		ts := obj.LastModifiedTime()
		if !ts.Equal(*test.expectedTime) {
			t.Errorf("expected: %v, saw: %v for: %v", test.expectedTime, ts, test)
		}
		server.Close()
	}
}
Example #11
0
func TestForEachIssueDo(t *testing.T) {
	issue1 := github_test.Issue("bob", 1, nil, true)
	issue5 := github_test.Issue("bob", 5, nil, true)
	issue6 := github_test.Issue("bob", 6, nil, true)
	issue7 := github_test.Issue("bob", 7, nil, true)
	issue20 := github_test.Issue("bob", 20, nil, true)

	user := github.User{Login: stringPtr("bob")}
	tests := []struct {
		Issues      [][]github.Issue
		Pages       []int
		ValidIssues int
	}{
		{
			Issues: [][]github.Issue{
				{*issue5},
			},
			Pages:       []int{0},
			ValidIssues: 1,
		},
		{
			Issues: [][]github.Issue{
				{*issue5},
				{*issue6},
				{*issue7},
				{
					{
						Number: intPtr(8),
						// no User, invalid
					},
				},
			},
			Pages:       []int{4, 4, 4, 0},
			ValidIssues: 3,
		},
		{
			Issues: [][]github.Issue{
				// Invalid 1 < MinPRNumber
				// Invalid 20 > MaxPRNumber
				{*issue1, *issue20},
				// two valid issues
				{*issue5, *issue6},
				{
					{
						// no Number, invalid
						User: &user,
					},
				},
			},
			Pages:       []int{3, 3, 0},
			ValidIssues: 2,
		},
	}

	for i, test := range tests {
		client, server, mux := github_test.InitServer(t, nil, nil, nil, nil, nil, nil)
		config := &Config{
			client:      client,
			Org:         "foo",
			Project:     "bar",
			MinPRNumber: 5,
			MaxPRNumber: 15,
		}
		count := 0
		mux.HandleFunc("/repos/foo/bar/issues", func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			// this means page 0, return page 1
			page := r.URL.Query().Get("page")
			if page == "" {
				t.Errorf("Should not get page 0, start with page 1")
			}
			if page != strconv.Itoa(count+1) {
				t.Errorf("Unexpected page: %s", r.URL.Query().Get("page"))
			}
			if r.URL.Query().Get("sort") != "created" {
				t.Errorf("Unexpected sort: %s", r.URL.Query().Get("sort"))
			}
			if r.URL.Query().Get("per_page") != "100" {
				t.Errorf("Unexpected per_page: %s", r.URL.Query().Get("per_page"))
			}
			w.Header().Add("Link",
				fmt.Sprintf("<https://api.github.com/?page=%d>; rel=\"last\"", test.Pages[count]))
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(test.Issues[count])
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}

			w.Write(data)
			count++
		})
		objects := []*MungeObject{}
		handle := func(obj *MungeObject) error {
			objects = append(objects, obj)
			return nil
		}
		err := config.ForEachIssueDo(handle)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if len(objects) != test.ValidIssues {
			t.Errorf("Test: %d Unexpected output %d vs %d", i, len(objects), test.ValidIssues)
		}

		if count != len(test.Issues) {
			t.Errorf("Test: %d Unexpected number of fetches: %d", i, count)
		}
		server.Close()
	}
}
func TestPathLabelMunge(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		files       []*github.CommitFile
		events      []*github.IssueEvent
		mustHave    []string
		mustNotHave []string
	}{
		{
			files:       commitFiles([]string{"docs/proposals"}),
			events:      BotAddedDesign(),
			mustHave:    []string{"kind/design"},
			mustNotHave: []string{"kind/api-change", "kind/new-api"},
		},
		{
			files:       commitFiles([]string{"docs/my/proposals"}),
			events:      BotAddedDesign(),
			mustHave:    []string{},
			mustNotHave: []string{"kind/design", "kind/api-change", "kind/new-api"},
		},
		{
			files:       commitFiles([]string{"pkg/api/types.go"}),
			events:      BotAddedDesign(),
			mustHave:    []string{"kind/api-change"},
			mustNotHave: []string{"kind/design", "kind/new-api"},
		},
		{
			files:       commitFiles([]string{"pkg/api/v1/types.go"}),
			events:      BotAddedDesign(),
			mustHave:    []string{"kind/api-change"},
			mustNotHave: []string{"kind/design", "kind/new-api"},
		},
		{
			files:       commitFiles([]string{"pkg/api/v1/duh/types.go"}),
			events:      BotAddedDesign(),
			mustHave:    []string{},
			mustNotHave: []string{"kind/design", "kind/api-change", "kind/new-api"},
		},
		{
			files:       commitFiles([]string{"pkg/apis/experimental/register.go"}),
			events:      BotAddedDesign(),
			mustHave:    []string{"kind/new-api"},
			mustNotHave: []string{"kind/api-change", "kind/design"},
		},
		{
			files:       commitFiles([]string{"pkg/apis/experimental/v1beta1/register.go"}),
			events:      BotAddedDesign(),
			mustHave:    []string{"kind/new-api"},
			mustNotHave: []string{"kind/api-change", "kind/design"},
		},
		{
			files:       commitFiles([]string{"pkg/apis/experiments/v1beta1/duh/register.go"}),
			events:      BotAddedDesign(),
			mustHave:    []string{},
			mustNotHave: []string{"kind/design", "kind/api-change", "kind/new-api"},
		},
		{
			files:       commitFiles([]string{"README"}),
			events:      OtherAddedDesign(),
			mustHave:    []string{"kind/design"},
			mustNotHave: []string{"kind/api-change", "kind/new-api"},
		},
	}
	for testNum, test := range tests {
		client, server, mux := github_test.InitServer(t, docsProposalIssue(), ValidPR(), test.events, nil, nil, nil, test.files)
		mux.HandleFunc("/repos/o/r/issues/1/labels/kind/design", func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte{})
		})
		mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := []github.Label{{}}
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)

		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		p := PathLabelMunger{}
		p.PathLabelFile = "../path-label.txt"
		err := p.Initialize(config, nil)
		if err != nil {
			t.Fatalf("%v", err)
		}

		obj, err := config.GetObject(1)
		if err != nil {
			t.Fatalf("%v", err)
		}

		p.Munge(obj)

		for _, l := range test.mustHave {
			if !obj.HasLabel(l) {
				t.Errorf("%d: Did not find label %q, labels: %v", testNum, l, obj.Issue.Labels)
			}
		}
		for _, l := range test.mustNotHave {
			if obj.HasLabel(l) {
				t.Errorf("%d: Found label %q and should not have, labels: %v", testNum, l, obj.Issue.Labels)
			}
		}
		server.Close()
	}
}
func TestCherrypickAuthApprove(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name                string
		issue               *github.Issue
		issueBody           string
		prBranch            string
		parentIssue         *github.Issue
		milestone           *github.Milestone
		shouldHaveLabel     string
		shouldHaveMilestone string
		shouldNotHaveLabel  string
		shouldNotHaveMile   string
	}{
		{
			name:                "Add cpApproved and milestone",
			issue:               github_test.Issue(botName, 1, []string{}, true),
			issueBody:           "Cherry pick of #2 on release-1.2.",
			prBranch:            "release-1.2",
			parentIssue:         github_test.Issue(botName, 2, []string{cpApprovedLabel}, true),
			milestone:           &github.Milestone{Title: stringPtr("v1.2"), Number: intPtr(1)},
			shouldHaveLabel:     cpApprovedLabel,
			shouldHaveMilestone: "v1.2",
		},
		{
			name:                "Add milestone",
			issue:               github_test.Issue(botName, 1, []string{cpApprovedLabel}, true),
			issueBody:           "Cherry pick of #2 on release-1.2.",
			prBranch:            "release-1.2",
			parentIssue:         github_test.Issue(botName, 2, []string{cpApprovedLabel}, true),
			milestone:           &github.Milestone{Title: stringPtr("v1.2"), Number: intPtr(1)},
			shouldHaveLabel:     cpApprovedLabel,
			shouldHaveMilestone: "v1.2",
		},
		{
			name:               "Do not add because parent not have",
			issue:              github_test.Issue(botName, 1, []string{}, true),
			issueBody:          "Cherry pick of #2 on release-1.2.",
			prBranch:           "release-1.2",
			parentIssue:        github_test.Issue(botName, 2, []string{}, true),
			milestone:          &github.Milestone{Title: stringPtr("v1.2"), Number: intPtr(1)},
			shouldNotHaveLabel: cpApprovedLabel,
			shouldNotHaveMile:  "v1.2",
		},
		{
			name:               "PR against wrong branch",
			issue:              github_test.Issue(botName, 1, []string{}, true),
			issueBody:          "Cherry pick of #2 on release-1.2.",
			prBranch:           "release-1.1",
			parentIssue:        github_test.Issue(botName, 2, []string{cpApprovedLabel}, true),
			milestone:          &github.Milestone{Title: stringPtr("v1.2"), Number: intPtr(1)},
			shouldNotHaveLabel: cpApprovedLabel,
			shouldNotHaveMile:  "v1.2",
		},
		{
			name:               "Parent milestone against other branch",
			issue:              github_test.Issue(botName, 1, []string{}, true),
			issueBody:          "Cherry pick of #2 on release-1.2.",
			prBranch:           "release-1.2",
			parentIssue:        github_test.Issue(botName, 2, []string{cpApprovedLabel}, true),
			milestone:          &github.Milestone{Title: stringPtr("v1.1"), Number: intPtr(1)},
			shouldNotHaveLabel: cpApprovedLabel,
			shouldNotHaveMile:  "v1.1",
		},
		{
			name:               "Parent has no milestone",
			issue:              github_test.Issue(botName, 1, []string{}, true),
			issueBody:          "Cherry pick of #2 on release-1.2.",
			prBranch:           "release-1.2",
			parentIssue:        github_test.Issue(botName, 2, []string{cpApprovedLabel}, true),
			shouldNotHaveLabel: cpApprovedLabel,
			shouldNotHaveMile:  "v1.2",
		},
	}
	for testNum, test := range tests {
		test.issue.Body = &test.issueBody

		pr := ValidPR()
		pr.Base.Ref = &test.prBranch
		client, server, mux := github_test.InitServer(t, test.issue, pr, nil, nil, nil, nil, nil)

		path := fmt.Sprintf("/repos/o/r/issues/%d/labels", *test.issue.Number)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := []github.Label{{}}
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})

		path = "/repos/o/r/milestones"
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := []github.Milestone{}
			if test.milestone != nil {
				out = append(out, *test.milestone)
			}
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})

		test.parentIssue.Milestone = test.milestone
		path = fmt.Sprintf("/repos/o/r/issues/%d", *test.parentIssue.Number)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			data, err := json.Marshal(test.parentIssue)
			if err != nil {
				t.Errorf("%v", err)
			}
			if r.Method != "GET" {
				t.Errorf("Unexpected method: expected: GET got: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			w.Write(data)
		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		c := CherrypickAutoApprove{}
		err := c.Initialize(config, nil)
		if err != nil {
			t.Fatalf("%v", err)
		}

		err = c.EachLoop()
		if err != nil {
			t.Fatalf("%v", err)
		}

		obj, err := config.GetObject(*test.issue.Number)
		if err != nil {
			t.Fatalf("%v", err)
		}

		c.Munge(obj)
		if test.shouldHaveLabel != "" && !obj.HasLabel(test.shouldHaveLabel) {
			t.Errorf("%d:%q: missing label %q", testNum, test.name, test.shouldHaveLabel)
		}
		if test.shouldHaveMilestone != "" && obj.ReleaseMilestone() != test.shouldHaveMilestone {
			t.Errorf("%d:%q: missing milestone %q", testNum, test.name, test.shouldHaveMilestone)
		}
		if test.shouldNotHaveLabel != "" && obj.HasLabel(test.shouldNotHaveLabel) {
			t.Errorf("%d:%q: extra label %q", testNum, test.name, test.shouldNotHaveLabel)
		}
		if test.shouldNotHaveMile != "" && obj.ReleaseMilestone() == test.shouldNotHaveMile {
			t.Errorf("%d:%q: extra milestone %q", testNum, test.name, test.shouldNotHaveMile)
		}

		server.Close()
	}
}
func TestQueueOrder(t *testing.T) {
	tests := []struct {
		name     string
		issues   []github.Issue
		expected []int
	}{
		{
			name: "Just prNum",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, nil, true),
				*github_test.Issue(whitelistUser, 3, nil, true),
				*github_test.Issue(whitelistUser, 4, nil, true),
				*github_test.Issue(whitelistUser, 5, nil, true),
			},
			expected: []int{2, 3, 4, 5},
		},
		{
			name: "With a priority label",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, []string{"priority/P1"}, true),
				*github_test.Issue(whitelistUser, 3, []string{"priority/P1"}, true),
				*github_test.Issue(whitelistUser, 4, []string{"priority/P0"}, true),
				*github_test.Issue(whitelistUser, 5, nil, true),
			},
			expected: []int{4, 2, 3, 5},
		},
		{
			name: "With two priority labels",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, []string{"priority/P1", "priority/P0"}, true),
				*github_test.Issue(whitelistUser, 3, []string{"priority/P1"}, true),
				*github_test.Issue(whitelistUser, 4, []string{"priority/P0"}, true),
				*github_test.Issue(whitelistUser, 5, nil, true),
			},
			expected: []int{2, 4, 3, 5},
		},
		{
			name: "With unrelated labels",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, []string{"priority/P1", "priority/P0"}, true),
				*github_test.Issue(whitelistUser, 3, []string{"priority/P1", "kind/design"}, true),
				*github_test.Issue(whitelistUser, 4, []string{"priority/P0"}, true),
				*github_test.Issue(whitelistUser, 5, []string{"LGTM", "kind/new-api"}, true),
			},
			expected: []int{2, 4, 3, 5},
		},
		{
			name: "With invalid priority label",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, []string{"priority/P1", "priority/P0"}, true),
				*github_test.Issue(whitelistUser, 3, []string{"priority/P1", "kind/design", "priority/high"}, true),
				*github_test.Issue(whitelistUser, 4, []string{"priority/P0", "priorty/bob"}, true),
				*github_test.Issue(whitelistUser, 5, nil, true),
			},
			expected: []int{2, 4, 3, 5},
		},
		{
			name: "Unlabeled counts as P3",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, nil, true),
				*github_test.Issue(whitelistUser, 3, []string{"priority/P3"}, true),
				*github_test.Issue(whitelistUser, 4, []string{"priority/P2"}, true),
				*github_test.Issue(whitelistUser, 5, nil, true),
			},
			expected: []int{4, 2, 3, 5},
		},
		{
			name: "e2e-not-required counts as P-negative 1",
			issues: []github.Issue{
				*github_test.Issue(whitelistUser, 2, nil, true),
				*github_test.Issue(whitelistUser, 3, []string{"priority/P3"}, true),
				*github_test.Issue(whitelistUser, 4, []string{"priority/P2"}, true),
				*github_test.Issue(whitelistUser, 5, nil, true),
				*github_test.Issue(whitelistUser, 6, []string{"priority/P3", e2eNotRequiredLabel}, true),
			},
			expected: []int{6, 4, 2, 3, 5},
		},
	}
	for testNum, test := range tests {
		config := &github_util.Config{}
		client, server, mux := github_test.InitServer(t, nil, nil, nil, nil, nil)
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)
		sq := getTestSQ(false, config, server)
		for i := range test.issues {
			issue := &test.issues[i]
			github_test.ServeIssue(t, mux, issue)

			issueNum := *issue.Number
			obj, err := config.GetObject(issueNum)
			if err != nil {
				t.Fatalf("%d:%q unable to get issue: %v", testNum, test.name, err)
			}
			sq.githubE2EQueue[issueNum] = obj
		}
		actual := sq.orderedE2EQueue()
		if len(actual) != len(test.expected) {
			t.Fatalf("%d:%q len(actual):%v != len(expected):%v", testNum, test.name, actual, test.expected)
		}
		for i, a := range actual {
			e := test.expected[i]
			if a != e {
				t.Errorf("%d:%q a[%d]:%d != e[%d]:%d", testNum, test.name, i, a, i, e)
			}
		}
		server.Close()
	}
}
Example #15
0
func TestMunge(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name             string // because when the fail, counting is hard
		pr               *github.PullRequest
		issue            *github.Issue
		commits          []github.RepositoryCommit
		events           []github.IssueEvent
		ciStatus         *github.CombinedStatus
		jenkinsJob       jenkins.Job
		e2ePass          bool
		unitPass         bool
		mergeAfterQueued bool
		reason           string
		state            string // what the github status context should be for the PR HEAD
	}{
		// Should pass because the entire thing was run and good
		{
			name:       "Test1",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:   SuccessStatus(),
			jenkinsJob: SuccessJenkins(),
			e2ePass:    true,
			unitPass:   true,
			reason:     merged,
			state:      "success",
		},
		// Should list as 'merged' but the merge should happen before it gets e2e tested
		// and we should bail early instead of waiting for a test that will never come.
		{
			name:       "Test2",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(),
			ciStatus:   SuccessStatus(),
			jenkinsJob: SuccessJenkins(),
			// The test should never run, but if it does, make sure it fails
			mergeAfterQueued: true,
			reason:           merged,
			state:            "success",
		},
		// Should merge even though github ci failed because of dont-require-e2e
		{
			name:       "Test3",
			pr:         ValidPR(),
			issue:      DontRequireGithubE2EIssue(),
			ciStatus:   GithubE2EFailStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			reason:     merged,
			state:      "success",
		},
		// Should merge even though user not in whitelist because has ok-to-merge
		{
			name:       "Test4",
			pr:         ValidPR(),
			issue:      UserNotInWhitelistOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			e2ePass:    true,
			unitPass:   true,
			reason:     merged,
			state:      "success",
		},
		// Fail because PR can't automatically merge
		{
			name:   "Test5",
			pr:     UnMergeablePR(),
			issue:  NoOKToMergeIssue(),
			reason: unmergeable,
			state:  "pending",
		},
		// Fail because we don't know if PR can automatically merge
		{
			name:   "Test6",
			pr:     UndeterminedMergeablePR(),
			issue:  NoOKToMergeIssue(),
			reason: undeterminedMergability,
			state:  "pending",
		},
		// Fail because the "cla: yes" label was not applied
		{
			name:   "Test7",
			pr:     ValidPR(),
			issue:  NoCLAIssue(),
			reason: noCLA,
			state:  "pending",
		},
		// Fail because github CI tests have failed (or at least are not success)
		{
			name:   "Test8",
			pr:     NonWhitelistUserPR(),
			issue:  NoOKToMergeIssue(),
			reason: ciFailure,
			state:  "pending",
		},
		// Fail because the user is not in the whitelist and we don't have "ok-to-merge"
		{
			name:     "Test9",
			pr:       ValidPR(),
			issue:    UserNotInWhitelistNoOKToMergeIssue(),
			ciStatus: SuccessStatus(),
			reason:   needsok,
			state:    "pending",
		},
		// Fail because missing LGTM label
		{
			name:     "Test10",
			pr:       ValidPR(),
			issue:    NoLGTMIssue(),
			ciStatus: SuccessStatus(),
			reason:   noLGTM,
			state:    "pending",
		},
		// Fail because we can't tell if LGTM was added before the last change
		{
			name:     "Test11",
			pr:       ValidPR(),
			issue:    NoOKToMergeIssue(),
			ciStatus: SuccessStatus(),
			reason:   unknown,
			state:    "failure",
		},
		// Fail because LGTM was added before the last change
		{
			name:     "Test12",
			pr:       ValidPR(),
			issue:    NoOKToMergeIssue(),
			ciStatus: SuccessStatus(),
			events:   OldLGTMEvents(),
			commits:  Commits(), // Modified at time.Unix(7), 8, and 9
			reason:   lgtmEarly,
			state:    "pending",
		},
		// Fail because jenkins instances are failing (whole submit queue blocks)
		{
			name:       "Test13",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: FailJenkins(),
			reason:     e2eFailure,
			state:      "success",
		},
		// Fail because the second run of github e2e tests failed
		{
			name:       "Test14",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(),
			jenkinsJob: SuccessJenkins(),
			reason:     ghE2EFailed,
			state:      "pending",
		},
		// When we check the reason it may be queued or it may already have failed.
		{
			name:       "Test15",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			reason:     ghE2EQueued,
			// The state is unpredictable. When it goes on the queue it is success.
			// When it fails the build it is pending. So state depends on how far along
			// this were when we checked. Thus just don't check it...
			state: "",
		},
		// Fail because the second run of github e2e tests failed
		{
			name:       "Test16",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			reason:     ghE2EFailed,
			state:      "pending",
		},
		{
			name:       "Fail because E2E pass, but unit test fail",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:   SuccessStatus(),
			jenkinsJob: SuccessJenkins(),
			e2ePass:    true,
			unitPass:   false,
			reason:     ghE2EFailed,
			state:      "pending",
		},
		{
			name:       "Fail because E2E fail, but unit test pass",
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:   SuccessStatus(),
			jenkinsJob: SuccessJenkins(),
			e2ePass:    false,
			unitPass:   true,
			reason:     ghE2EFailed,
			state:      "pending",
		},
	}
	for testNum, test := range tests {
		issueNum := testNum + 1
		issueNumStr := strconv.Itoa(issueNum)

		test.issue.Number = &issueNum
		client, server, mux := github_test.InitServer(t, test.issue, test.pr, test.events, test.commits, test.ciStatus)

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)
		// Don't wait so long for it to go pending or back
		d := 250 * time.Millisecond
		config.PendingWaitTime = &d

		stateSet := ""

		numJenkinsCalls := 0
		// Respond with success to jenkins requests.
		mux.HandleFunc("/job/foo/lastCompletedBuild/api/json", func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(test.jenkinsJob)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)

			// There is no good spot for this, but this gets called
			// before we queue the PR. So mark the PR as "merged".
			// When the sq initializes, it will check the Jenkins status,
			// so we don't want to modify the PR there. Instead we need
			// to wait until the second time we check Jenkins, which happens
			// we did the IsMerged() check.
			numJenkinsCalls = numJenkinsCalls + 1
			if numJenkinsCalls == 2 && test.mergeAfterQueued {
				test.pr.Merged = boolPtr(true)
				test.pr.Mergeable = nil
			}
		})
		path := fmt.Sprintf("/repos/o/r/issues/%d/comments", issueNum)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "POST" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			type comment struct {
				Body string `json:"body"`
			}
			c := new(comment)
			json.NewDecoder(r.Body).Decode(c)
			msg := c.Body
			if strings.HasPrefix(msg, "@k8s-bot test this") {
				go fakeRunGithubE2ESuccess(test.ciStatus, test.e2ePass, test.unitPass)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(github.IssueComment{})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})
		path = fmt.Sprintf("/repos/o/r/pulls/%d/merge", issueNum)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "PUT" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(github.PullRequestMergeResult{})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
			test.pr.Merged = boolPtr(true)
		})
		path = fmt.Sprintf("/repos/o/r/statuses/%s", *test.pr.Head.SHA)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "POST" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			decoder := json.NewDecoder(r.Body)
			var status github.RepoStatus
			err := decoder.Decode(&status)
			if err != nil {
				t.Errorf("Unable to decode status: %v", err)
			}

			stateSet = *status.State

			data, err := json.Marshal(status)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.WriteHeader(http.StatusOK)
			w.Write(data)
			test.pr.Merged = boolPtr(true)
		})

		sq := SubmitQueue{}
		sq.RequiredStatusContexts = []string{jenkinsUnitContext}
		sq.E2EStatusContext = jenkinsE2EContext
		sq.UnitStatusContext = jenkinsUnitContext
		sq.JenkinsHost = server.URL
		sq.JenkinsJobs = []string{"foo"}
		sq.WhitelistOverride = "ok-to-merge"
		sq.Initialize(config)
		sq.EachLoop()
		sq.userWhitelist.Insert(whitelistUser)

		obj := github_util.TestObject(config, test.issue, test.pr, test.commits, test.events)
		sq.Munge(obj)
		done := make(chan bool, 1)
		go func(done chan bool) {
			for {
				if sq.prStatus[issueNumStr].Reason == test.reason {
					done <- true
					return
				}
				found := false
				for _, status := range sq.statusHistory {
					if status.Number == issueNum && status.Reason == test.reason {
						found = true
						break
					}
				}
				if found {
					done <- true
					return
				}
				time.Sleep(1 * time.Millisecond)
			}
		}(done)
		select {
		case <-done:
		case <-time.After(10 * time.Second):
			t.Errorf("%d:%s timed out waiting expected reason=%q but got prStatus:%q history:%v", testNum, test.name, test.reason, sq.prStatus[issueNumStr].Reason, sq.statusHistory)
		}
		close(done)
		server.Close()

		if test.state != "" && test.state != stateSet {
			t.Errorf("%d:%s state set to %q but expected %q", testNum, test.name, stateSet, test.state)
		}
	}
}
func TestAddLGTMIfCommented(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name        string
		comments    []*github.IssueComment
		issue       *github.Issue
		assignees   mungerutil.UserSet
		mustHave    []string
		mustNotHave []string
	}{
		{
			name:  "Other comments should not add LGTM.",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/comment 1", "user 1", 0),
				github_test.IssueComment(2, "/comment 2 //comment3", "user 2", 1),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 1")),
			mustHave:    []string{},
			mustNotHave: []string{lgtmLabel},
		},
		{
			name:  "/lgtm by non-assignee should not add LGTM label",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/lgtm", "user 1", 0),
				github_test.IssueComment(2, "comment 2", "user 2", 1),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 2")),
			mustHave:    []string{},
			mustNotHave: []string{lgtmLabel},
		},
		{
			name:  "/lgtm by assignee should add LGTM label",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/lgtm", "user 1", 0),
				github_test.IssueComment(2, "comment 2", "user 2", 1),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 1")),
			mustHave:    []string{lgtmLabel},
			mustNotHave: []string{},
		},
		{
			name:  "/lgtm by assignee followed by cancellation by non-assignee should add lgtm",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/lgtm", "user 1", 0),
				github_test.IssueComment(2, "/lgtm cancel", "user 2", 1),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 1")),
			mustHave:    []string{lgtmLabel},
			mustNotHave: []string{},
		},
		{
			name:  "/lgtm by assignee followed by /lgtm cancel should not add lgtm",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/lgtm", "user 1", 0),
				github_test.IssueComment(2, "/lgtm cancel", "user 2", 1),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 1", "user 2")),
			mustHave:    []string{},
			mustNotHave: []string{lgtmLabel},
		},
		{
			name:  "/lgtm followed by comment should be honored",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/lgtm //this is a comment", "user 1", 0),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 1")),
			mustHave:    []string{lgtmLabel},
			mustNotHave: []string{},
		},
		{
			name:  "/lgtm cancel by bot should be honored",
			issue: prWithoutLGTM,
			comments: []*github.IssueComment{
				github_test.IssueComment(1, "/lgtm //this is a comment", "user 1", 0),
				github_test.IssueComment(1, "/lgtm cancel //this is a bot", botName, 0),
			},
			assignees:   mungerutil.UserSet(sets.NewString("user 1")),
			mustHave:    []string{},
			mustNotHave: []string{lgtmLabel},
		},
	}

	for testNum, test := range tests {
		pr := ValidPR()
		client, server, mux := github_test.InitServer(t, test.issue, pr, nil, nil, nil, nil, nil)
		path := fmt.Sprintf("/repos/o/r/issue/%s/labels", *test.issue.Number)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			out := []github.Label{{}}
			data, err := json.Marshal(out)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		l := LGTMHandler{}
		obj, err := config.GetObject(*test.issue.Number)
		if err != nil {
			t.Fatalf("%v", err)
		}

		l.addLGTMIfCommented(obj, test.comments, test.assignees)
		for _, lab := range test.mustHave {
			if !obj.HasLabel(lab) {
				t.Errorf("%s:%d: Did not find label %q, labels: %v", test.name, testNum, lab, obj.Issue.Labels)
			}
		}
		for _, lab := range test.mustNotHave {
			if obj.HasLabel(lab) {
				t.Errorf("%s:%d: Found label %q and should not have, labels: %v", test.name, testNum, lab, obj.Issue.Labels)
			}
		}
		server.Close()
	}
}
func TestOldUnitTestMunge(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		name     string
		tested   bool
		ciStatus *github.CombinedStatus
	}{
		{
			name:     "Test0",
			tested:   true,
			ciStatus: SuccessStatus(), // Ran at time.Unix(0,0)
		},
		{
			name:     "Test1",
			tested:   false,
			ciStatus: NowStatus(), // Ran at time.Unix(0,0)
		},
	}
	for testNum, test := range tests {
		issueNum := testNum + 1
		tested := false

		issue := NoOKToMergeIssue()
		issue.Number = intPtr(issueNum)
		pr := ValidPR()
		pr.Number = intPtr(issueNum)
		client, server, mux := github_test.InitServer(t, issue, pr, nil, nil, test.ciStatus)

		path := fmt.Sprintf("/repos/o/r/issues/%d/comments", issueNum)
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "POST" {
				t.Errorf("Unexpected method: %s", r.Method)
			}

			type comment struct {
				Body string `json:"body"`
			}
			c := new(comment)
			json.NewDecoder(r.Body).Decode(c)
			msg := c.Body
			if strings.HasPrefix(msg, "@"+jenkinsBotName+" test this") {
				tested = true
				test.ciStatus.State = stringPtr("pending")
				for id := range test.ciStatus.Statuses {
					status := &test.ciStatus.Statuses[id]
					if *status.Context == jenkinsE2EContext || *status.Context == jenkinsUnitContext {
						status.State = stringPtr("pending")
						break
					}
				}

			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(github.IssueComment{})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)

		s := StaleGreenCI{}
		err := s.Initialize(config, nil)
		if err != nil {
			t.Fatalf("%v", err)
		}

		obj, err := config.GetObject(issueNum)
		if err != nil {
			t.Fatalf("%v", err)
		}

		s.Munge(obj)

		if tested != test.tested {
			t.Errorf("%d:%s tested=%t but should be %t", testNum, test.name, tested, test.tested)
		}
		server.Close()
	}
}
func TestMunge(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tests := []struct {
		pr               *github.PullRequest
		issue            *github.Issue
		commits          []github.RepositoryCommit
		events           []github.IssueEvent
		ciStatus         *github.CombinedStatus
		jenkinsJob       jenkins.Job
		shouldPass       bool
		mergeAfterQueued bool
		reasons          []string
	}{
		// Should pass because the entire thing was run and good
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			ciStatus:   SuccessStatus(),
			jenkinsJob: SuccessJenkins(),
			shouldPass: true,
			reasons:    []string{merged},
		},
		// Should list as 'merged' but the merge should happen before it gets e2e tested
		// and we should bail early instead of waiting for a test that will never come.
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(),
			ciStatus:   SuccessStatus(),
			jenkinsJob: SuccessJenkins(),
			// The test should never run, but if it does, make sure it fails
			shouldPass:       false,
			mergeAfterQueued: true,
			reasons:          []string{merged},
		},
		// Should merge even though github ci failed because of dont-require-e2e
		{
			pr:         ValidPR(),
			issue:      DontRequireGithubE2EIssue(),
			ciStatus:   GithubE2EFailStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			reasons:    []string{merged},
		},
		// Should merge even though user not in whitelist because has ok-to-merge
		{
			pr:         ValidPR(),
			issue:      UserNotInWhitelistOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			shouldPass: true,
			reasons:    []string{merged},
		},
		// Fail because PR can't automatically merge
		{
			pr:      UnMergeablePR(),
			issue:   NoOKToMergeIssue(),
			reasons: []string{unmergeable},
		},
		// Fail because we don't know if PR can automatically merge
		{
			pr:      UndeterminedMergeablePR(),
			issue:   NoOKToMergeIssue(),
			reasons: []string{undeterminedMergability},
		},
		// Fail because the "cla: yes" label was not applied
		{
			pr:      ValidPR(),
			issue:   NoCLAIssue(),
			reasons: []string{noCLA},
		},
		// Fail because github CI tests have failed (or at least are not success)
		{
			pr:      NonWhitelistUserPR(),
			issue:   NoOKToMergeIssue(),
			reasons: []string{ciFailure},
		},
		// Fail because the user is not in the whitelist and we don't have "ok-to-merge"
		{
			pr:       ValidPR(),
			issue:    UserNotInWhitelistNoOKToMergeIssue(),
			ciStatus: SuccessStatus(),
			reasons:  []string{needsok},
		},
		// Fail because missing LGTM label
		{
			pr:       ValidPR(),
			issue:    NoLGTMIssue(),
			ciStatus: SuccessStatus(),
			reasons:  []string{noLGTM},
		},
		// Fail because we can't tell if LGTM was added before the last change
		{
			pr:       ValidPR(),
			issue:    NoOKToMergeIssue(),
			ciStatus: SuccessStatus(),
			reasons:  []string{unknown},
		},
		// Fail because LGTM was added before the last change
		{
			pr:       ValidPR(),
			issue:    NoOKToMergeIssue(),
			ciStatus: SuccessStatus(),
			events:   OldLGTMEvents(),
			commits:  Commits(), // Modified at time.Unix(7), 8, and 9
			reasons:  []string{lgtmEarly},
		},
		// Fail because jenkins instances are failing (whole submit queue blocks)
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: FailJenkins(),
			reasons:    []string{e2eFailure},
		},
		// Fail because the second run of github e2e tests failed
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(),
			jenkinsJob: SuccessJenkins(),
			reasons:    []string{ghE2EFailed},
		},
		// Should pass because the jenkins ci is green even tho shippable is pending.
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(),
			ciStatus:   JenkinsCIGreenShippablePendingStatus(),
			jenkinsJob: SuccessJenkins(),
			shouldPass: true,
			reasons:    []string{merged},
		},
		// Should pass because the shippable is green (no jenkins ci).
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			events:     NewLGTMEvents(),
			commits:    Commits(),
			ciStatus:   ShippableGreenStatus(),
			jenkinsJob: SuccessJenkins(),
			shouldPass: true,
			reasons:    []string{merged},
		},
		// When we check the reason it may be queued or it may already have failed.
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			shouldPass: false,
			reasons:    []string{ghE2EQueued, ghE2EFailed},
		},
		// Fail because the second run of github e2e tests failed
		{
			pr:         ValidPR(),
			issue:      NoOKToMergeIssue(),
			ciStatus:   SuccessStatus(),
			events:     NewLGTMEvents(),
			commits:    Commits(), // Modified at time.Unix(7), 8, and 9
			jenkinsJob: SuccessJenkins(),
			shouldPass: false,
			reasons:    []string{ghE2EFailed},
		},
	}
	for testNum, test := range tests {
		client, server, mux := github_test.InitServer(t, test.issue, test.pr, test.events, test.commits, test.ciStatus)

		config := &github_util.Config{}
		config.Org = "o"
		config.Project = "r"
		config.SetClient(client)
		// Don't wait so long for it to go pending or back
		d := 250 * time.Millisecond
		config.PendingWaitTime = &d

		numJenkinsCalls := 0
		// Respond with success to jenkins requests.
		mux.HandleFunc("/job/foo/lastCompletedBuild/api/json", func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(test.jenkinsJob)
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)

			// There is no good spot for this, but this gets called
			// before we queue the PR. So mark the PR as "merged".
			// When the sq initializes, it will check the Jenkins status,
			// so we don't want to modify the PR there. Instead we need
			// to wait until the second time we check Jenkins, which happens
			// we did the IsMerged() check.
			numJenkinsCalls = numJenkinsCalls + 1
			if numJenkinsCalls == 2 && test.mergeAfterQueued {
				test.pr.Merged = boolPtr(true)
				test.pr.Mergeable = nil
			}
		})
		mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "POST" {
				t.Errorf("Unexpected method: %s", r.Method)
			}

			type comment struct {
				Body string `json:"body"`
			}
			c := new(comment)
			json.NewDecoder(r.Body).Decode(c)
			msg := c.Body
			if strings.HasPrefix(msg, "@k8s-bot test this") {
				go fakeRunGithubE2ESuccess(test.ciStatus, test.shouldPass)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(github.IssueComment{})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
		})
		mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "PUT" {
				t.Errorf("Unexpected method: %s", r.Method)
			}
			w.WriteHeader(http.StatusOK)
			data, err := json.Marshal(github.PullRequestMergeResult{})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			w.Write(data)
			test.pr.Merged = boolPtr(true)
		})

		sq := SubmitQueue{}
		sq.RequiredStatusContexts = []string{claContext}
		sq.DontRequireE2ELabel = "e2e-not-required"
		sq.E2EStatusContext = gceE2EContext
		sq.JenkinsHost = server.URL
		sq.JenkinsJobs = []string{"foo"}
		sq.WhitelistOverride = "ok-to-merge"
		sq.Initialize(config)
		sq.EachLoop()
		sq.userWhitelist.Insert(whitelistUser)

		obj := github_util.TestObject(config, test.issue, test.pr, test.commits, test.events)
		sq.Munge(obj)
		done := make(chan bool, 1)
		go func(done chan bool) {
			for {
				reason := sq.prStatus["1"].Reason
				for _, r := range test.reasons {
					if r == reason {
						done <- true
						return
					}
				}
				time.Sleep(1 * time.Millisecond)
			}
		}(done)
		select {
		case <-done:
		case <-time.After(10 * time.Second):
			t.Fatalf("test:%d timed out waiting expected reason=%v but got %q", testNum, test.reasons, sq.prStatus["1"].Reason)
		}
		server.Close()
	}
}