blob: 5a599d82fa5b6ef1974ec8402598cf2c76e26eaf [file] [log] [blame]
package codereview
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"testing"
"time"
assert "github.com/stretchr/testify/require"
"go.skia.org/infra/autoroll/go/recent_rolls"
"go.skia.org/infra/go/autoroll"
"go.skia.org/infra/go/buildbucket"
"go.skia.org/infra/go/ds"
"go.skia.org/infra/go/ds/testutil"
"go.skia.org/infra/go/gerrit"
gerrit_testutils "go.skia.org/infra/go/gerrit/testutils"
"go.skia.org/infra/go/mockhttpclient"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/testutils/unittest"
)
func makeFakeRoll(issueNum int64, from, to string, dryRun, android bool) (*gerrit.ChangeInfo, *autoroll.AutoRollIssue) {
// Gerrit API only has millisecond precision.
now := time.Now().UTC().Round(time.Millisecond)
description := fmt.Sprintf(`Roll src/third_party/skia/ %s..%s (42 commits).
blah blah
TBR=some-sheriff
`, from[:12], to[:12])
rev := &gerrit.Revision{
ID: "1",
Number: 1,
CreatedString: now.Format(gerrit.TIME_FORMAT),
Created: now,
}
cqLabel := gerrit.COMMITQUEUE_LABEL_SUBMIT
if dryRun {
if android {
cqLabel = gerrit.AUTOSUBMIT_LABEL_NONE
} else {
cqLabel = gerrit.COMMITQUEUE_LABEL_DRY_RUN
}
}
roll := &gerrit.ChangeInfo{
Created: now,
CreatedString: now.Format(gerrit.TIME_FORMAT),
Subject: description,
ChangeId: fmt.Sprintf("%d", issueNum),
Issue: issueNum,
Owner: &gerrit.Owner{
Email: "fake-deps-roller@chromium.org",
},
Project: "skia",
Revisions: map[string]*gerrit.Revision{
"1": rev,
},
Patchsets: []*gerrit.Revision{rev},
Updated: now,
UpdatedString: now.Format(gerrit.TIME_FORMAT),
}
if android {
roll.Labels = map[string]*gerrit.LabelEntry{
gerrit.PRESUBMIT_VERIFIED_LABEL: {
All: []*gerrit.LabelDetail{},
},
gerrit.AUTOSUBMIT_LABEL: {
All: []*gerrit.LabelDetail{
{
Value: gerrit.AUTOSUBMIT_LABEL_SUBMIT,
},
},
},
}
} else {
roll.Labels = map[string]*gerrit.LabelEntry{
gerrit.CODEREVIEW_LABEL: {
All: []*gerrit.LabelDetail{
{
Value: gerrit.CODEREVIEW_LABEL_APPROVE,
},
},
},
gerrit.COMMITQUEUE_LABEL: {
All: []*gerrit.LabelDetail{
{
Value: cqLabel,
},
},
},
}
}
return roll, &autoroll.AutoRollIssue{
Issue: issueNum,
RollingFrom: from,
RollingTo: to,
}
}
func TestGerritRoll(t *testing.T) {
unittest.LargeTest(t)
tmp, err := ioutil.TempDir("", "")
assert.NoError(t, err)
defer testutils.RemoveAll(t, tmp)
testutil.InitDatastore(t, ds.KIND_AUTOROLL_ROLL)
g := gerrit_testutils.NewGerrit(t, tmp, false)
ctx := context.Background()
recent, err := recent_rolls.NewRecentRolls(ctx, "test-roller")
assert.NoError(t, err)
// Upload and retrieve the roll.
from := "abcde12345abcde12345abcde12345abcde12345"
to := "fghij67890fghij67890fghij67890fghij67890"
ci, issue := makeFakeRoll(123, from, to, false, false)
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
gr, err := newGerritRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.False(t, gr.IsFinished())
assert.False(t, gr.IsSuccess())
g.AssertEmpty()
assert.Equal(t, to, gr.RollingTo())
// Insert into DB.
current := recent.CurrentRoll()
assert.Nil(t, current)
assert.NoError(t, gr.InsertIntoDB(ctx))
current = recent.CurrentRoll()
assert.NotNil(t, current)
assert.Equal(t, current.Issue, ci.Issue)
g.AssertEmpty()
// Add a comment.
msg := "Here's a comment"
g.MockAddComment(ci, msg)
assert.NoError(t, gr.AddComment(ctx, msg))
g.AssertEmpty()
// Set dry run.
g.MockSetDryRun(ci, "Mode was changed to dry run")
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.SwitchToDryRun(ctx))
g.AssertEmpty()
// Set normal.
g.MockSetCQ(ci, "Mode was changed to normal")
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.SwitchToNormal(ctx))
g.AssertEmpty()
// Update.
ci.Status = gerrit.CHANGE_STATUS_MERGED
// Landing a change adds an empty patchset.
rev := &gerrit.Revision{
Number: int64(len(ci.Revisions) + 1),
Created: time.Now(),
Kind: "",
}
ci.Revisions[fmt.Sprintf("%d", rev.Number)] = rev
ci.Patchsets = append(ci.Patchsets, rev)
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.Update(ctx))
assert.True(t, gr.IsFinished())
assert.True(t, gr.IsSuccess())
assert.Nil(t, recent.CurrentRoll())
// Upload and retrieve another roll, dry run this time.
ci, issue = makeFakeRoll(124, from, to, true, false)
g.MockGetIssueProperties(ci)
tryjob := &buildbucket.Build{
Id: "99999",
Created: time.Now().UTC().Round(time.Millisecond),
Status: autoroll.TRYBOT_STATUS_STARTED,
Parameters: &buildbucket.Parameters{
BuilderName: "fake-builder",
Properties: buildbucket.Properties{
Category: "cq",
GerritPatchset: "1",
},
},
}
g.MockGetTrybotResults(ci, 1, []*buildbucket.Build{tryjob})
gr, err = newGerritRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.False(t, gr.IsDryRunFinished())
assert.False(t, gr.IsDryRunSuccess())
g.AssertEmpty()
assert.Equal(t, to, gr.RollingTo())
// Insert into DB.
current = recent.CurrentRoll()
assert.Nil(t, current)
assert.NoError(t, gr.InsertIntoDB(ctx))
current = recent.CurrentRoll()
assert.NotNil(t, current)
assert.Equal(t, current.Issue, ci.Issue)
g.AssertEmpty()
// Success.
tryjob.Status = autoroll.TRYBOT_STATUS_COMPLETED
tryjob.Result = autoroll.TRYBOT_RESULT_SUCCESS
ci.Labels[gerrit.COMMITQUEUE_LABEL] = &gerrit.LabelEntry{
All: []*gerrit.LabelDetail{
{
Value: gerrit.COMMITQUEUE_LABEL_NONE,
},
},
}
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, []*buildbucket.Build{tryjob})
assert.NoError(t, gr.Update(ctx))
assert.True(t, gr.IsDryRunFinished())
assert.True(t, gr.IsDryRunSuccess())
g.AssertEmpty()
// Close for cleanup.
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, []*buildbucket.Build{tryjob})
assert.NoError(t, gr.Update(ctx))
// Verify that all of the mutation functions handle a conflict (eg.
// someone closed the CL) gracefully.
// 1. SwitchToDryRun.
ci, issue = makeFakeRoll(125, from, to, false, false)
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
gr, err = newGerritRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url, reqBytes := g.MakePostRequest(ci, "Mode was changed to dry run", map[string]int{
gerrit.COMMITQUEUE_LABEL: gerrit.COMMITQUEUE_LABEL_DRY_RUN,
})
g.Mock.MockOnce(url, mockhttpclient.MockPostError("application/json", reqBytes, "CONFLICT", http.StatusConflict))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.SwitchToDryRun(ctx))
g.AssertEmpty()
// 2. SwitchToNormal
ci, issue = makeFakeRoll(126, from, to, false, false)
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
gr, err = newGerritRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url, reqBytes = g.MakePostRequest(ci, "Mode was changed to normal", map[string]int{
gerrit.COMMITQUEUE_LABEL: gerrit.COMMITQUEUE_LABEL_SUBMIT,
})
g.Mock.MockOnce(url, mockhttpclient.MockPostError("application/json", reqBytes, "CONFLICT", http.StatusConflict))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.SwitchToNormal(ctx))
g.AssertEmpty()
// 3. Close.
ci, issue = makeFakeRoll(127, from, to, false, false)
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
gr, err = newGerritRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url = fmt.Sprintf("%s/a/changes/%d/abandon", gerrit_testutils.FAKE_GERRIT_URL, ci.Issue)
req := testutils.MarshalJSON(t, &struct {
Message string `json:"message"`
}{
Message: "close it!",
})
g.Mock.MockOnce(url, mockhttpclient.MockPostError("application/json", []byte(req), "CONFLICT", http.StatusConflict))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.Close(ctx, autoroll.ROLL_RESULT_FAILURE, "close it!"))
g.AssertEmpty()
// Verify that we set the correct status when abandoning a CL.
ci, issue = makeFakeRoll(128, from, to, false, false)
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
gr, err = newGerritRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url = fmt.Sprintf("%s/a/changes/%d/abandon", gerrit_testutils.FAKE_GERRIT_URL, ci.Issue)
req = testutils.MarshalJSON(t, &struct {
Message string `json:"message"`
}{
Message: "close it!",
})
g.Mock.MockOnce(url, mockhttpclient.MockPostDialogue("application/json", []byte(req), nil))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
g.MockGetTrybotResults(ci, 1, nil)
assert.NoError(t, gr.Close(ctx, autoroll.ROLL_RESULT_DRY_RUN_SUCCESS, "close it!"))
g.AssertEmpty()
issue, err = recent.Get(ctx, 128)
assert.NoError(t, err)
assert.Equal(t, issue.Result, autoroll.ROLL_RESULT_DRY_RUN_SUCCESS)
}
func TestGerritAndroidRoll(t *testing.T) {
unittest.LargeTest(t)
tmp, err := ioutil.TempDir("", "")
assert.NoError(t, err)
defer testutils.RemoveAll(t, tmp)
testutil.InitDatastore(t, ds.KIND_AUTOROLL_ROLL)
g := gerrit_testutils.NewGerrit(t, tmp, true)
ctx := context.Background()
recent, err := recent_rolls.NewRecentRolls(ctx, "test-roller")
assert.NoError(t, err)
// Upload and retrieve the roll.
from := "abcde12345abcde12345abcde12345abcde12345"
to := "fghij67890fghij67890fghij67890fghij67890"
ci, issue := makeFakeRoll(123, from, to, false, true)
g.MockGetIssueProperties(ci)
gr, err := newGerritAndroidRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.False(t, gr.IsFinished())
assert.False(t, gr.IsSuccess())
g.AssertEmpty()
assert.Equal(t, to, gr.RollingTo())
// Insert into DB.
current := recent.CurrentRoll()
assert.Nil(t, current)
assert.NoError(t, gr.InsertIntoDB(ctx))
current = recent.CurrentRoll()
assert.NotNil(t, current)
assert.Equal(t, current.Issue, ci.Issue)
g.AssertEmpty()
// Add a comment.
msg := "Here's a comment"
g.MockAddComment(ci, msg)
assert.NoError(t, gr.AddComment(ctx, msg))
g.AssertEmpty()
// Set dry run.
g.MockSetDryRun(ci, "Mode was changed to dry run")
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.SwitchToDryRun(ctx))
g.AssertEmpty()
// Set normal.
g.MockSetCQ(ci, "Mode was changed to normal")
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.SwitchToNormal(ctx))
g.AssertEmpty()
// Update.
ci.Status = gerrit.CHANGE_STATUS_MERGED
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.Update(ctx))
assert.True(t, gr.IsFinished())
assert.True(t, gr.IsSuccess())
assert.Nil(t, recent.CurrentRoll())
// Upload and retrieve another roll, dry run this time.
ci, issue = makeFakeRoll(124, from, to, true, true)
g.MockGetIssueProperties(ci)
gr, err = newGerritAndroidRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.False(t, gr.IsDryRunFinished())
assert.False(t, gr.IsDryRunSuccess())
g.AssertEmpty()
assert.Equal(t, to, gr.RollingTo())
// Insert into DB.
current = recent.CurrentRoll()
assert.Nil(t, current)
assert.NoError(t, gr.InsertIntoDB(ctx))
current = recent.CurrentRoll()
assert.NotNil(t, current)
assert.Equal(t, current.Issue, ci.Issue)
g.AssertEmpty()
// Success.
ci.Labels[gerrit.PRESUBMIT_VERIFIED_LABEL] = &gerrit.LabelEntry{
All: []*gerrit.LabelDetail{
{
Value: gerrit.PRESUBMIT_VERIFIED_LABEL_ACCEPTED,
},
},
}
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.Update(ctx))
assert.True(t, gr.IsDryRunFinished())
assert.True(t, gr.IsDryRunSuccess())
g.AssertEmpty()
// Close for cleanup.
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.Update(ctx))
// Verify that all of the mutation functions handle a conflict (eg.
// someone closed the CL) gracefully.
// 1. SwitchToDryRun.
ci, issue = makeFakeRoll(125, from, to, false, true)
g.MockGetIssueProperties(ci)
gr, err = newGerritAndroidRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url, reqBytes := g.MakePostRequest(ci, "Mode was changed to dry run", map[string]int{
gerrit.AUTOSUBMIT_LABEL: gerrit.AUTOSUBMIT_LABEL_NONE,
})
g.Mock.MockOnce(url, mockhttpclient.MockPostError("application/json", reqBytes, "CONFLICT", http.StatusConflict))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.SwitchToDryRun(ctx))
g.AssertEmpty()
// 2. SwitchToNormal
ci, issue = makeFakeRoll(126, from, to, false, true)
g.MockGetIssueProperties(ci)
gr, err = newGerritAndroidRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url, reqBytes = g.MakePostRequest(ci, "Mode was changed to normal", map[string]int{
gerrit.AUTOSUBMIT_LABEL: gerrit.AUTOSUBMIT_LABEL_SUBMIT,
})
g.Mock.MockOnce(url, mockhttpclient.MockPostError("application/json", reqBytes, "CONFLICT", http.StatusConflict))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.SwitchToNormal(ctx))
g.AssertEmpty()
// 3. Close.
ci, issue = makeFakeRoll(127, from, to, false, true)
g.MockGetIssueProperties(ci)
gr, err = newGerritAndroidRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url = fmt.Sprintf("%s/a/changes/%d/abandon", gerrit_testutils.FAKE_GERRIT_URL, ci.Issue)
req := testutils.MarshalJSON(t, &struct {
Message string `json:"message"`
}{
Message: "close it!",
})
g.Mock.MockOnce(url, mockhttpclient.MockPostError("application/json", []byte(req), "CONFLICT", http.StatusConflict))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.Close(ctx, autoroll.ROLL_RESULT_FAILURE, "close it!"))
g.AssertEmpty()
// Verify that we set the correct status when abandoning a CL.
ci, issue = makeFakeRoll(128, from, to, false, true)
g.MockGetIssueProperties(ci)
gr, err = newGerritAndroidRoll(ctx, issue, g.Gerrit, recent, "http://issue/", nil)
assert.NoError(t, err)
assert.NoError(t, gr.InsertIntoDB(ctx))
url = fmt.Sprintf("%s/a/changes/%d/abandon", gerrit_testutils.FAKE_GERRIT_URL, ci.Issue)
req = testutils.MarshalJSON(t, &struct {
Message string `json:"message"`
}{
Message: "close it!",
})
g.Mock.MockOnce(url, mockhttpclient.MockPostDialogue("application/json", []byte(req), nil))
ci.Status = gerrit.CHANGE_STATUS_ABANDONED
g.MockGetIssueProperties(ci)
assert.NoError(t, gr.Close(ctx, autoroll.ROLL_RESULT_DRY_RUN_SUCCESS, "close it!"))
g.AssertEmpty()
issue, err = recent.Get(ctx, 128)
assert.NoError(t, err)
assert.Equal(t, issue.Result, autoroll.ROLL_RESULT_DRY_RUN_SUCCESS)
}