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 }