go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/run/impl/handler/triggers_test.go (about)

     1  // Copyright 2021 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 handler
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"google.golang.org/protobuf/proto"
    23  	"google.golang.org/protobuf/types/known/timestamppb"
    24  
    25  	cfgpb "go.chromium.org/luci/cv/api/config/v2"
    26  	"go.chromium.org/luci/cv/internal/changelist"
    27  	"go.chromium.org/luci/cv/internal/configs/prjcfg/prjcfgtest"
    28  	"go.chromium.org/luci/cv/internal/cvtesting"
    29  	"go.chromium.org/luci/cv/internal/run"
    30  	"go.chromium.org/luci/cv/internal/run/eventpb"
    31  	"go.chromium.org/luci/cv/internal/run/impl/state"
    32  
    33  	. "github.com/smartystreets/goconvey/convey"
    34  )
    35  
    36  func TestOnCompletedResetTriggers(t *testing.T) {
    37  	t.Parallel()
    38  
    39  	Convey("OnCompletedResetTriggers works", t, func() {
    40  		ct := cvtesting.Test{}
    41  		ctx, cancel := ct.SetUp(t)
    42  		defer cancel()
    43  
    44  		const (
    45  			lProject = "chromium"
    46  			gHost    = "example-review.googlesource.com"
    47  			opID     = "1-1"
    48  		)
    49  
    50  		prjcfgtest.Create(ctx, lProject, &cfgpb.Config{ConfigGroups: []*cfgpb.ConfigGroup{{Name: "single"}}})
    51  
    52  		rs := &state.RunState{
    53  			Run: run.Run{
    54  				ID:            lProject + "/1111111111111-1-deadbeef",
    55  				Status:        run.Status_RUNNING,
    56  				Mode:          run.DryRun,
    57  				ConfigGroupID: prjcfgtest.MustExist(ctx, lProject).ConfigGroupIDs[0],
    58  				OngoingLongOps: &run.OngoingLongOps{
    59  					Ops: map[string]*run.OngoingLongOps_Op{
    60  						opID: {
    61  							Work: &run.OngoingLongOps_Op_ResetTriggers_{
    62  								ResetTriggers: &run.OngoingLongOps_Op_ResetTriggers{
    63  									Requests: []*run.OngoingLongOps_Op_ResetTriggers_Request{
    64  										{Clid: 1},
    65  									},
    66  									RunStatusIfSucceeded: run.Status_SUCCEEDED,
    67  								},
    68  							},
    69  						},
    70  					},
    71  				},
    72  			},
    73  		}
    74  		result := &eventpb.LongOpCompleted{
    75  			OperationId: opID,
    76  		}
    77  		now := ct.Clock.Now()
    78  		h, _ := makeTestHandler(&ct)
    79  
    80  		assertHasLogEntry := func(rs *state.RunState, target *run.LogEntry) {
    81  			for _, le := range rs.LogEntries {
    82  				if proto.Equal(target, le) {
    83  					return
    84  				}
    85  			}
    86  			So(fmt.Sprintf("log entry is missing: %s", target), ShouldBeEmpty)
    87  		}
    88  
    89  		Convey("on expiration", func() {
    90  			result.Status = eventpb.LongOpCompleted_EXPIRED
    91  			res, err := h.OnLongOpCompleted(ctx, rs, result)
    92  			So(err, ShouldBeNil)
    93  			So(res.State.Status, ShouldEqual, run.Status_FAILED)
    94  			for _, op := range res.State.OngoingLongOps.GetOps() {
    95  				if op.GetExecutePostAction() == nil {
    96  					SoMsg("should not contain any long op other than post action", op.GetWork(), ShouldBeNil)
    97  				}
    98  			}
    99  			assertHasLogEntry(res.State, &run.LogEntry{
   100  				Time: timestamppb.New(now),
   101  				Kind: &run.LogEntry_Info_{
   102  					Info: &run.LogEntry_Info{
   103  						Label:   logEntryLabelResetTriggers,
   104  						Message: fmt.Sprintf("failed to reset the triggers of CLs within the %s deadline", maxResetTriggersDuration),
   105  					},
   106  				},
   107  			})
   108  			So(res.SideEffectFn, ShouldNotBeNil)
   109  			So(res.PreserveEvents, ShouldBeFalse)
   110  		})
   111  
   112  		Convey("on failure", func() {
   113  			result.Status = eventpb.LongOpCompleted_FAILED
   114  			result.Result = &eventpb.LongOpCompleted_ResetTriggers_{
   115  				ResetTriggers: &eventpb.LongOpCompleted_ResetTriggers{
   116  					Results: []*eventpb.LongOpCompleted_ResetTriggers_Result{
   117  						{
   118  							Id:         1,
   119  							ExternalId: string(changelist.MustGobID(gHost, 111)),
   120  							Detail: &eventpb.LongOpCompleted_ResetTriggers_Result_FailureInfo{
   121  								FailureInfo: &eventpb.LongOpCompleted_ResetTriggers_Result_Failure{
   122  									FailureMessage: "no permission to vote",
   123  								},
   124  							},
   125  						},
   126  					},
   127  				},
   128  			}
   129  			res, err := h.OnLongOpCompleted(ctx, rs, result)
   130  			So(err, ShouldBeNil)
   131  			So(res.State.Status, ShouldEqual, run.Status_FAILED)
   132  			for _, op := range res.State.OngoingLongOps.GetOps() {
   133  				if op.GetExecutePostAction() == nil {
   134  					SoMsg("should not contain any long op other than post action", op.GetWork(), ShouldBeNil)
   135  				}
   136  			}
   137  			assertHasLogEntry(res.State, &run.LogEntry{
   138  				Time: timestamppb.New(now),
   139  				Kind: &run.LogEntry_Info_{
   140  					Info: &run.LogEntry_Info{
   141  						Label:   logEntryLabelResetTriggers,
   142  						Message: "failed to reset the trigger of change https://example-review.googlesource.com/c/111. Reason: no permission to vote",
   143  					},
   144  				},
   145  			})
   146  			So(res.SideEffectFn, ShouldNotBeNil)
   147  			So(res.PreserveEvents, ShouldBeFalse)
   148  		})
   149  
   150  		Convey("on success", func() {
   151  			result.Status = eventpb.LongOpCompleted_SUCCEEDED
   152  			result.Result = &eventpb.LongOpCompleted_ResetTriggers_{
   153  				ResetTriggers: &eventpb.LongOpCompleted_ResetTriggers{
   154  					Results: []*eventpb.LongOpCompleted_ResetTriggers_Result{
   155  						{
   156  							Id:         1,
   157  							ExternalId: string(changelist.MustGobID(gHost, 111)),
   158  							Detail: &eventpb.LongOpCompleted_ResetTriggers_Result_SuccessInfo{
   159  								SuccessInfo: &eventpb.LongOpCompleted_ResetTriggers_Result_Success{
   160  									ResetAt: timestamppb.New(now.Add(-1 * time.Minute)),
   161  								},
   162  							},
   163  						},
   164  					},
   165  				},
   166  			}
   167  			res, err := h.OnLongOpCompleted(ctx, rs, result)
   168  			So(err, ShouldBeNil)
   169  			So(res.State.Status, ShouldEqual, run.Status_SUCCEEDED)
   170  			for _, op := range res.State.OngoingLongOps.GetOps() {
   171  				if op.GetExecutePostAction() == nil {
   172  					SoMsg("should not contain any long op other than post action", op.GetWork(), ShouldBeNil)
   173  				}
   174  			}
   175  			assertHasLogEntry(res.State, &run.LogEntry{
   176  				Time: timestamppb.New(now.Add(-1 * time.Minute)),
   177  				Kind: &run.LogEntry_Info_{
   178  					Info: &run.LogEntry_Info{
   179  						Label:   logEntryLabelResetTriggers,
   180  						Message: "successfully reset the trigger of change https://example-review.googlesource.com/c/111",
   181  					},
   182  				},
   183  			})
   184  			So(res.SideEffectFn, ShouldNotBeNil)
   185  			So(res.PreserveEvents, ShouldBeFalse)
   186  		})
   187  
   188  		Convey("on partial failure", func() {
   189  			result.Status = eventpb.LongOpCompleted_FAILED
   190  			rs.OngoingLongOps.GetOps()[opID].GetResetTriggers().Requests =
   191  				[]*run.OngoingLongOps_Op_ResetTriggers_Request{
   192  					{Clid: 1},
   193  					{Clid: 2},
   194  				}
   195  			result.Result = &eventpb.LongOpCompleted_ResetTriggers_{
   196  				ResetTriggers: &eventpb.LongOpCompleted_ResetTriggers{
   197  					Results: []*eventpb.LongOpCompleted_ResetTriggers_Result{
   198  						{
   199  							Id:         1,
   200  							ExternalId: string(changelist.MustGobID(gHost, 111)),
   201  							Detail: &eventpb.LongOpCompleted_ResetTriggers_Result_SuccessInfo{
   202  								SuccessInfo: &eventpb.LongOpCompleted_ResetTriggers_Result_Success{
   203  									ResetAt: timestamppb.New(now.Add(-1 * time.Minute)),
   204  								},
   205  							},
   206  						},
   207  						{
   208  							Id:         2,
   209  							ExternalId: string(changelist.MustGobID(gHost, 222)),
   210  							Detail: &eventpb.LongOpCompleted_ResetTriggers_Result_FailureInfo{
   211  								FailureInfo: &eventpb.LongOpCompleted_ResetTriggers_Result_Failure{
   212  									FailureMessage: "no permission to vote",
   213  								},
   214  							},
   215  						},
   216  					},
   217  				},
   218  			}
   219  			res, err := h.OnLongOpCompleted(ctx, rs, result)
   220  			So(err, ShouldBeNil)
   221  			So(res.State.Status, ShouldEqual, run.Status_FAILED)
   222  			for _, op := range res.State.OngoingLongOps.GetOps() {
   223  				if op.GetExecutePostAction() == nil {
   224  					SoMsg("should not contain any long op other than post action", op.GetWork(), ShouldBeNil)
   225  				}
   226  			}
   227  			assertHasLogEntry(res.State, &run.LogEntry{
   228  				Time: timestamppb.New(now.Add(-1 * time.Minute)),
   229  				Kind: &run.LogEntry_Info_{
   230  					Info: &run.LogEntry_Info{
   231  						Label:   logEntryLabelResetTriggers,
   232  						Message: "successfully reset the trigger of change https://example-review.googlesource.com/c/111",
   233  					},
   234  				},
   235  			})
   236  			assertHasLogEntry(res.State, &run.LogEntry{
   237  				Time: timestamppb.New(now),
   238  				Kind: &run.LogEntry_Info_{
   239  					Info: &run.LogEntry_Info{
   240  						Label:   logEntryLabelResetTriggers,
   241  						Message: "failed to reset the trigger of change https://example-review.googlesource.com/c/222. Reason: no permission to vote",
   242  					},
   243  				},
   244  			})
   245  			So(res.SideEffectFn, ShouldNotBeNil)
   246  			So(res.PreserveEvents, ShouldBeFalse)
   247  		})
   248  	})
   249  }