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  }