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 }