go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/build/state_test.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package build
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/smartystreets/goconvey/convey"
    24  	"golang.org/x/time/rate"
    25  	"google.golang.org/protobuf/types/known/timestamppb"
    26  
    27  	bbpb "go.chromium.org/luci/buildbucket/proto"
    28  	"go.chromium.org/luci/common/clock/testclock"
    29  	. "go.chromium.org/luci/common/testing/assertions"
    30  	"go.chromium.org/luci/logdog/client/butlerlib/streamclient"
    31  )
    32  
    33  func TestState(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	Convey(`State`, t, func() {
    37  		ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
    38  		nowpb := timestamppb.New(testclock.TestRecentTimeUTC)
    39  
    40  		origInfra := &bbpb.BuildInfra{
    41  			Buildbucket: &bbpb.BuildInfra_Buildbucket{
    42  				ServiceConfigRevision: "I am a string",
    43  			},
    44  		}
    45  		st, ctx, err := Start(ctx, &bbpb.Build{
    46  			Infra: origInfra,
    47  		})
    48  		So(err, ShouldBeNil)
    49  		defer func() {
    50  			if st != nil {
    51  				st.End(nil)
    52  			}
    53  		}()
    54  
    55  		Convey(`StartStep`, func() {
    56  			step, _ := StartStep(ctx, "some step")
    57  			defer func() { step.End(nil) }()
    58  
    59  			So(st.buildPb.Steps, ShouldResembleProto, []*bbpb.Step{
    60  				{Name: "some step", StartTime: nowpb, Status: bbpb.Status_STARTED},
    61  			})
    62  		})
    63  
    64  		Convey(`End`, func() {
    65  			Convey(`cannot End twice`, func() {
    66  				st.End(nil)
    67  				So(func() { st.End(nil) }, ShouldPanicLike, "cannot mutate ended build")
    68  				st = nil
    69  			})
    70  		})
    71  
    72  		Convey(`Build.Infra()`, func() {
    73  			build := st.Build()
    74  			So(build.GetInfra().Buildbucket.ServiceConfigRevision, ShouldResemble, "I am a string")
    75  			build.GetInfra().Buildbucket.ServiceConfigRevision = "narf"
    76  			So(origInfra.Buildbucket.ServiceConfigRevision, ShouldResemble, "I am a string")
    77  
    78  			Convey(`nil build`, func() {
    79  				st, _, err := Start(ctx, nil)
    80  				So(err, ShouldBeNil)
    81  				defer func() {
    82  					if st != nil {
    83  						st.End(nil)
    84  					}
    85  				}()
    86  				So(st.Build().GetInfra(), ShouldBeNil)
    87  			})
    88  		})
    89  	})
    90  }
    91  
    92  func TestStateLogging(t *testing.T) {
    93  	t.Parallel()
    94  
    95  	Convey(`State logging`, t, func() {
    96  		scFake, lc := streamclient.NewUnregisteredFake("fakeNS")
    97  
    98  		ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
    99  		buildInfra := &bbpb.BuildInfra{
   100  			Logdog: &bbpb.BuildInfra_LogDog{
   101  				Hostname: "logs.chromium.org",
   102  				Project:  "example",
   103  				Prefix:   "builds/8888888888",
   104  			},
   105  		}
   106  		st, ctx, err := Start(ctx, &bbpb.Build{
   107  			Output: &bbpb.Build_Output{
   108  				Logs: []*bbpb.Log{
   109  					{Name: "something"},
   110  					{Name: "other"},
   111  				},
   112  			},
   113  			Infra: buildInfra,
   114  		}, OptLogsink(lc))
   115  		So(err, ShouldBeNil)
   116  		defer func() { st.End(nil) }()
   117  		So(st, ShouldNotBeNil)
   118  
   119  		Convey(`existing logs are reserved`, func() {
   120  			So(st.logNames.pool, ShouldResemble, map[string]int{
   121  				"something": 1,
   122  				"other":     1,
   123  			})
   124  		})
   125  
   126  		Convey(`can open logs`, func() {
   127  			log := st.Log("some log")
   128  			fmt.Fprintln(log, "here's some stuff")
   129  			So(st.buildPb, ShouldResembleProto, &bbpb.Build{
   130  				StartTime: timestamppb.New(testclock.TestRecentTimeUTC),
   131  				Status:    bbpb.Status_STARTED,
   132  				Input:     &bbpb.Build_Input{},
   133  				Output: &bbpb.Build_Output{
   134  					Logs: []*bbpb.Log{
   135  						{Name: "something"},
   136  						{Name: "other"},
   137  						{Name: "some log", Url: "log/2"},
   138  					},
   139  					Status: bbpb.Status_STARTED,
   140  				},
   141  				Infra: buildInfra,
   142  			})
   143  
   144  			So(scFake.Data()["fakeNS/log/2"].GetStreamData(), ShouldContainSubstring, "here's some stuff")
   145  
   146  			// Check the link.
   147  			wantLink := "https://logs.chromium.org/logs/example/builds/8888888888/+/fakeNS/log/2"
   148  			So(log.UILink(), ShouldEqual, wantLink)
   149  		})
   150  
   151  		Convey(`can open datagram logs`, func() {
   152  			log := st.LogDatagram("some log")
   153  			log.WriteDatagram([]byte("here's some stuff"))
   154  
   155  			So(st.buildPb, ShouldResembleProto, &bbpb.Build{
   156  				StartTime: timestamppb.New(testclock.TestRecentTimeUTC),
   157  				Status:    bbpb.Status_STARTED,
   158  				Input:     &bbpb.Build_Input{},
   159  				Output: &bbpb.Build_Output{
   160  					Logs: []*bbpb.Log{
   161  						{Name: "something"},
   162  						{Name: "other"},
   163  						{Name: "some log", Url: "log/2"},
   164  					},
   165  					Status: bbpb.Status_STARTED,
   166  				},
   167  				Infra: buildInfra,
   168  			})
   169  
   170  			So(scFake.Data()["fakeNS/log/2"].GetDatagrams(), ShouldContain, "here's some stuff")
   171  		})
   172  
   173  	})
   174  }
   175  
   176  type buildItem struct {
   177  	vers  int64
   178  	build *bbpb.Build
   179  }
   180  
   181  type buildWaiter chan buildItem
   182  
   183  func newBuildWaiter() buildWaiter {
   184  	// 100 depth is cheap way to queue all changes
   185  	return make(chan buildItem, 100)
   186  }
   187  
   188  func (b buildWaiter) waitFor(target int64) *bbpb.Build {
   189  	var last int64
   190  	for {
   191  		select {
   192  		case cur := <-b:
   193  			last = cur.vers
   194  			if cur.vers >= target {
   195  				return cur.build
   196  			}
   197  
   198  		case <-time.After(50 * time.Millisecond):
   199  			panic(fmt.Errorf("buildWaiter.waitFor timed out: last version %d", last))
   200  		}
   201  	}
   202  }
   203  
   204  func (b buildWaiter) sendFn(vers int64, build *bbpb.Build) {
   205  	b <- buildItem{vers, build}
   206  }
   207  
   208  func TestStateSend(t *testing.T) {
   209  	t.Parallel()
   210  
   211  	Convey(`Test that OptSend works`, t, func() {
   212  		lastBuildVers := newBuildWaiter()
   213  
   214  		ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
   215  		ts := timestamppb.New(testclock.TestRecentTimeUTC)
   216  		st, ctx, err := Start(ctx, nil, OptSend(rate.Inf, lastBuildVers.sendFn))
   217  		So(err, ShouldBeNil)
   218  		defer func() {
   219  			if st != nil {
   220  				st.End(nil)
   221  			}
   222  		}()
   223  
   224  		Convey(`startup causes no send`, func() {
   225  			So(func() { lastBuildVers.waitFor(1) }, ShouldPanicLike, "timed out")
   226  		})
   227  
   228  		Convey(`adding a step sends`, func() {
   229  			step, _ := StartStep(ctx, "something")
   230  			So(lastBuildVers.waitFor(2), ShouldResembleProto, &bbpb.Build{
   231  				Status:    bbpb.Status_STARTED,
   232  				StartTime: ts,
   233  				Input:     &bbpb.Build_Input{},
   234  				Output: &bbpb.Build_Output{
   235  					Status: bbpb.Status_STARTED,
   236  				},
   237  				Steps: []*bbpb.Step{
   238  					{
   239  						Name:      "something",
   240  						StartTime: ts,
   241  						Status:    bbpb.Status_STARTED,
   242  					},
   243  				},
   244  			})
   245  
   246  			Convey(`closing a step sends`, func() {
   247  				step.End(nil)
   248  				So(lastBuildVers.waitFor(3), ShouldResembleProto, &bbpb.Build{
   249  					Status:    bbpb.Status_STARTED,
   250  					StartTime: ts,
   251  					Input:     &bbpb.Build_Input{},
   252  					Output: &bbpb.Build_Output{
   253  						Status: bbpb.Status_STARTED,
   254  					},
   255  					Steps: []*bbpb.Step{
   256  						{
   257  							Name:      "something",
   258  							StartTime: ts,
   259  							EndTime:   ts,
   260  							Status:    bbpb.Status_SUCCESS,
   261  						},
   262  					},
   263  				})
   264  			})
   265  
   266  			Convey(`manipulating a step sends`, func() {
   267  				step.SetSummaryMarkdown("hey")
   268  				So(lastBuildVers.waitFor(3), ShouldResembleProto, &bbpb.Build{
   269  					Status:    bbpb.Status_STARTED,
   270  					StartTime: ts,
   271  					Input:     &bbpb.Build_Input{},
   272  					Output: &bbpb.Build_Output{
   273  						Status: bbpb.Status_STARTED,
   274  					},
   275  					Steps: []*bbpb.Step{
   276  						{
   277  							Name:            "something",
   278  							StartTime:       ts,
   279  							Status:          bbpb.Status_STARTED,
   280  							SummaryMarkdown: "hey",
   281  						},
   282  					},
   283  				})
   284  			})
   285  		})
   286  
   287  		Convey(`ending build sends`, func() {
   288  			st.End(nil)
   289  			st = nil
   290  			So(lastBuildVers.waitFor(1), ShouldResembleProto, &bbpb.Build{
   291  				Status:    bbpb.Status_SUCCESS,
   292  				StartTime: ts,
   293  				EndTime:   ts,
   294  				Input:     &bbpb.Build_Input{},
   295  				Output: &bbpb.Build_Output{
   296  					Status: bbpb.Status_SUCCESS,
   297  				},
   298  			})
   299  		})
   300  
   301  	})
   302  }
   303  
   304  func TestStateView(t *testing.T) {
   305  	t.Parallel()
   306  
   307  	Convey(`Test State View functionality`, t, func() {
   308  		st, _, err := Start(context.Background(), nil)
   309  		So(err, ShouldBeNil)
   310  		defer func() { st.End(nil) }()
   311  
   312  		Convey(`SetSummaryMarkdown`, func() {
   313  			st.SetSummaryMarkdown("hi")
   314  
   315  			So(st.buildPb.SummaryMarkdown, ShouldResemble, "hi")
   316  
   317  			st.SetSummaryMarkdown("there")
   318  
   319  			So(st.buildPb.SummaryMarkdown, ShouldResemble, "there")
   320  		})
   321  
   322  		Convey(`SetCritical`, func() {
   323  			st.SetCritical(bbpb.Trinary_YES)
   324  
   325  			So(st.buildPb.Critical, ShouldResemble, bbpb.Trinary_YES)
   326  
   327  			st.SetCritical(bbpb.Trinary_NO)
   328  
   329  			So(st.buildPb.Critical, ShouldResemble, bbpb.Trinary_NO)
   330  
   331  			st.SetCritical(bbpb.Trinary_UNSET)
   332  
   333  			So(st.buildPb.Critical, ShouldResemble, bbpb.Trinary_UNSET)
   334  		})
   335  
   336  		Convey(`SetGitilesCommit`, func() {
   337  			st.SetGitilesCommit(&bbpb.GitilesCommit{
   338  				Host: "a host",
   339  			})
   340  
   341  			So(st.buildPb.Output.GitilesCommit, ShouldResembleProto, &bbpb.GitilesCommit{
   342  				Host: "a host",
   343  			})
   344  
   345  			st.SetGitilesCommit(nil)
   346  
   347  			So(st.buildPb.Output.GitilesCommit, ShouldBeNil)
   348  		})
   349  	})
   350  }