blob: b87c1318a9c2486117d9aaf1e6b10d4e453db004 [file] [log] [blame]
// Copyright 2020 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 build
import (
"context"
"fmt"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"golang.org/x/time/rate"
"google.golang.org/protobuf/types/known/timestamppb"
bbpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/clock/testclock"
. "go.chromium.org/luci/common/testing/assertions"
"go.chromium.org/luci/logdog/client/butlerlib/streamclient"
)
func TestState(t *testing.T) {
t.Parallel()
Convey(`State`, t, func() {
ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
nowpb := timestamppb.New(testclock.TestRecentTimeUTC)
origInfra := &bbpb.BuildInfra{
Buildbucket: &bbpb.BuildInfra_Buildbucket{
ServiceConfigRevision: "I am a string",
},
}
st, ctx, err := Start(ctx, &bbpb.Build{
Infra: origInfra,
})
So(err, ShouldBeNil)
defer func() {
if st != nil {
st.End(nil)
}
}()
Convey(`StartStep`, func() {
step, _ := StartStep(ctx, "some step")
defer func() { step.End(nil) }()
So(st.buildPb.Steps, ShouldResembleProto, []*bbpb.Step{
{Name: "some step", StartTime: nowpb, Status: bbpb.Status_STARTED},
})
})
Convey(`End`, func() {
Convey(`cannot End twice`, func() {
st.End(nil)
So(func() { st.End(nil) }, ShouldPanicLike, "cannot mutate ended build")
st = nil
})
})
Convey(`Infra`, func() {
infra := st.Infra()
So(infra.Buildbucket.ServiceConfigRevision, ShouldResemble, "I am a string")
infra.Buildbucket.ServiceConfigRevision = "narf"
So(origInfra.Buildbucket.ServiceConfigRevision, ShouldResemble, "I am a string")
Convey(`nil build`, func() {
st, _, err := Start(ctx, nil)
So(err, ShouldBeNil)
defer func() {
if st != nil {
st.End(nil)
}
}()
So(st.Infra(), ShouldBeNil)
})
})
})
}
func TestStateLogging(t *testing.T) {
t.Parallel()
Convey(`State logging`, t, func() {
scFake, lc := streamclient.NewUnregisteredFake("fakeNS")
ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
st, ctx, err := Start(ctx, &bbpb.Build{
Output: &bbpb.Build_Output{
Logs: []*bbpb.Log{
{Name: "something"},
{Name: "other"},
},
},
}, OptLogsink(lc))
So(err, ShouldBeNil)
defer func() { st.End(nil) }()
So(st, ShouldNotBeNil)
Convey(`existing logs are reserved`, func() {
So(st.logNames.pool, ShouldResemble, map[string]int{
"something": 1,
"other": 1,
})
})
Convey(`can open logs`, func() {
log := st.Log("some log")
fmt.Fprintln(log, "here's some stuff")
So(st.buildPb, ShouldResembleProto, &bbpb.Build{
StartTime: timestamppb.New(testclock.TestRecentTimeUTC),
Status: bbpb.Status_STARTED,
Input: &bbpb.Build_Input{},
Output: &bbpb.Build_Output{
Logs: []*bbpb.Log{
{Name: "something"},
{Name: "other"},
{Name: "some log", Url: "log/2"},
},
},
})
So(scFake.Data()["fakeNS/log/2"].GetStreamData(), ShouldContainSubstring, "here's some stuff")
})
Convey(`can open datagram logs`, func() {
log := st.LogDatagram("some log")
log.WriteDatagram([]byte("here's some stuff"))
So(st.buildPb, ShouldResembleProto, &bbpb.Build{
StartTime: timestamppb.New(testclock.TestRecentTimeUTC),
Status: bbpb.Status_STARTED,
Input: &bbpb.Build_Input{},
Output: &bbpb.Build_Output{
Logs: []*bbpb.Log{
{Name: "something"},
{Name: "other"},
{Name: "some log", Url: "log/2"},
},
},
})
So(scFake.Data()["fakeNS/log/2"].GetDatagrams(), ShouldContain, "here's some stuff")
})
})
}
type buildItem struct {
vers int64
build *bbpb.Build
}
type buildWaiter chan buildItem
func newBuildWaiter() buildWaiter {
// 100 depth is cheap way to queue all changes
return make(chan buildItem, 100)
}
func (b buildWaiter) waitFor(target int64) *bbpb.Build {
var last int64
for {
select {
case cur := <-b:
last = cur.vers
if cur.vers >= target {
return cur.build
}
case <-time.After(50 * time.Millisecond):
panic(fmt.Errorf("buildWaiter.waitFor timed out: last version %d", last))
}
}
}
func (b buildWaiter) sendFn(vers int64, build *bbpb.Build) {
b <- buildItem{vers, build}
}
func TestStateSend(t *testing.T) {
t.Parallel()
Convey(`Test that OptSend works`, t, func() {
lastBuildVers := newBuildWaiter()
ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
ts := timestamppb.New(testclock.TestRecentTimeUTC)
st, ctx, err := Start(ctx, nil, OptSend(rate.Inf, lastBuildVers.sendFn))
So(err, ShouldBeNil)
defer func() {
if st != nil {
st.End(nil)
}
}()
Convey(`startup causes no send`, func() {
So(func() { lastBuildVers.waitFor(1) }, ShouldPanicLike, "timed out")
})
Convey(`adding a step sends`, func() {
step, _ := StartStep(ctx, "something")
So(lastBuildVers.waitFor(2), ShouldResembleProto, &bbpb.Build{
Status: bbpb.Status_STARTED,
StartTime: ts,
Input: &bbpb.Build_Input{},
Output: &bbpb.Build_Output{},
Steps: []*bbpb.Step{
{
Name: "something",
StartTime: ts,
Status: bbpb.Status_STARTED,
},
},
})
Convey(`closing a step sends`, func() {
step.End(nil)
So(lastBuildVers.waitFor(3), ShouldResembleProto, &bbpb.Build{
Status: bbpb.Status_STARTED,
StartTime: ts,
Input: &bbpb.Build_Input{},
Output: &bbpb.Build_Output{},
Steps: []*bbpb.Step{
{
Name: "something",
StartTime: ts,
EndTime: ts,
Status: bbpb.Status_SUCCESS,
},
},
})
})
Convey(`manipulating a step sends`, func() {
step.SetSummaryMarkdown("hey")
So(lastBuildVers.waitFor(3), ShouldResembleProto, &bbpb.Build{
Status: bbpb.Status_STARTED,
StartTime: ts,
Input: &bbpb.Build_Input{},
Output: &bbpb.Build_Output{},
Steps: []*bbpb.Step{
{
Name: "something",
StartTime: ts,
Status: bbpb.Status_STARTED,
SummaryMarkdown: "hey",
},
},
})
})
})
Convey(`ending build sends`, func() {
st.End(nil)
st = nil
So(lastBuildVers.waitFor(1), ShouldResembleProto, &bbpb.Build{
Status: bbpb.Status_SUCCESS,
StartTime: ts,
EndTime: ts,
Input: &bbpb.Build_Input{},
Output: &bbpb.Build_Output{},
})
})
})
}
func TestStateView(t *testing.T) {
t.Parallel()
Convey(`Test State View functionality`, t, func() {
st, _, err := Start(context.Background(), nil)
So(err, ShouldBeNil)
defer func() { st.End(nil) }()
Convey(`SetSummaryMarkdown`, func() {
st.SetSummaryMarkdown("hi")
So(st.buildPb.SummaryMarkdown, ShouldResemble, "hi")
st.SetSummaryMarkdown("there")
So(st.buildPb.SummaryMarkdown, ShouldResemble, "there")
})
Convey(`SetCritical`, func() {
st.SetCritical(bbpb.Trinary_YES)
So(st.buildPb.Critical, ShouldResemble, bbpb.Trinary_YES)
st.SetCritical(bbpb.Trinary_NO)
So(st.buildPb.Critical, ShouldResemble, bbpb.Trinary_NO)
st.SetCritical(bbpb.Trinary_UNSET)
So(st.buildPb.Critical, ShouldResemble, bbpb.Trinary_UNSET)
})
Convey(`SetGitilesCommit`, func() {
st.SetGitilesCommit(&bbpb.GitilesCommit{
Host: "a host",
})
So(st.buildPb.Output.GitilesCommit, ShouldResembleProto, &bbpb.GitilesCommit{
Host: "a host",
})
st.SetGitilesCommit(nil)
So(st.buildPb.Output.GitilesCommit, ShouldBeNil)
})
})
}