go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/tasks/cancel_test.go (about)

     1  // Copyright 2022 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 tasks
    16  
    17  import (
    18  	"context"
    19  	"sort"
    20  	"testing"
    21  	"time"
    22  
    23  	"google.golang.org/protobuf/types/known/timestamppb"
    24  
    25  	"go.chromium.org/luci/common/clock/testclock"
    26  	"go.chromium.org/luci/gae/filter/txndefer"
    27  	"go.chromium.org/luci/gae/impl/memory"
    28  	"go.chromium.org/luci/gae/service/datastore"
    29  	"go.chromium.org/luci/server/tq"
    30  
    31  	"go.chromium.org/luci/buildbucket/appengine/internal/metrics"
    32  	"go.chromium.org/luci/buildbucket/appengine/model"
    33  	taskdefs "go.chromium.org/luci/buildbucket/appengine/tasks/defs"
    34  	pb "go.chromium.org/luci/buildbucket/proto"
    35  
    36  	. "github.com/smartystreets/goconvey/convey"
    37  	. "go.chromium.org/luci/common/testing/assertions"
    38  )
    39  
    40  func TestCancelBuild(t *testing.T) {
    41  	Convey("Do", t, func() {
    42  		ctx := txndefer.FilterRDS(memory.Use(context.Background()))
    43  		ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins")
    44  		datastore.GetTestable(ctx).AutoIndex(true)
    45  		datastore.GetTestable(ctx).Consistent(true)
    46  		ctx, sch := tq.TestingContext(ctx, nil)
    47  		now := testclock.TestRecentTimeLocal
    48  		ctx, _ = testclock.UseTime(ctx, now)
    49  
    50  		Convey("not found", func() {
    51  			bld, err := Cancel(ctx, 1)
    52  			So(err, ShouldErrLike, "not found")
    53  			So(bld, ShouldBeNil)
    54  			So(sch.Tasks(), ShouldBeEmpty)
    55  		})
    56  
    57  		Convey("found", func() {
    58  			So(datastore.Put(ctx, &model.Build{
    59  				Proto: &pb.Build{
    60  					Id: 1,
    61  					Builder: &pb.BuilderID{
    62  						Project: "project",
    63  						Bucket:  "bucket",
    64  						Builder: "builder",
    65  					},
    66  				},
    67  			}), ShouldBeNil)
    68  			So(datastore.Put(ctx, &model.BuildStatus{
    69  				Build:  datastore.MakeKey(ctx, "Build", 1),
    70  				Status: pb.Status_SCHEDULED,
    71  			}), ShouldBeNil)
    72  			bld, err := Cancel(ctx, 1)
    73  			So(err, ShouldBeNil)
    74  			So(bld.Proto, ShouldResembleProto, &pb.Build{
    75  				Id: 1,
    76  				Builder: &pb.BuilderID{
    77  					Project: "project",
    78  					Bucket:  "bucket",
    79  					Builder: "builder",
    80  				},
    81  				UpdateTime: timestamppb.New(now),
    82  				EndTime:    timestamppb.New(now),
    83  				Status:     pb.Status_CANCELED,
    84  			})
    85  			So(sch.Tasks(), ShouldHaveLength, 3)
    86  			bs := &model.BuildStatus{
    87  				Build: datastore.MakeKey(ctx, "Build", 1),
    88  			}
    89  			So(datastore.Get(ctx, bs), ShouldBeNil)
    90  			So(bs.Status, ShouldEqual, pb.Status_CANCELED)
    91  		})
    92  
    93  		Convey("ended", func() {
    94  			So(datastore.Put(ctx, &model.Build{
    95  				Proto: &pb.Build{
    96  					Id: 1,
    97  					Builder: &pb.BuilderID{
    98  						Project: "project",
    99  						Bucket:  "bucket",
   100  						Builder: "builder",
   101  					},
   102  					Status: pb.Status_SUCCESS,
   103  				},
   104  			}), ShouldBeNil)
   105  			So(datastore.Put(ctx, &model.BuildStatus{
   106  				Build:  datastore.MakeKey(ctx, "Build", 1),
   107  				Status: pb.Status_SUCCESS,
   108  			}), ShouldBeNil)
   109  			bld, err := Cancel(ctx, 1)
   110  			So(err, ShouldBeNil)
   111  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   112  				Id: 1,
   113  				Builder: &pb.BuilderID{
   114  					Project: "project",
   115  					Bucket:  "bucket",
   116  					Builder: "builder",
   117  				},
   118  				Status: pb.Status_SUCCESS,
   119  			})
   120  			So(sch.Tasks(), ShouldBeEmpty)
   121  		})
   122  
   123  		Convey("swarming task cancellation", func() {
   124  			So(datastore.Put(ctx, &model.Build{
   125  				Proto: &pb.Build{
   126  					Id: 1,
   127  					Builder: &pb.BuilderID{
   128  						Project: "project",
   129  						Bucket:  "bucket",
   130  						Builder: "builder",
   131  					},
   132  				},
   133  			}), ShouldBeNil)
   134  			So(datastore.Put(ctx, &model.BuildInfra{
   135  				Build: datastore.MakeKey(ctx, "Build", 1),
   136  				Proto: &pb.BuildInfra{
   137  					Swarming: &pb.BuildInfra_Swarming{
   138  						Hostname: "example.com",
   139  						TaskId:   "id",
   140  					},
   141  				},
   142  			}), ShouldBeNil)
   143  			So(datastore.Put(ctx, &model.BuildStatus{
   144  				Build:  datastore.MakeKey(ctx, "Build", 1),
   145  				Status: pb.Status_STARTED,
   146  			}), ShouldBeNil)
   147  			bld, err := Cancel(ctx, 1)
   148  			So(err, ShouldBeNil)
   149  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   150  				Id: 1,
   151  				Builder: &pb.BuilderID{
   152  					Project: "project",
   153  					Bucket:  "bucket",
   154  					Builder: "builder",
   155  				},
   156  				UpdateTime: timestamppb.New(now),
   157  				EndTime:    timestamppb.New(now),
   158  				Status:     pb.Status_CANCELED,
   159  			})
   160  			So(sch.Tasks(), ShouldHaveLength, 4)
   161  			bs := &model.BuildStatus{
   162  				Build: datastore.MakeKey(ctx, "Build", 1),
   163  			}
   164  			So(datastore.Get(ctx, bs), ShouldBeNil)
   165  			So(bs.Status, ShouldEqual, pb.Status_CANCELED)
   166  		})
   167  
   168  		Convey("backend task cancellation", func() {
   169  			So(datastore.Put(ctx, &model.Build{
   170  				Proto: &pb.Build{
   171  					Id: 1,
   172  					Builder: &pb.BuilderID{
   173  						Project: "project",
   174  						Bucket:  "bucket",
   175  						Builder: "builder",
   176  					},
   177  				},
   178  			}), ShouldBeNil)
   179  			So(datastore.Put(ctx, &model.BuildInfra{
   180  				Build: datastore.MakeKey(ctx, "Build", 1),
   181  				Proto: &pb.BuildInfra{
   182  					Backend: &pb.BuildInfra_Backend{
   183  						Hostname: "example.com",
   184  						Task: &pb.Task{
   185  							Id: &pb.TaskID{
   186  								Id:     "123",
   187  								Target: "swarming://chromium-swarmin-dev",
   188  							},
   189  							Status: pb.Status_STARTED,
   190  						},
   191  					},
   192  				},
   193  			}), ShouldBeNil)
   194  			So(datastore.Put(ctx, &model.BuildStatus{
   195  				Build:  datastore.MakeKey(ctx, "Build", 1),
   196  				Status: pb.Status_STARTED,
   197  			}), ShouldBeNil)
   198  			bld, err := Cancel(ctx, 1)
   199  			So(err, ShouldBeNil)
   200  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   201  				Id: 1,
   202  				Builder: &pb.BuilderID{
   203  					Project: "project",
   204  					Bucket:  "bucket",
   205  					Builder: "builder",
   206  				},
   207  				UpdateTime: timestamppb.New(now),
   208  				EndTime:    timestamppb.New(now),
   209  				Status:     pb.Status_CANCELED,
   210  			})
   211  			So(sch.Tasks(), ShouldHaveLength, 4)
   212  			bs := &model.BuildStatus{
   213  				Build: datastore.MakeKey(ctx, "Build", 1),
   214  			}
   215  			So(datastore.Get(ctx, bs), ShouldBeNil)
   216  			So(bs.Status, ShouldEqual, pb.Status_CANCELED)
   217  		})
   218  
   219  		Convey("resultdb finalization", func() {
   220  			So(datastore.Put(ctx, &model.Build{
   221  				Proto: &pb.Build{
   222  					Id: 1,
   223  					Builder: &pb.BuilderID{
   224  						Project: "project",
   225  						Bucket:  "bucket",
   226  						Builder: "builder",
   227  					},
   228  				},
   229  			}), ShouldBeNil)
   230  			So(datastore.Put(ctx, &model.BuildInfra{
   231  				Build: datastore.MakeKey(ctx, "Build", 1),
   232  				Proto: &pb.BuildInfra{
   233  					Resultdb: &pb.BuildInfra_ResultDB{
   234  						Hostname:   "example.com",
   235  						Invocation: "id",
   236  					},
   237  				},
   238  			}), ShouldBeNil)
   239  			So(datastore.Put(ctx, &model.BuildStatus{
   240  				Build:  datastore.MakeKey(ctx, "Build", 1),
   241  				Status: pb.Status_STARTED,
   242  			}), ShouldBeNil)
   243  			bld, err := Cancel(ctx, 1)
   244  			So(err, ShouldBeNil)
   245  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   246  				Id: 1,
   247  				Builder: &pb.BuilderID{
   248  					Project: "project",
   249  					Bucket:  "bucket",
   250  					Builder: "builder",
   251  				},
   252  				UpdateTime: timestamppb.New(now),
   253  				EndTime:    timestamppb.New(now),
   254  				Status:     pb.Status_CANCELED,
   255  			})
   256  			So(sch.Tasks(), ShouldHaveLength, 4)
   257  			bs := &model.BuildStatus{
   258  				Build: datastore.MakeKey(ctx, "Build", 1),
   259  			}
   260  			So(datastore.Get(ctx, bs), ShouldBeNil)
   261  			So(bs.Status, ShouldEqual, pb.Status_CANCELED)
   262  		})
   263  	})
   264  
   265  	Convey("Start", t, func() {
   266  		ctx := txndefer.FilterRDS(memory.Use(context.Background()))
   267  		datastore.GetTestable(ctx).AutoIndex(true)
   268  		datastore.GetTestable(ctx).Consistent(true)
   269  		ctx, sch := tq.TestingContext(ctx, nil)
   270  		now := testclock.TestRecentTimeLocal
   271  		ctx, _ = testclock.UseTime(ctx, now)
   272  
   273  		Convey("not found", func() {
   274  			_, err := StartCancel(ctx, 1, "")
   275  			So(err, ShouldErrLike, "not found")
   276  			So(sch.Tasks(), ShouldBeEmpty)
   277  		})
   278  
   279  		Convey("found", func() {
   280  			So(datastore.Put(ctx, &model.Build{
   281  				Proto: &pb.Build{
   282  					Id: 1,
   283  					Builder: &pb.BuilderID{
   284  						Project: "project",
   285  						Bucket:  "bucket",
   286  						Builder: "builder",
   287  					},
   288  					Status: pb.Status_STARTED,
   289  				},
   290  			}), ShouldBeNil)
   291  			bld, err := StartCancel(ctx, 1, "summary")
   292  			So(err, ShouldBeNil)
   293  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   294  				Id: 1,
   295  				Builder: &pb.BuilderID{
   296  					Project: "project",
   297  					Bucket:  "bucket",
   298  					Builder: "builder",
   299  				},
   300  				UpdateTime:           timestamppb.New(now),
   301  				Status:               pb.Status_STARTED,
   302  				CancelTime:           timestamppb.New(now),
   303  				CanceledBy:           "buildbucket",
   304  				CancellationMarkdown: "summary",
   305  			})
   306  			So(sch.Tasks(), ShouldHaveLength, 1)
   307  		})
   308  
   309  		Convey("ended", func() {
   310  			So(datastore.Put(ctx, &model.Build{
   311  				Proto: &pb.Build{
   312  					Id: 1,
   313  					Builder: &pb.BuilderID{
   314  						Project: "project",
   315  						Bucket:  "bucket",
   316  						Builder: "builder",
   317  					},
   318  					Status: pb.Status_SUCCESS,
   319  				},
   320  			}), ShouldBeNil)
   321  			bld, err := StartCancel(ctx, 1, "summary")
   322  			So(err, ShouldBeNil)
   323  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   324  				Id: 1,
   325  				Builder: &pb.BuilderID{
   326  					Project: "project",
   327  					Bucket:  "bucket",
   328  					Builder: "builder",
   329  				},
   330  				Status: pb.Status_SUCCESS,
   331  			})
   332  			So(sch.Tasks(), ShouldBeEmpty)
   333  		})
   334  
   335  		Convey("in cancel process", func() {
   336  			So(datastore.Put(ctx, &model.Build{
   337  				Proto: &pb.Build{
   338  					Id: 1,
   339  					Builder: &pb.BuilderID{
   340  						Project: "project",
   341  						Bucket:  "bucket",
   342  						Builder: "builder",
   343  					},
   344  					Status:     pb.Status_STARTED,
   345  					CancelTime: timestamppb.New(now.Add(-time.Minute)),
   346  				},
   347  			}), ShouldBeNil)
   348  			bld, err := StartCancel(ctx, 1, "summary")
   349  			So(err, ShouldBeNil)
   350  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   351  				Id: 1,
   352  				Builder: &pb.BuilderID{
   353  					Project: "project",
   354  					Bucket:  "bucket",
   355  					Builder: "builder",
   356  				},
   357  				Status:     pb.Status_STARTED,
   358  				CancelTime: timestamppb.New(now.Add(-time.Minute)),
   359  			})
   360  			So(sch.Tasks(), ShouldBeEmpty)
   361  		})
   362  
   363  		Convey("w/ decedents", func() {
   364  			So(datastore.Put(ctx, &model.Build{
   365  				ID: 1,
   366  				Proto: &pb.Build{
   367  					Id: 1,
   368  					Builder: &pb.BuilderID{
   369  						Project: "project",
   370  						Bucket:  "bucket",
   371  						Builder: "builder",
   372  					},
   373  				},
   374  			}), ShouldBeNil)
   375  			// Child can outlive parent.
   376  			So(datastore.Put(ctx, &model.Build{
   377  				ID: 2,
   378  				Proto: &pb.Build{
   379  					Id: 2,
   380  					Builder: &pb.BuilderID{
   381  						Project: "project",
   382  						Bucket:  "bucket",
   383  						Builder: "builder",
   384  					},
   385  					AncestorIds:      []int64{1},
   386  					CanOutliveParent: true,
   387  				},
   388  			}), ShouldBeNil)
   389  			// Child cannot outlive parent.
   390  			So(datastore.Put(ctx, &model.Build{
   391  				ID: 3,
   392  				Proto: &pb.Build{
   393  					Id: 3,
   394  					Builder: &pb.BuilderID{
   395  						Project: "project",
   396  						Bucket:  "bucket",
   397  						Builder: "builder",
   398  					},
   399  					AncestorIds:      []int64{1},
   400  					CanOutliveParent: false,
   401  				},
   402  			}), ShouldBeNil)
   403  			// Grandchild.
   404  			So(datastore.Put(ctx, &model.Build{
   405  				ID: 4,
   406  				Proto: &pb.Build{
   407  					Id: 3,
   408  					Builder: &pb.BuilderID{
   409  						Project: "project",
   410  						Bucket:  "bucket",
   411  						Builder: "builder",
   412  					},
   413  					AncestorIds:      []int64{1, 3},
   414  					CanOutliveParent: false,
   415  				},
   416  			}), ShouldBeNil)
   417  			bld, err := StartCancel(ctx, 1, "summary")
   418  			So(err, ShouldBeNil)
   419  			So(bld.Proto, ShouldResembleProto, &pb.Build{
   420  				Id: 1,
   421  				Builder: &pb.BuilderID{
   422  					Project: "project",
   423  					Bucket:  "bucket",
   424  					Builder: "builder",
   425  				},
   426  				UpdateTime:           timestamppb.New(now),
   427  				CancelTime:           timestamppb.New(now),
   428  				CancellationMarkdown: "summary",
   429  				CanceledBy:           "buildbucket",
   430  			})
   431  			ids := make([]int, len(sch.Tasks()))
   432  			for i, task := range sch.Tasks() {
   433  				switch v := task.Payload.(type) {
   434  				case *taskdefs.CancelBuildTask:
   435  					ids[i] = int(v.BuildId)
   436  				default:
   437  					panic("unexpected task payload")
   438  				}
   439  			}
   440  			So(sch.Tasks(), ShouldHaveLength, 3)
   441  			sort.Ints(ids)
   442  			So(ids, ShouldResemble, []int{1, 3, 4})
   443  		})
   444  	})
   445  }