blob: e3b671778aef6c7a0e4ac1fddaf63f5c2f78ce7f [file] [log] [blame]
// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package buildbucket
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"go.chromium.org/luci/appengine/gaetesting"
"go.chromium.org/luci/auth/identity"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
bbv1 "go.chromium.org/luci/common/api/buildbucket/buildbucket/v1"
"go.chromium.org/luci/common/clock/testclock"
"go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/luci/milo/common"
"go.chromium.org/luci/milo/common/model"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/auth/authtest"
"go.chromium.org/luci/server/caching"
"go.chromium.org/luci/server/router"
"github.com/golang/mock/gomock"
. "github.com/smartystreets/goconvey/convey"
)
func newMockClient(c context.Context, t *testing.T) (context.Context, *gomock.Controller, *buildbucketpb.MockBuildsClient) {
ctrl := gomock.NewController(t)
client := buildbucketpb.NewMockBuildsClient(ctrl)
factory := func(c context.Context, host string, as auth.RPCAuthorityKind, opts ...auth.RPCOption) (buildbucketpb.BuildsClient, error) {
return client, nil
}
return WithBuildsClientFactory(c, factory), ctrl, client
}
// Buildbucket timestamps round off to milliseconds, so define a reference.
var RefTime = time.Date(2016, time.February, 3, 4, 5, 6, 0, time.UTC)
func makeReq(build bbv1.LegacyApiCommonBuildMessage) io.ReadCloser {
bmsg := struct {
Build bbv1.LegacyApiCommonBuildMessage `json:"build"`
Hostname string `json:"hostname"`
}{build, "hostname"}
bm, _ := json.Marshal(bmsg)
sub := "projects/luci-milo/subscriptions/buildbucket-public"
msg := common.PubSubSubscription{
Subscription: sub,
Message: common.PubSubMessage{
Data: base64.StdEncoding.EncodeToString(bm),
},
}
jmsg, _ := json.Marshal(msg)
return ioutil.NopCloser(bytes.NewReader(jmsg))
}
func TestPubSub(t *testing.T) {
t.Parallel()
Convey(`TestPubSub`, t, func() {
c := gaetesting.TestingContextWithAppID("luci-milo-dev")
datastore.GetTestable(c).Consistent(true)
c, _ = testclock.UseTime(c, RefTime)
c = auth.WithState(c, &authtest.FakeState{
Identity: identity.AnonymousIdentity,
IdentityGroups: []string{"all"},
})
c = caching.WithRequestCache(c)
c, ctrl, mbc := newMockClient(c, t)
defer ctrl.Finish()
// Initialize the appropriate builder.
builderSummary := &model.BuilderSummary{
BuilderID: "buildbucket/luci.fake.bucket/fake_builder",
}
datastore.Put(c, builderSummary)
// Initialize the appropriate project config.
datastore.Put(c, &common.Project{
ID: "fake",
IgnoredBuilderIDs: []string{"bucket/fake_ignored_builder"},
})
// We'll copy this LegacyApiCommonBuildMessage base for convenience.
buildBase := bbv1.LegacyApiCommonBuildMessage{
Project: "fake",
Bucket: "luci.fake.bucket",
Tags: []string{"builder:fake_builder"},
CreatedBy: string(identity.AnonymousIdentity),
CreatedTs: bbv1.FormatTimestamp(RefTime.Add(2 * time.Hour)),
}
Convey("New in-process build", func() {
bKey := MakeBuildKey(c, "hostname", "1234")
buildExp := buildBase
buildExp.Id = 1234
created := timestamppb.New(RefTime.Add(2 * time.Hour))
started := timestamppb.New(RefTime.Add(3 * time.Hour))
updated := timestamppb.New(RefTime.Add(5 * time.Hour))
propertiesMap := map[string]interface{}{
"$recipe_engine/milo/blamelist_pins": []interface{}{
map[string]interface{}{
"host": "chromium.googlesource.com",
"id": "8930f18245df678abc944376372c77ba5e2a658b",
"project": "angle/angle",
},
map[string]interface{}{
"host": "chromium.googlesource.com",
"id": "07033c702f81a75dfc2d83888ba3f8b354d0e920",
"project": "chromium/src",
},
},
}
properties, _ := structpb.NewStruct(propertiesMap)
mbc.EXPECT().GetBuild(gomock.Any(), gomock.Any()).Return(&buildbucketpb.Build{
Id: 1234,
Status: buildbucketpb.Status_STARTED,
CreateTime: created,
StartTime: started,
UpdateTime: updated,
Builder: &buildbucketpb.BuilderID{
Project: "fake",
Bucket: "bucket",
Builder: "fake_builder",
},
Input: &buildbucketpb.Build_Input{
Experimental: true,
},
Output: &buildbucketpb.Build_Output{
Properties: properties,
},
}, nil).AnyTimes()
h := httptest.NewRecorder()
r := &http.Request{Body: makeReq(buildExp)}
PubSubHandler(&router.Context{
Context: c,
Writer: h,
Request: r,
})
So(h.Code, ShouldEqual, 200)
datastore.GetTestable(c).CatchupIndexes()
Convey("stores BuildSummary and BuilderSummary", func() {
buildAct := model.BuildSummary{BuildKey: bKey}
err := datastore.Get(c, &buildAct)
So(err, ShouldBeNil)
So(buildAct.BuildKey.String(), ShouldEqual, bKey.String())
So(buildAct.BuilderID, ShouldEqual, "buildbucket/luci.fake.bucket/fake_builder")
So(buildAct.Summary, ShouldResemble, model.Summary{
Status: model.Running,
Start: RefTime.Add(3 * time.Hour),
})
So(buildAct.Created, ShouldResemble, RefTime.Add(2*time.Hour))
So(buildAct.Experimental, ShouldBeTrue)
So(buildAct.BlamelistPins, ShouldResemble, []string{
"commit/gitiles/chromium.googlesource.com/angle/angle/+/8930f18245df678abc944376372c77ba5e2a658b",
"commit/gitiles/chromium.googlesource.com/chromium/src/+/07033c702f81a75dfc2d83888ba3f8b354d0e920",
})
blder := model.BuilderSummary{BuilderID: "buildbucket/luci.fake.bucket/fake_builder"}
err = datastore.Get(c, &blder)
So(err, ShouldBeNil)
So(blder.LastFinishedStatus, ShouldResemble, model.NotRun)
So(blder.LastFinishedBuildID, ShouldEqual, "")
})
})
Convey("Completed build", func() {
bKey := MakeBuildKey(c, "hostname", "2234")
buildExp := buildBase
buildExp.Id = 2234
created := timestamppb.New(RefTime.Add(2 * time.Hour))
started := timestamppb.New(RefTime.Add(3 * time.Hour))
updated := timestamppb.New(RefTime.Add(6 * time.Hour))
completed := timestamppb.New(RefTime.Add(6 * time.Hour))
mbc.EXPECT().GetBuild(gomock.Any(), gomock.Any()).Return(&buildbucketpb.Build{
Id: 2234,
Status: buildbucketpb.Status_SUCCESS,
CreateTime: created,
StartTime: started,
EndTime: completed,
UpdateTime: updated,
Builder: &buildbucketpb.BuilderID{
Project: "fake",
Bucket: "bucket",
Builder: "fake_builder",
},
Input: &buildbucketpb.Build_Input{
GitilesCommit: &buildbucketpb.GitilesCommit{
Host: "chromium.googlesource.com",
Id: "8930f18245df678abc944376372c77ba5e2a658b",
Project: "angle/angle",
},
},
}, nil).AnyTimes()
h := httptest.NewRecorder()
r := &http.Request{Body: makeReq(buildExp)}
PubSubHandler(&router.Context{
Context: c,
Writer: h,
Request: r,
})
So(h.Code, ShouldEqual, 200)
Convey("stores BuildSummary and BuilderSummary", func() {
buildAct := model.BuildSummary{BuildKey: bKey}
err := datastore.Get(c, &buildAct)
So(err, ShouldBeNil)
So(buildAct.BuildKey.String(), ShouldEqual, bKey.String())
So(buildAct.BuilderID, ShouldEqual, "buildbucket/luci.fake.bucket/fake_builder")
So(buildAct.Summary, ShouldResemble, model.Summary{
Status: model.Success,
Start: RefTime.Add(3 * time.Hour),
End: RefTime.Add(6 * time.Hour),
})
So(buildAct.Created, ShouldResemble, RefTime.Add(2*time.Hour))
blder := model.BuilderSummary{BuilderID: "buildbucket/luci.fake.bucket/fake_builder"}
err = datastore.Get(c, &blder)
So(err, ShouldBeNil)
So(blder.LastFinishedCreated, ShouldResemble, RefTime.Add(2*time.Hour))
So(blder.LastFinishedStatus, ShouldResemble, model.Success)
So(blder.LastFinishedBuildID, ShouldEqual, "buildbucket/2234")
So(buildAct.BlamelistPins, ShouldResemble, []string{
"commit/gitiles/chromium.googlesource.com/angle/angle/+/8930f18245df678abc944376372c77ba5e2a658b",
})
})
Convey("results in earlier update not being ingested", func() {
eBuild := bbv1.LegacyApiCommonBuildMessage{
Id: 2234,
Project: "fake",
Bucket: "luci.fake.bucket",
Tags: []string{"builder:fake_builder"},
CreatedBy: string(identity.AnonymousIdentity),
CreatedTs: bbv1.FormatTimestamp(RefTime.Add(2 * time.Hour)),
StartedTs: bbv1.FormatTimestamp(RefTime.Add(3 * time.Hour)),
UpdatedTs: bbv1.FormatTimestamp(RefTime.Add(4 * time.Hour)),
Status: "STARTED",
}
h := httptest.NewRecorder()
r := &http.Request{Body: makeReq(eBuild)}
PubSubHandler(&router.Context{
Context: c,
Writer: h,
Request: r,
})
So(h.Code, ShouldEqual, 200)
buildAct := model.BuildSummary{BuildKey: bKey}
err := datastore.Get(c, &buildAct)
So(err, ShouldBeNil)
So(buildAct.Summary, ShouldResemble, model.Summary{
Status: model.Success,
Start: RefTime.Add(3 * time.Hour),
End: RefTime.Add(6 * time.Hour),
})
So(buildAct.Created, ShouldResemble, RefTime.Add(2*time.Hour))
blder := model.BuilderSummary{BuilderID: "buildbucket/luci.fake.bucket/fake_builder"}
err = datastore.Get(c, &blder)
So(err, ShouldBeNil)
So(blder.LastFinishedCreated, ShouldResemble, RefTime.Add(2*time.Hour))
So(blder.LastFinishedStatus, ShouldResemble, model.Success)
So(blder.LastFinishedBuildID, ShouldEqual, "buildbucket/2234")
})
})
Convey("Builders in IgnoredBuilderIds should be ignored", func() {
created := timestamppb.New(RefTime.Add(2 * time.Hour))
started := timestamppb.New(RefTime.Add(3 * time.Hour))
updated := timestamppb.New(RefTime.Add(5 * time.Hour))
builderID := buildbucketpb.BuilderID{
Project: "fake",
Bucket: "bucket",
Builder: "fake_ignored_builder",
}
mbc.EXPECT().GetBuild(gomock.Any(), gomock.Any()).Return(&buildbucketpb.Build{
Id: 3234,
Status: buildbucketpb.Status_SUCCESS,
CreateTime: created,
StartTime: started,
UpdateTime: updated,
Builder: &builderID,
}, nil).AnyTimes()
bKey := MakeBuildKey(c, "hostname", "3234")
eBuild := bbv1.LegacyApiCommonBuildMessage{
Id: 3234,
Project: "fake",
Bucket: "luci.fake.bucket",
Tags: []string{"builder:fake_ignored_builder"},
CreatedBy: string(identity.AnonymousIdentity),
CreatedTs: bbv1.FormatTimestamp(RefTime.Add(2 * time.Hour)),
StartedTs: bbv1.FormatTimestamp(RefTime.Add(3 * time.Hour)),
UpdatedTs: bbv1.FormatTimestamp(RefTime.Add(4 * time.Hour)),
Status: "COMPLETED",
Result: "SUCCESS",
}
h := httptest.NewRecorder()
r := &http.Request{Body: makeReq(eBuild)}
PubSubHandler(&router.Context{
Context: c,
Writer: h,
Request: r,
})
So(h.Code, ShouldEqual, 200)
buildAct := model.BuildSummary{BuildKey: bKey}
err := datastore.Get(c, &buildAct)
So(err, ShouldEqual, datastore.ErrNoSuchEntity)
blder := model.BuilderSummary{BuilderID: common.LegacyBuilderIDString(&builderID)}
err = datastore.Get(c, &blder)
So(err, ShouldEqual, datastore.ErrNoSuchEntity)
})
})
}