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 }