go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/build/step_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 22 . "github.com/smartystreets/goconvey/convey" 23 "google.golang.org/protobuf/types/known/timestamppb" 24 25 bbpb "go.chromium.org/luci/buildbucket/proto" 26 "go.chromium.org/luci/common/clock/testclock" 27 "go.chromium.org/luci/common/errors" 28 "go.chromium.org/luci/common/logging" 29 "go.chromium.org/luci/common/logging/memlogger" 30 "go.chromium.org/luci/common/system/environ" 31 "go.chromium.org/luci/common/testing/assertions" 32 . "go.chromium.org/luci/common/testing/assertions" 33 "go.chromium.org/luci/logdog/client/butlerlib/streamclient" 34 "go.chromium.org/luci/luciexe" 35 ) 36 37 func TestStepNoop(t *testing.T) { 38 Convey(`Step no-op mode`, t, func() { 39 ctx := memlogger.Use(context.Background()) 40 logs := logging.Get(ctx).(*memlogger.MemLogger) 41 42 Convey(`Step creation`, func() { 43 Convey(`ScheduleStep`, func() { 44 step, ctx := ScheduleStep(ctx, "some step") 45 defer func() { step.End(nil) }() 46 47 So(logs, memlogger.ShouldHaveLog, 48 logging.Info, "set status: SCHEDULED", logging.Fields{"build.step": "some step"}) 49 50 So(step, ShouldNotBeNil) 51 So(getState(ctx).stepNamePrefix(), ShouldResemble, "some step|") 52 53 So(step.Start, ShouldNotPanic) 54 So(logs, memlogger.ShouldHaveLog, logging.Info, "set status: STARTED") 55 So(logs.Messages(), ShouldHaveLength, 2) 56 57 So(step.Start, ShouldNotPanic) // noop 58 So(logs.Messages(), ShouldHaveLength, 2) 59 }) 60 61 Convey(`StartStep`, func() { 62 step, ctx := StartStep(ctx, "some step") 63 defer func() { step.End(nil) }() 64 65 So(logs, memlogger.ShouldHaveLog, logging.Info, "set status: SCHEDULED") 66 So(logs, memlogger.ShouldHaveLog, logging.Info, "set status: STARTED") 67 68 So(step, ShouldNotBeNil) 69 So(getState(ctx).stepNamePrefix(), ShouldResemble, "some step|") 70 So(logs.Messages(), ShouldHaveLength, 2) 71 72 So(step.Start, ShouldNotPanic) // noop 73 So(logs.Messages(), ShouldHaveLength, 2) 74 }) 75 76 Convey(`Bad step name`, func() { 77 So(func() { 78 StartStep(ctx, "bad | step") 79 }, ShouldPanicLike, "reserved character") 80 }) 81 }) 82 83 Convey(`Step closure`, func() { 84 Convey(`SUCCESS`, func() { 85 step, ctx := StartStep(ctx, "some step") 86 step.End(nil) 87 So(step.stepPb.Status, ShouldResemble, bbpb.Status_SUCCESS) 88 89 So(logs, memlogger.ShouldHaveLog, logging.Info, "set status: SUCCESS") 90 91 Convey(`cannot double-close`, func() { 92 So(func() { step.End(nil) }, ShouldPanicLike, "cannot mutate ended step") 93 }) 94 95 Convey(`cancels context as well`, func() { 96 So(ctx.Err(), ShouldResemble, context.Canceled) 97 }) 98 }) 99 100 Convey(`error`, func() { 101 step, _ := StartStep(ctx, "some step") 102 step.End(errors.New("bad stuff")) 103 So(step.stepPb.Status, ShouldResemble, bbpb.Status_FAILURE) 104 105 So(logs, memlogger.ShouldHaveLog, logging.Error, "set status: FAILURE: bad stuff") 106 }) 107 108 Convey(`CANCELED`, func() { 109 step, _ := StartStep(ctx, "some step") 110 step.End(context.Canceled) 111 So(step.stepPb.Status, ShouldResemble, bbpb.Status_CANCELED) 112 113 So(logs, memlogger.ShouldHaveLog, logging.Warning, "set status: CANCELED: context canceled") 114 }) 115 116 Convey(`panic`, func() { 117 step, _ := StartStep(ctx, "some step") 118 func() { 119 defer func() { 120 step.End(nil) 121 recover() // so testing assertions can happen 122 }() 123 panic("doom!") 124 }() 125 So(step.stepPb.Status, ShouldResemble, bbpb.Status_INFRA_FAILURE) 126 So(logs, memlogger.ShouldHaveLog, logging.Error, "set status: INFRA_FAILURE: PANIC") 127 }) 128 129 Convey(`with SummaryMarkdown`, func() { 130 step, _ := StartStep(ctx, "some step") 131 step.SetSummaryMarkdown("cool story!") 132 step.End(nil) 133 So(logs, memlogger.ShouldHaveLog, logging.Info, "set status: SUCCESS\n with SummaryMarkdown:\ncool story!") 134 }) 135 136 Convey(`modify starts step`, func() { 137 step, _ := ScheduleStep(ctx, "some step") 138 defer func() { step.End(nil) }() 139 step.SetSummaryMarkdown("cool story!") 140 So(step.stepPb.Status, ShouldResemble, bbpb.Status_STARTED) 141 }) 142 143 Convey(`closure of un-started step`, func() { 144 step, ctx := ScheduleStep(ctx, "some step") 145 So(func() { step.End(nil) }, ShouldNotPanic) 146 So(step.stepPb.Status, ShouldResemble, bbpb.Status_SUCCESS) 147 So(step.stepPb.StartTime, ShouldNotBeNil) 148 So(ctx.Err(), ShouldResemble, context.Canceled) 149 }) 150 }) 151 152 Convey(`Recursive steps`, func() { 153 Convey(`basic`, func() { 154 parent, ctx := StartStep(ctx, "parent") 155 defer func() { parent.End(nil) }() 156 157 child, ctx := StartStep(ctx, "child") 158 defer func() { child.End(nil) }() 159 160 So(parent.name, ShouldResemble, "parent") 161 So(child.name, ShouldResemble, "parent|child") 162 }) 163 164 Convey(`creating child step starts parent`, func() { 165 parent, ctx := ScheduleStep(ctx, "parent") 166 defer func() { parent.End(nil) }() 167 168 child, ctx := ScheduleStep(ctx, "child") 169 defer func() { child.End(nil) }() 170 171 So(parent.name, ShouldResemble, "parent") 172 So(parent.stepPb.Status, ShouldResemble, bbpb.Status_STARTED) 173 So(child.name, ShouldResemble, "parent|child") 174 So(child.stepPb.Status, ShouldResemble, bbpb.Status_SCHEDULED) 175 }) 176 }) 177 178 Convey(`Step logs`, func() { 179 ctx := memlogger.Use(ctx) 180 logs := logging.Get(ctx).(*memlogger.MemLogger) 181 step, _ := StartStep(ctx, "some step") 182 183 Convey(`text`, func() { 184 log := step.Log("a log") 185 _, err := log.Write([]byte("this is stuff")) 186 So(err, ShouldBeNil) 187 step.End(nil) 188 So(logs, memlogger.ShouldHaveLog, logging.Info, "this is stuff") 189 So(log.UILink(), ShouldEqual, "") 190 }) 191 192 Convey(`binary`, func() { 193 log := step.Log("a log", streamclient.Binary()) 194 _, err := log.Write([]byte("this is stuff")) 195 So(err, ShouldBeNil) 196 step.End(nil) 197 So(logs, memlogger.ShouldHaveLog, logging.Warning, "dropping BINARY log \"a log\"") 198 So(log.UILink(), ShouldEqual, "") 199 }) 200 201 Convey(`datagram`, func() { 202 log := step.LogDatagram("a log") 203 So(log.WriteDatagram([]byte("this is stuff")), ShouldBeNil) 204 step.End(nil) 205 So(logs, memlogger.ShouldHaveLog, logging.Warning, "dropping DATAGRAM log \"a log\"") 206 }) 207 }) 208 }) 209 } 210 211 func TestStepLog(t *testing.T) { 212 Convey(`Step logging`, t, func() { 213 scFake, lc := streamclient.NewUnregisteredFake("fakeNS") 214 ctx, _ := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC) 215 build := &bbpb.Build{ 216 Infra: &bbpb.BuildInfra{ 217 Logdog: &bbpb.BuildInfra_LogDog{ 218 Hostname: "logs.chromium.org", 219 Project: "example", 220 Prefix: "builds/8888888888", 221 }, 222 }, 223 } 224 buildState, ctx, err := Start(ctx, build, OptLogsink(lc)) 225 So(err, ShouldBeNil) 226 defer func() { buildState.End(nil) }() 227 So(buildState, ShouldNotBeNil) 228 229 Convey(`logging redirects`, func() { 230 step, ctx := StartStep(ctx, "some step") 231 logging.Infof(ctx, "hi there!") 232 step.End(nil) 233 234 So(step.stepPb, assertions.ShouldResembleProto, &bbpb.Step{ 235 Name: "some step", 236 StartTime: timestamppb.New(testclock.TestRecentTimeUTC), 237 EndTime: timestamppb.New(testclock.TestRecentTimeUTC), 238 Status: bbpb.Status_SUCCESS, 239 Logs: []*bbpb.Log{ 240 {Name: "log", Url: "step/0/log/0"}, 241 }, 242 }) 243 244 So(scFake.Data()["fakeNS/step/0/log/0"].GetStreamData(), ShouldContainSubstring, "hi there!") 245 }) 246 247 Convey(`can open logs`, func() { 248 step, _ := StartStep(ctx, "some step") 249 log := step.Log("some log") 250 fmt.Fprintln(log, "here's some stuff") 251 step.End(nil) 252 253 So(step.stepPb, assertions.ShouldResembleProto, &bbpb.Step{ 254 Name: "some step", 255 StartTime: timestamppb.New(testclock.TestRecentTimeUTC), 256 EndTime: timestamppb.New(testclock.TestRecentTimeUTC), 257 Status: bbpb.Status_SUCCESS, 258 Logs: []*bbpb.Log{ 259 // log link is removed because logging is not used for this step. 260 // {Name: "log", Url: "step/0/log/0"}, 261 {Name: "some log", Url: "step/0/log/1"}, 262 }, 263 }) 264 265 So(scFake.Data()["fakeNS/step/0/log/1"].GetStreamData(), ShouldContainSubstring, "here's some stuff") 266 267 // Check the link. 268 wantLink := "https://logs.chromium.org/logs/example/builds/8888888888/+/fakeNS/step/0/log/1" 269 So(log.UILink(), ShouldEqual, wantLink) 270 }) 271 272 Convey(`can open datagram logs`, func() { 273 step, _ := StartStep(ctx, "some step") 274 log := step.LogDatagram("some log") 275 log.WriteDatagram([]byte("here's some stuff")) 276 step.End(nil) 277 278 So(step.stepPb, assertions.ShouldResembleProto, &bbpb.Step{ 279 Name: "some step", 280 StartTime: timestamppb.New(testclock.TestRecentTimeUTC), 281 EndTime: timestamppb.New(testclock.TestRecentTimeUTC), 282 Status: bbpb.Status_SUCCESS, 283 Logs: []*bbpb.Log{ 284 // log link is removed because logging is not used for this step. 285 // {Name: "log", Url: "step/0/log/0"}, 286 {Name: "some log", Url: "step/0/log/1"}, 287 }, 288 }) 289 290 So(scFake.Data()["fakeNS/step/0/log/1"].GetDatagrams(), ShouldContain, "here's some stuff") 291 }) 292 293 Convey(`sets LOGDOG_NAMESPACE`, func() { 294 _, subCtx := StartStep(ctx, "some step") 295 So(environ.FromCtx(subCtx).Get(luciexe.LogdogNamespaceEnv), ShouldEqual, "fakeNS/step/0/u") 296 297 _, subSubCtx := StartStep(ctx, "some sub step") 298 So(environ.FromCtx(subSubCtx).Get(luciexe.LogdogNamespaceEnv), ShouldEqual, "fakeNS/step/1/u") 299 }) 300 }) 301 }