go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/internal/buildstatus/buildstatus_test.go (about)

     1  // Copyright 2023 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 buildstatus
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"go.chromium.org/luci/gae/impl/memory"
    22  
    23  	"go.chromium.org/luci/gae/filter/txndefer"
    24  	"go.chromium.org/luci/gae/service/datastore"
    25  
    26  	"go.chromium.org/luci/buildbucket/appengine/model"
    27  	pb "go.chromium.org/luci/buildbucket/proto"
    28  
    29  	. "github.com/smartystreets/goconvey/convey"
    30  	. "go.chromium.org/luci/common/testing/assertions"
    31  )
    32  
    33  func update(ctx context.Context, u *Updater) (*model.BuildStatus, error) {
    34  	var bs *model.BuildStatus
    35  	txErr := datastore.RunInTransaction(ctx, func(ctx context.Context) error {
    36  		var err error
    37  		bs, err = u.Do(ctx)
    38  		return err
    39  	}, nil)
    40  	return bs, txErr
    41  }
    42  func TestUpdate(t *testing.T) {
    43  	t.Parallel()
    44  	Convey("Update", t, func() {
    45  		ctx := memory.Use(context.Background())
    46  		ctx = txndefer.FilterRDS(ctx)
    47  		datastore.GetTestable(ctx).AutoIndex(true)
    48  		datastore.GetTestable(ctx).Consistent(true)
    49  
    50  		Convey("fail", func() {
    51  
    52  			Convey("not in transaction", func() {
    53  				u := &Updater{}
    54  				_, err := u.Do(ctx)
    55  				So(err, ShouldErrLike, "must update build status in a transaction")
    56  			})
    57  
    58  			Convey("update ended build", func() {
    59  				b := &model.Build{
    60  					ID: 1,
    61  					Proto: &pb.Build{
    62  						Id: 1,
    63  						Builder: &pb.BuilderID{
    64  							Project: "project",
    65  							Bucket:  "bucket",
    66  							Builder: "builder",
    67  						},
    68  						Status: pb.Status_SUCCESS,
    69  					},
    70  					Status: pb.Status_SUCCESS,
    71  				}
    72  				So(datastore.Put(ctx, b), ShouldBeNil)
    73  				u := &Updater{
    74  					Build:       b,
    75  					BuildStatus: &StatusWithDetails{Status: pb.Status_SUCCESS},
    76  				}
    77  				_, err := update(ctx, u)
    78  				So(err, ShouldErrLike, "cannot update status for an ended build")
    79  			})
    80  
    81  			Convey("output status and task status", func() {
    82  				b := &model.Build{
    83  					ID: 1,
    84  					Proto: &pb.Build{
    85  						Id: 1,
    86  						Builder: &pb.BuilderID{
    87  							Project: "project",
    88  							Bucket:  "bucket",
    89  							Builder: "builder",
    90  						},
    91  						Status: pb.Status_SCHEDULED,
    92  					},
    93  					Status: pb.Status_SCHEDULED,
    94  				}
    95  				So(datastore.Put(ctx, b), ShouldBeNil)
    96  				u := &Updater{
    97  					Build:        b,
    98  					OutputStatus: &StatusWithDetails{Status: pb.Status_SUCCESS},
    99  					TaskStatus:   &StatusWithDetails{Status: pb.Status_SUCCESS},
   100  				}
   101  				_, err := update(ctx, u)
   102  				So(err, ShouldErrLike, "impossible: update build output status and task status at the same time")
   103  			})
   104  
   105  			Convey("nothing is provided to update", func() {
   106  				b := &model.Build{
   107  					ID: 1,
   108  					Proto: &pb.Build{
   109  						Id: 1,
   110  						Builder: &pb.BuilderID{
   111  							Project: "project",
   112  							Bucket:  "bucket",
   113  							Builder: "builder",
   114  						},
   115  						Status: pb.Status_SCHEDULED,
   116  					},
   117  					Status: pb.Status_SCHEDULED,
   118  				}
   119  				So(datastore.Put(ctx, b), ShouldBeNil)
   120  				u := &Updater{
   121  					Build: b,
   122  				}
   123  				_, err := update(ctx, u)
   124  				So(err, ShouldErrLike, "cannot set a build status to UNSPECIFIED")
   125  			})
   126  
   127  			Convey("BuildStatus not found", func() {
   128  				b := &model.Build{
   129  					ID: 1,
   130  					Proto: &pb.Build{
   131  						Id: 1,
   132  						Builder: &pb.BuilderID{
   133  							Project: "project",
   134  							Bucket:  "bucket",
   135  							Builder: "builder",
   136  						},
   137  						Status: pb.Status_SCHEDULED,
   138  					},
   139  					Status: pb.Status_SCHEDULED,
   140  				}
   141  				So(datastore.Put(ctx, b), ShouldBeNil)
   142  				u := &Updater{
   143  					Build:       b,
   144  					BuildStatus: &StatusWithDetails{Status: pb.Status_SUCCESS},
   145  				}
   146  				_, err := update(ctx, u)
   147  				So(err, ShouldErrLike, "not found")
   148  			})
   149  		})
   150  
   151  		Convey("pass", func() {
   152  
   153  			b := &model.Build{
   154  				ID: 87654321,
   155  				Proto: &pb.Build{
   156  					Id: 87654321,
   157  					Builder: &pb.BuilderID{
   158  						Project: "project",
   159  						Bucket:  "bucket",
   160  						Builder: "builder",
   161  					},
   162  					Output: &pb.Build_Output{},
   163  					Status: pb.Status_SCHEDULED,
   164  				},
   165  				Status: pb.Status_SCHEDULED,
   166  			}
   167  			bk := datastore.KeyForObj(ctx, b)
   168  			bs := &model.BuildStatus{
   169  				Build:  bk,
   170  				Status: pb.Status_SCHEDULED,
   171  			}
   172  			So(datastore.Put(ctx, b, bs), ShouldBeNil)
   173  			updatedStatus := b.Proto.Status
   174  			updatedStatusDetails := b.Proto.StatusDetails
   175  			u := &Updater{
   176  				Build: b,
   177  				PostProcess: func(c context.Context, bld *model.Build) error {
   178  					updatedStatus = bld.Proto.Status
   179  					updatedStatusDetails = bld.Proto.StatusDetails
   180  					return nil
   181  				},
   182  			}
   183  
   184  			Convey("direct update on build status ignore sub status", func() {
   185  				u.BuildStatus = &StatusWithDetails{Status: pb.Status_STARTED}
   186  				u.OutputStatus = &StatusWithDetails{Status: pb.Status_SUCCESS} // only for test, impossible in practice
   187  				bs, err := update(ctx, u)
   188  				So(err, ShouldBeNil)
   189  				So(bs.Status, ShouldEqual, pb.Status_STARTED)
   190  				So(updatedStatus, ShouldEqual, pb.Status_STARTED)
   191  			})
   192  
   193  			Convey("update output status", func() {
   194  				Convey("start, so build status is updated", func() {
   195  					u.OutputStatus = &StatusWithDetails{Status: pb.Status_STARTED}
   196  					bs, err := update(ctx, u)
   197  					So(err, ShouldBeNil)
   198  					So(bs.Status, ShouldEqual, pb.Status_STARTED)
   199  					So(updatedStatus, ShouldEqual, pb.Status_STARTED)
   200  				})
   201  
   202  				Convey("end, so build status is unchanged", func() {
   203  					u.OutputStatus = &StatusWithDetails{Status: pb.Status_SUCCESS}
   204  					bs, err := update(ctx, u)
   205  					So(err, ShouldBeNil)
   206  					So(bs, ShouldBeNil)
   207  					So(updatedStatus, ShouldEqual, pb.Status_SCHEDULED)
   208  				})
   209  			})
   210  
   211  			Convey("update task status", func() {
   212  				Convey("end, so build status is updated", func() {
   213  					b.Proto.Output.Status = pb.Status_SUCCESS
   214  					So(datastore.Put(ctx, b), ShouldBeNil)
   215  					u.TaskStatus = &StatusWithDetails{Status: pb.Status_SUCCESS}
   216  					bs, err := update(ctx, u)
   217  					So(err, ShouldBeNil)
   218  					So(bs.Status, ShouldEqual, pb.Status_SUCCESS)
   219  					So(updatedStatus, ShouldEqual, pb.Status_SUCCESS)
   220  				})
   221  
   222  				Convey("start, so build status is unchanged", func() {
   223  					b.Proto.Output.Status = pb.Status_STARTED
   224  					So(datastore.Put(ctx, b), ShouldBeNil)
   225  					u.TaskStatus = &StatusWithDetails{Status: pb.Status_STARTED}
   226  					bs, err := update(ctx, u)
   227  					So(err, ShouldBeNil)
   228  					So(bs, ShouldBeNil)
   229  					So(updatedStatus, ShouldEqual, pb.Status_SCHEDULED)
   230  				})
   231  
   232  				Convey("final status based on both statuses", func() {
   233  					// output status is from the build entity.
   234  					Convey("output status not ended when task status success", func() {
   235  						b.Proto.Output.Status = pb.Status_STARTED
   236  						So(datastore.Put(ctx, b), ShouldBeNil)
   237  						u.TaskStatus = &StatusWithDetails{Status: pb.Status_SUCCESS}
   238  						bs, err := update(ctx, u)
   239  						So(err, ShouldBeNil)
   240  						So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE)
   241  						So(updatedStatus, ShouldEqual, pb.Status_INFRA_FAILURE)
   242  					})
   243  					Convey("output status not ended when task status fail", func() {
   244  						b.Proto.Output.Status = pb.Status_STARTED
   245  						So(datastore.Put(ctx, b), ShouldBeNil)
   246  						u.TaskStatus = &StatusWithDetails{
   247  							Status: pb.Status_INFRA_FAILURE,
   248  							Details: &pb.StatusDetails{
   249  								ResourceExhaustion: &pb.StatusDetails_ResourceExhaustion{},
   250  							},
   251  						}
   252  						bs, err := update(ctx, u)
   253  						So(err, ShouldBeNil)
   254  						So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE)
   255  						So(updatedStatus, ShouldEqual, pb.Status_INFRA_FAILURE)
   256  					})
   257  					Convey("output status SUCCESS, task status FAILURE", func() {
   258  						b.Proto.Output.Status = pb.Status_SUCCESS
   259  						So(datastore.Put(ctx, b), ShouldBeNil)
   260  						u.TaskStatus = &StatusWithDetails{Status: pb.Status_FAILURE}
   261  						bs, err := update(ctx, u)
   262  						So(err, ShouldBeNil)
   263  						So(bs.Status, ShouldEqual, pb.Status_FAILURE)
   264  						So(updatedStatus, ShouldEqual, pb.Status_FAILURE)
   265  					})
   266  					Convey("output status SUCCESS, task status Status_INFRA_FAILURE", func() {
   267  						b.Proto.Output.Status = pb.Status_SUCCESS
   268  						So(datastore.Put(ctx, b), ShouldBeNil)
   269  						u.TaskStatus = &StatusWithDetails{Status: pb.Status_INFRA_FAILURE}
   270  						bs, err := update(ctx, u)
   271  						So(err, ShouldBeNil)
   272  						So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE)
   273  						So(updatedStatus, ShouldEqual, pb.Status_INFRA_FAILURE)
   274  					})
   275  					Convey("output status FAILURE, task status PASS", func() {
   276  						b.Proto.Output.Status = pb.Status_FAILURE
   277  						So(datastore.Put(ctx, b), ShouldBeNil)
   278  						u.TaskStatus = &StatusWithDetails{Status: pb.Status_SUCCESS}
   279  						bs, err := update(ctx, u)
   280  						So(err, ShouldBeNil)
   281  						So(bs.Status, ShouldEqual, pb.Status_FAILURE)
   282  						So(updatedStatus, ShouldEqual, pb.Status_FAILURE)
   283  					})
   284  					Convey("output status FAILURE, task status INFRA_FAILURE", func() {
   285  						b.Proto.Output.Status = pb.Status_FAILURE
   286  						So(datastore.Put(ctx, b), ShouldBeNil)
   287  						u.TaskStatus = &StatusWithDetails{
   288  							Status: pb.Status_INFRA_FAILURE,
   289  							Details: &pb.StatusDetails{
   290  								ResourceExhaustion: &pb.StatusDetails_ResourceExhaustion{},
   291  							},
   292  						}
   293  						bs, err := update(ctx, u)
   294  						So(err, ShouldBeNil)
   295  						So(bs.Status, ShouldEqual, pb.Status_FAILURE)
   296  						So(updatedStatus, ShouldEqual, pb.Status_FAILURE)
   297  					})
   298  					Convey("both infra_failure with different details", func() {
   299  						b.Proto.Output.Status = pb.Status_INFRA_FAILURE
   300  						b.Proto.Output.StatusDetails = &pb.StatusDetails{
   301  							Timeout: &pb.StatusDetails_Timeout{},
   302  						}
   303  						So(datastore.Put(ctx, b), ShouldBeNil)
   304  						u.TaskStatus = &StatusWithDetails{
   305  							Status: pb.Status_INFRA_FAILURE,
   306  							Details: &pb.StatusDetails{
   307  								ResourceExhaustion: &pb.StatusDetails_ResourceExhaustion{},
   308  							},
   309  						}
   310  						bs, err := update(ctx, u)
   311  						So(err, ShouldBeNil)
   312  						So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE)
   313  						So(updatedStatus, ShouldEqual, pb.Status_INFRA_FAILURE)
   314  						So(updatedStatusDetails, ShouldResembleProto, &pb.StatusDetails{
   315  							Timeout: &pb.StatusDetails_Timeout{},
   316  						})
   317  					})
   318  				})
   319  			})
   320  		})
   321  	})
   322  }