blob: 81fef9aa8f421ac6860cc817d13ce361c824d04b [file] [log] [blame]
package backends
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/golang/mock/gomock"
"go.chromium.org/luci/grpc/appstatus"
"go.skia.org/infra/go/buildbucket"
. "github.com/smartystreets/goconvey/convey"
bpb "go.chromium.org/luci/buildbucket/proto"
. "go.chromium.org/luci/common/testing/assertions"
spb "google.golang.org/protobuf/types/known/structpb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
const (
TestHost = "chromium-review.googlesource.com"
)
func TestBuildbucketClientConfigs(t *testing.T) {
Convey(`OK`, t, func() {
Convey(`Defaults`, func() {
c := DefaultClientConfig()
So(c.Host, ShouldEqual, buildbucket.DEFAULT_HOST)
So(c.Retries, ShouldEqual, DefaultRetries)
So(c.PerRPCTimeout, ShouldEqual, DefaultPerRPCTimeout)
})
})
}
func createGerritChange(host, project string, change, patchset int64) *bpb.GerritChange {
return &bpb.GerritChange{
Host: host,
Project: project,
Change: change,
Patchset: patchset,
}
}
func createBuild(id int64, status bpb.Status, endTime *timestamppb.Timestamp, bucket, builder string, patches []*bpb.GerritChange) *bpb.Build {
return &bpb.Build{
Id: id,
Status: status,
// This build finished 31 days from runtime, just outside Cas.
EndTime: endTime,
Builder: &bpb.BuilderID{
Project: ChromeProject,
Bucket: bucket,
Builder: builder,
},
Input: &bpb.Build_Input{
GerritChanges: patches,
},
}
}
func TestCancelBuild(t *testing.T) {
ctx := context.Background()
buildID := int64(12345)
summary := "no longer needed"
Convey(`OK`, t, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := &bpb.CancelBuildRequest{
Id: buildID,
SummaryMarkdown: summary,
}
mbc.EXPECT().CancelBuild(ctx, req).Return(nil, nil)
err := c.CancelBuild(ctx, buildID, summary)
So(err, ShouldBeNil)
})
Convey(`Err`, t, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := &bpb.CancelBuildRequest{
Id: buildID,
SummaryMarkdown: summary,
}
resp := appstatus.BadRequest(errors.New("random error"))
mbc.EXPECT().CancelBuild(ctx, req).Return(nil, resp)
err := c.CancelBuild(ctx, buildID, summary)
So(err, ShouldErrLike, "Failed to cancel build 12345")
})
}
func TestGetBuildWithDeps(t *testing.T) {
t.Parallel()
ctx := context.Background()
builder := "builder"
commit := "12345"
c1ps1 := createGerritChange(TestHost, ChromeProject, 54321, 1)
expiredTime := timestamppb.New(time.Now().AddDate(0, -31, 0))
patches := []*bpb.GerritChange{c1ps1}
build1 := createBuild(1, bpb.Status_SUCCESS, expiredTime, DefaultBucket, builder, patches)
build2 := createBuild(2, bpb.Status_STARTED, nil, DefaultBucket, builder, patches)
Convey(`OK`, t, func() {
Convey(`E2E`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createSearchBuildRequest(builder, DefaultBucket, commit, patches)
So(req.Predicate.Builder.Project, ShouldEqual, ChromeProject)
// response in reverse chronological order.
resp := &bpb.SearchBuildsResponse{
Builds: []*bpb.Build{
build2,
build1,
},
}
mbc.EXPECT().SearchBuilds(ctx, req).Return(resp, nil)
build, err := c.GetBuildWithPatches(ctx, builder, DefaultBucket, commit, patches)
So(err, ShouldBeNil)
So(build.GetId(), ShouldEqual, 2)
So(build.Input.GerritChanges, ShouldResembleProto, patches)
})
})
Convey(`No Build Returned`, t, func() {
Convey(`Old Build`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createSearchBuildRequest(builder, DefaultBucket, commit, patches)
resp := &bpb.SearchBuildsResponse{
Builds: []*bpb.Build{
build1,
},
}
mbc.EXPECT().SearchBuilds(ctx, req).Return(resp, nil)
build, err := c.GetBuildWithPatches(ctx, builder, DefaultBucket, commit, patches)
So(err, ShouldBeNil)
So(build, ShouldBeNil)
})
Convey(`Recent Failed Build`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createSearchBuildRequest(builder, DefaultBucket, commit, patches)
// 1 day old
ts := timestamppb.New(time.Now().AddDate(0, 1, 0))
resp := &bpb.SearchBuildsResponse{
Builds: []*bpb.Build{
createBuild(1, bpb.Status_FAILURE, ts, DefaultBucket, builder, patches),
},
}
mbc.EXPECT().SearchBuilds(ctx, req).Return(resp, nil)
build, err := c.GetBuildWithPatches(ctx, builder, DefaultBucket, commit, patches)
So(err, ShouldBeNil)
So(build, ShouldBeNil)
})
Convey(`Nil patches`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createSearchBuildRequest(builder, DefaultBucket, commit, nil)
// 1 day old
ts := timestamppb.New(time.Now().AddDate(0, 1, 0))
resp := &bpb.SearchBuildsResponse{
Builds: []*bpb.Build{
createBuild(1, bpb.Status_FAILURE, ts, DefaultBucket, builder, nil),
},
}
mbc.EXPECT().SearchBuilds(ctx, req).Return(resp, nil)
build, err := c.GetBuildWithPatches(ctx, builder, DefaultBucket, commit, nil)
So(err, ShouldBeNil)
So(build, ShouldBeNil)
})
})
}
func TestGetBuildFromWaterfall(t *testing.T) {
t.Parallel()
ctx := context.Background()
builder := "Linux Builder Perf"
mirror, _ := PinpointWaterfall[builder]
commit := "12345"
// must be CI bucket, aka WaterfallBucket
ts := timestamppb.New(time.Now().AddDate(0, 1, 0))
build1 := createBuild(1, bpb.Status_SUCCESS, ts, WaterfallBucket, mirror, nil)
build2 := createBuild(2, bpb.Status_STARTED, nil, WaterfallBucket, mirror, nil)
Convey(`OK`, t, func() {
Convey(`E2E`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createSearchBuildRequest(mirror, WaterfallBucket, commit, nil)
// response in reverse chronological order.
resp := &bpb.SearchBuildsResponse{
Builds: []*bpb.Build{
build2,
build1,
},
}
mbc.EXPECT().SearchBuilds(ctx, req).Return(resp, nil)
build, err := c.GetBuildFromWaterfall(ctx, builder, commit)
So(err, ShouldBeNil)
So(build.GetId(), ShouldEqual, 2)
})
})
Convey(`Err`, t, func() {
Convey(`Unsupported Builder`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
b, err := c.GetBuildFromWaterfall(ctx, "builder", commit)
So(err, ShouldErrLike, "has no supported CI waterfall builder")
So(b, ShouldBeNil)
})
})
}
func TestGetBuildStatus(t *testing.T) {
t.Parallel()
ctx := context.Background()
buildID := int64(12345)
Convey(`OK`, t, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := &bpb.GetBuildStatusRequest{
Id: buildID,
}
resp := createBuild(buildID, bpb.Status_STARTED, nil, "try", "builder", nil)
mbc.EXPECT().GetBuildStatus(ctx, req).Return(resp, nil)
status, err := c.GetBuildStatus(ctx, buildID)
So(err, ShouldBeNil)
So(status, ShouldEqual, bpb.Status_STARTED)
})
Convey(`Err`, t, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := &bpb.GetBuildStatusRequest{
Id: buildID,
}
resp := appstatus.BadRequest(errors.New("random error"))
mbc.EXPECT().GetBuildStatus(ctx, req).Return(nil, resp)
status, err := c.GetBuildStatus(ctx, buildID)
So(err, ShouldErrLike, "random error")
So(status, ShouldEqual, bpb.Status_STATUS_UNSPECIFIED)
})
}
func createCASResponse(buildID int64, status bpb.Status, target, hash string) *bpb.Build {
return &bpb.Build{
Id: buildID,
Status: status,
Output: &bpb.Build_Output{
Properties: &spb.Struct{
Fields: map[string]*spb.Value{
// ignored
"foo": {},
// parsed
"swarm_hashes_refs/refs/head/main/without_patch": {
Kind: &spb.Value_StructValue{
StructValue: &spb.Struct{
Fields: map[string]*spb.Value{
target: {
Kind: &spb.Value_StringValue{
StringValue: hash,
},
},
},
},
},
},
},
},
},
}
}
func TestGetCASReference(t *testing.T) {
ctx := context.Background()
buildID := int64(12345)
target := "performance_test_suite"
Convey(`OK`, t, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createCASReferenceRequest(buildID)
So(req.Mask.Fields.Paths[0], ShouldEqual, "output.properties")
resp := createCASResponse(buildID, bpb.Status_SUCCESS, target, "somehash/123")
mbc.EXPECT().GetBuild(ctx, req).Return(resp, nil)
ref, err := c.GetCASReference(ctx, buildID, target)
So(err, ShouldBeNil)
So(ref.CasInstance, ShouldEqual, DefaultCASInstance)
So(ref.Digest.Hash, ShouldEqual, "somehash")
So(ref.Digest.SizeBytes, ShouldEqual, 123)
})
Convey(`Err`, t, func() {
Convey(`Non Successful Build`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createCASReferenceRequest(buildID)
resp := &bpb.Build{
Id: buildID,
Status: bpb.Status_STARTED,
}
mbc.EXPECT().GetBuild(ctx, req).Return(resp, nil)
ref, err := c.GetCASReference(ctx, buildID, target)
So(ref, ShouldBeNil)
So(err, ShouldErrLike, "Cannot fetch CAS information from build 12345 with status STARTED")
})
Convey(`Missing Target`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createCASReferenceRequest(buildID)
resp := createCASResponse(buildID, bpb.Status_SUCCESS, "other_test_suite", "somehash/123")
mbc.EXPECT().GetBuild(ctx, req).Return(resp, nil)
ref, err := c.GetCASReference(ctx, buildID, target)
So(ref, ShouldBeNil)
So(err, ShouldErrLike, "The target performance_test_suite cannot be found in the output properties")
})
Convey(`Wrong CAS Hash Format`, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createCASReferenceRequest(buildID)
wrongHash := "somehash/123/456"
resp := createCASResponse(buildID, bpb.Status_SUCCESS, target, wrongHash)
mbc.EXPECT().GetBuild(ctx, req).Return(resp, nil)
ref, err := c.GetCASReference(ctx, buildID, target)
So(ref, ShouldBeNil)
So(err, ShouldErrLike, fmt.Sprintf("CAS hash %s has been changed to an unparsable format", wrongHash))
})
})
}
func TestStartChromeBuild(t *testing.T) {
ctx := context.Background()
builder := "Linux Builder Perf"
commit := "12345"
Convey(`OK`, t, func() {
ctl := gomock.NewController(t)
defer ctl.Finish()
mbc := bpb.NewMockBuildsClient(ctl)
c := NewBuildbucketClient(mbc)
req := c.createChromeBuildRequest("1", "1", builder, commit, nil)
// Checking defaults
So(req.Builder.Project, ShouldEqual, ChromeProject)
So(req.Builder.Bucket, ShouldEqual, DefaultBucket)
So(req.GitilesCommit.Host, ShouldEqual, ChromiumGitilesHost)
So(req.GitilesCommit.Project, ShouldEqual, ChromiumGitilesProject)
So(req.GitilesCommit.Ref, ShouldEqual, ChromiumGitilesRefAtHead)
resp := &bpb.Build{
Id: int64(12345),
Status: bpb.Status_SCHEDULED,
}
mbc.EXPECT().ScheduleBuild(ctx, req).Return(resp, nil)
build, err := c.StartChromeBuild(ctx, "1", "1", builder, commit, nil)
So(err, ShouldBeNil)
So(build.Status, ShouldEqual, bpb.Status_SCHEDULED)
})
}