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() } }
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() } }
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() } }
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() } }
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() } }
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() } }
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() } }
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() } }