go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/rerun/rerun_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 rerun 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 "go.chromium.org/luci/bisection/internal/buildbucket" 23 "go.chromium.org/luci/bisection/model" 24 pb "go.chromium.org/luci/bisection/proto/v1" 25 "go.chromium.org/luci/bisection/util" 26 "go.chromium.org/luci/bisection/util/testutil" 27 28 "github.com/golang/mock/gomock" 29 . "github.com/smartystreets/goconvey/convey" 30 bbpb "go.chromium.org/luci/buildbucket/proto" 31 . "go.chromium.org/luci/common/testing/assertions" 32 "google.golang.org/protobuf/types/known/structpb" 33 "google.golang.org/protobuf/types/known/timestamppb" 34 35 "go.chromium.org/luci/common/clock" 36 "go.chromium.org/luci/common/clock/testclock" 37 "go.chromium.org/luci/gae/impl/memory" 38 "go.chromium.org/luci/gae/service/datastore" 39 ) 40 41 func TestRerun(t *testing.T) { 42 t.Parallel() 43 44 Convey("getRerunPropertiesAndDimensions", t, func() { 45 c := memory.Use(context.Background()) 46 cl := testclock.New(testclock.TestTimeUTC) 47 c = clock.Set(c, cl) 48 49 // Setup mock for buildbucket 50 ctl := gomock.NewController(t) 51 defer ctl.Finish() 52 mc := buildbucket.NewMockedClient(c, ctl) 53 c = mc.Ctx 54 bootstrapProperties := &structpb.Struct{ 55 Fields: map[string]*structpb.Value{ 56 "bs_key_1": structpb.NewStringValue("bs_val_1"), 57 }, 58 } 59 60 targetBuilder := &structpb.Struct{ 61 Fields: map[string]*structpb.Value{ 62 "builder": structpb.NewStringValue("linux-test"), 63 "group": structpb.NewStringValue("buildergroup1"), 64 }, 65 } 66 67 res := &bbpb.Build{ 68 Builder: &bbpb.BuilderID{ 69 Project: "chromium", 70 Bucket: "ci", 71 Builder: "linux-test", 72 }, 73 Input: &bbpb.Build_Input{ 74 Properties: &structpb.Struct{ 75 Fields: map[string]*structpb.Value{ 76 "builder_group": structpb.NewStringValue("buildergroup1"), 77 "$bootstrap/properties": structpb.NewStructValue(bootstrapProperties), 78 "another_prop": structpb.NewStringValue("another_val"), 79 }, 80 }, 81 }, 82 Infra: &bbpb.BuildInfra{ 83 Swarming: &bbpb.BuildInfra_Swarming{ 84 TaskDimensions: []*bbpb.RequestedDimension{ 85 { 86 Key: "dimen_key_1", 87 Value: "dimen_val_1", 88 }, 89 { 90 Key: "os", 91 Value: "ubuntu", 92 }, 93 { 94 Key: "gpu", 95 Value: "Intel", 96 }, 97 }, 98 }, 99 }, 100 } 101 Convey("has extra prop and dim", func() { 102 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res, nil).AnyTimes() 103 extraProps := map[string]any{ 104 "analysis_id": 4646418413256704, 105 "compile_targets": []string{"target"}, 106 "bisection_host": "luci-bisection.appspot.com", 107 } 108 extraDimens := map[string]string{ 109 "id": "bot-12345", 110 } 111 112 props, dimens, err := getRerunPropertiesAndDimensions(c, 1234, extraProps, extraDimens) 113 So(err, ShouldBeNil) 114 So(props, ShouldResemble, &structpb.Struct{ 115 Fields: map[string]*structpb.Value{ 116 "builder_group": structpb.NewStringValue("buildergroup1"), 117 "target_builder": structpb.NewStructValue(targetBuilder), 118 "$bootstrap/properties": structpb.NewStructValue(&structpb.Struct{ 119 Fields: map[string]*structpb.Value{ 120 "bs_key_1": structpb.NewStringValue("bs_val_1"), 121 }, 122 }), 123 "analysis_id": structpb.NewNumberValue(4646418413256704), 124 "compile_targets": structpb.NewListValue(&structpb.ListValue{Values: []*structpb.Value{structpb.NewStringValue("target")}}), 125 "bisection_host": structpb.NewStringValue("luci-bisection.appspot.com"), 126 }, 127 }) 128 So(dimens, ShouldResemble, []*bbpb.RequestedDimension{ 129 { 130 Key: "os", 131 Value: "ubuntu", 132 }, 133 { 134 Key: "gpu", 135 Value: "Intel", 136 }, 137 { 138 Key: "id", 139 Value: "bot-12345", 140 }, 141 }) 142 }) 143 144 Convey("no extra dim", func() { 145 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res, nil).AnyTimes() 146 147 _, dimens, err := getRerunPropertiesAndDimensions(c, 1234, nil, nil) 148 So(err, ShouldBeNil) 149 So(dimens, ShouldResemble, []*bbpb.RequestedDimension{ 150 { 151 Key: "os", 152 Value: "ubuntu", 153 }, 154 { 155 Key: "gpu", 156 Value: "Intel", 157 }, 158 }) 159 }) 160 161 Convey("builder is a tester", func() { 162 res.Input.Properties.Fields["parent_build_id"] = structpb.NewStringValue("123") 163 parentBuild := &bbpb.Build{ 164 Infra: &bbpb.BuildInfra{Swarming: &bbpb.BuildInfra_Swarming{ 165 TaskDimensions: []*bbpb.RequestedDimension{{Key: "os", Value: "parent os"}}}, 166 }} 167 gomock.InOrder( 168 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res, nil), 169 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(parentBuild, nil), 170 ) 171 172 _, dimens, err := getRerunPropertiesAndDimensions(c, 1234, nil, nil) 173 So(err, ShouldBeNil) 174 So(dimens, ShouldResemble, []*bbpb.RequestedDimension{ 175 { 176 Key: "os", 177 Value: "parent os", 178 }, 179 }) 180 }) 181 }) 182 183 } 184 185 func TestCreateRerunBuildModel(t *testing.T) { 186 t.Parallel() 187 c := memory.Use(context.Background()) 188 cl := testclock.New(testclock.TestTimeUTC) 189 c = clock.Set(c, cl) 190 191 // Setup mock for buildbucket 192 ctl := gomock.NewController(t) 193 defer ctl.Finish() 194 mc := buildbucket.NewMockedClient(c, ctl) 195 c = mc.Ctx 196 res := &bbpb.Build{ 197 Infra: &bbpb.BuildInfra{ 198 Swarming: &bbpb.BuildInfra_Swarming{ 199 TaskDimensions: []*bbpb.RequestedDimension{ 200 { 201 Key: "dimen_key_1", 202 Value: "dimen_val_1", 203 }, 204 { 205 Key: "os", 206 Value: "ubuntu", 207 }, 208 { 209 Key: "gpu", 210 Value: "Intel", 211 }, 212 }, 213 }, 214 }, 215 } 216 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res, nil).AnyTimes() 217 218 build := &bbpb.Build{ 219 Builder: &bbpb.BuilderID{ 220 Project: "chromium", 221 Bucket: "findit", 222 Builder: "luci-bisection-single-revision", 223 }, 224 Input: &bbpb.Build_Input{ 225 GitilesCommit: &bbpb.GitilesCommit{ 226 Host: "chromium.googlesource.com", 227 Project: "chromium/src", 228 Ref: "ref", 229 Id: "3425", 230 }, 231 }, 232 Id: 123, 233 Status: bbpb.Status_STARTED, 234 CreateTime: ×tamppb.Timestamp{Seconds: 100}, 235 StartTime: ×tamppb.Timestamp{Seconds: 101}, 236 } 237 238 Convey("Create rerun build", t, func() { 239 compileFailure := &model.CompileFailure{ 240 Id: 111, 241 OutputTargets: []string{"target1"}, 242 } 243 So(datastore.Put(c, compileFailure), ShouldBeNil) 244 datastore.GetTestable(c).CatchupIndexes() 245 246 analysis := &model.CompileFailureAnalysis{ 247 Id: 444, 248 CompileFailure: datastore.KeyForObj(c, compileFailure), 249 } 250 So(datastore.Put(c, analysis), ShouldBeNil) 251 datastore.GetTestable(c).CatchupIndexes() 252 253 nsa := &model.CompileNthSectionAnalysis{ 254 ParentAnalysis: datastore.KeyForObj(c, analysis), 255 } 256 So(datastore.Put(c, nsa), ShouldBeNil) 257 datastore.GetTestable(c).CatchupIndexes() 258 259 heuristicAnalysis := &model.CompileHeuristicAnalysis{ 260 ParentAnalysis: datastore.KeyForObj(c, analysis), 261 } 262 So(datastore.Put(c, heuristicAnalysis), ShouldBeNil) 263 datastore.GetTestable(c).CatchupIndexes() 264 265 suspect := &model.Suspect{ 266 Score: 10, 267 ParentAnalysis: datastore.KeyForObj(c, heuristicAnalysis), 268 GitilesCommit: bbpb.GitilesCommit{ 269 Host: "chromium.googlesource.com", 270 Project: "chromium/src", 271 Ref: "ref", 272 Id: "3425", 273 }, 274 } 275 So(datastore.Put(c, suspect), ShouldBeNil) 276 datastore.GetTestable(c).CatchupIndexes() 277 278 Convey("Invalid data", func() { 279 _, err := CreateRerunBuildModel(c, build, model.RerunBuildType_CulpritVerification, nil, nsa, 0) 280 So(err, ShouldNotBeNil) 281 _, err = CreateRerunBuildModel(c, build, model.RerunBuildType_NthSection, suspect, nil, 0) 282 So(err, ShouldNotBeNil) 283 }) 284 285 Convey("Culprit verification", func() { 286 rerunBuildModel, err := CreateRerunBuildModel(c, build, model.RerunBuildType_CulpritVerification, suspect, nil, 100) 287 datastore.GetTestable(c).CatchupIndexes() 288 So(err, ShouldBeNil) 289 So(rerunBuildModel, ShouldResemble, &model.CompileRerunBuild{ 290 Id: 123, 291 LuciBuild: model.LuciBuild{ 292 BuildId: 123, 293 Project: "chromium", 294 Bucket: "findit", 295 Builder: "luci-bisection-single-revision", 296 Status: bbpb.Status_STARTED, 297 GitilesCommit: bbpb.GitilesCommit{ 298 Host: "chromium.googlesource.com", 299 Project: "chromium/src", 300 Ref: "ref", 301 Id: "3425", 302 }, 303 CreateTime: build.CreateTime.AsTime(), 304 StartTime: build.StartTime.AsTime(), 305 }, 306 }) 307 308 // Check SingleRerun 309 q := datastore.NewQuery("SingleRerun").Eq("rerun_build", datastore.KeyForObj(c, rerunBuildModel)) 310 singleReruns := []*model.SingleRerun{} 311 err = datastore.GetAll(c, q, &singleReruns) 312 So(err, ShouldBeNil) 313 So(len(singleReruns), ShouldEqual, 1) 314 So(singleReruns[0].Suspect, ShouldResemble, datastore.KeyForObj(c, suspect)) 315 So(singleReruns[0].Analysis, ShouldResemble, datastore.KeyForObj(c, analysis)) 316 So(singleReruns[0].Type, ShouldEqual, model.RerunBuildType_CulpritVerification) 317 So(singleReruns[0].Dimensions, ShouldResembleProto, util.ToDimensionsPB(res.Infra.Swarming.TaskDimensions)) 318 }) 319 320 Convey("Nth Section", func() { 321 build.Id = 124 322 rerunBuildModel1, err := CreateRerunBuildModel(c, build, model.RerunBuildType_NthSection, nil, nsa, 100) 323 datastore.GetTestable(c).CatchupIndexes() 324 So(err, ShouldBeNil) 325 So(rerunBuildModel1, ShouldResemble, &model.CompileRerunBuild{ 326 Id: 124, 327 LuciBuild: model.LuciBuild{ 328 BuildId: 124, 329 Project: "chromium", 330 Bucket: "findit", 331 Builder: "luci-bisection-single-revision", 332 Status: bbpb.Status_STARTED, 333 GitilesCommit: bbpb.GitilesCommit{ 334 Host: "chromium.googlesource.com", 335 Project: "chromium/src", 336 Ref: "ref", 337 Id: "3425", 338 }, 339 CreateTime: build.CreateTime.AsTime(), 340 StartTime: build.StartTime.AsTime(), 341 }, 342 }) 343 344 // Check SingleRerun 345 q := datastore.NewQuery("SingleRerun").Eq("rerun_build", datastore.KeyForObj(c, rerunBuildModel1)) 346 singleReruns := []*model.SingleRerun{} 347 err = datastore.GetAll(c, q, &singleReruns) 348 So(err, ShouldBeNil) 349 So(len(singleReruns), ShouldEqual, 1) 350 So(singleReruns[0].NthSectionAnalysis, ShouldResemble, datastore.KeyForObj(c, nsa)) 351 So(singleReruns[0].Analysis, ShouldResemble, datastore.KeyForObj(c, analysis)) 352 So(singleReruns[0].Type, ShouldEqual, model.RerunBuildType_NthSection) 353 So(singleReruns[0].Dimensions, ShouldResembleProto, util.ToDimensionsPB(res.Infra.Swarming.TaskDimensions)) 354 }) 355 }) 356 } 357 358 func TestUpdateRerunStatus(t *testing.T) { 359 t.Parallel() 360 361 Convey("TestUpdateRerunStatus", t, func() { 362 c := memory.Use(context.Background()) 363 testutil.UpdateIndices(c) 364 365 // Setup mock for buildbucket 366 ctl := gomock.NewController(t) 367 defer ctl.Finish() 368 mc := buildbucket.NewMockedClient(c, ctl) 369 c = mc.Ctx 370 res := &bbpb.Build{ 371 Id: 1234, 372 Builder: &bbpb.BuilderID{ 373 Project: "chromium", 374 Bucket: "findit", 375 Builder: "luci-bisection-single-revision", 376 }, 377 Status: bbpb.Status_STARTED, 378 StartTime: ×tamppb.Timestamp{Seconds: 100}, 379 EndTime: ×tamppb.Timestamp{Seconds: 200}, 380 } 381 382 Convey("build starts", func() { 383 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res, nil).AnyTimes() 384 rerunBuild := &model.CompileRerunBuild{ 385 Id: 1234, 386 } 387 So(datastore.Put(c, rerunBuild), ShouldBeNil) 388 datastore.GetTestable(c).CatchupIndexes() 389 singleRerun := &model.SingleRerun{ 390 RerunBuild: datastore.KeyForObj(c, rerunBuild), 391 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 392 } 393 So(datastore.Put(c, singleRerun), ShouldBeNil) 394 datastore.GetTestable(c).CatchupIndexes() 395 So(UpdateCompileRerunStatus(c, 1234), ShouldBeNil) 396 datastore.GetTestable(c).CatchupIndexes() 397 398 // Checking the start time 399 So(datastore.Get(c, rerunBuild), ShouldBeNil) 400 So(rerunBuild.StartTime.Unix(), ShouldEqual, 100) 401 So(rerunBuild.Status, ShouldEqual, bbpb.Status_STARTED) 402 So(datastore.Get(c, singleRerun), ShouldBeNil) 403 So(singleRerun.StartTime.Unix(), ShouldEqual, 100) 404 So(singleRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_IN_PROGRESS) 405 }) 406 407 Convey("build ends", func() { 408 res.Status = bbpb.Status_SUCCESS 409 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res, nil).AnyTimes() 410 Convey("rerun didn't end", func() { 411 rerunBuild := &model.CompileRerunBuild{ 412 Id: 1234, 413 } 414 So(datastore.Put(c, rerunBuild), ShouldBeNil) 415 singleRerun := &model.SingleRerun{ 416 RerunBuild: datastore.KeyForObj(c, rerunBuild), 417 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 418 } 419 So(datastore.Put(c, singleRerun), ShouldBeNil) 420 datastore.GetTestable(c).CatchupIndexes() 421 422 So(UpdateCompileRerunStatus(c, 1234), ShouldBeNil) 423 datastore.GetTestable(c).CatchupIndexes() 424 // Checking the end time and status. 425 So(datastore.Get(c, rerunBuild), ShouldBeNil) 426 So(rerunBuild.EndTime.Unix(), ShouldEqual, 200) 427 So(rerunBuild.Status, ShouldEqual, bbpb.Status_SUCCESS) 428 So(datastore.Get(c, singleRerun), ShouldBeNil) 429 So(singleRerun.EndTime.Unix(), ShouldEqual, 200) 430 So(singleRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_INFRA_FAILED) 431 }) 432 Convey("rerun ends", func() { 433 rerunBuild := &model.CompileRerunBuild{ 434 Id: 1234, 435 } 436 So(datastore.Put(c, rerunBuild), ShouldBeNil) 437 singleRerun := &model.SingleRerun{ 438 RerunBuild: datastore.KeyForObj(c, rerunBuild), 439 Status: pb.RerunStatus_RERUN_STATUS_PASSED, 440 EndTime: time.Unix(101, 0).UTC(), 441 } 442 So(datastore.Put(c, singleRerun), ShouldBeNil) 443 datastore.GetTestable(c).CatchupIndexes() 444 445 So(UpdateCompileRerunStatus(c, 1234), ShouldBeNil) 446 datastore.GetTestable(c).CatchupIndexes() 447 // Checking the end time and status. 448 So(datastore.Get(c, rerunBuild), ShouldBeNil) 449 So(rerunBuild.EndTime.Unix(), ShouldEqual, 200) 450 So(rerunBuild.Status, ShouldEqual, bbpb.Status_SUCCESS) 451 So(datastore.Get(c, singleRerun), ShouldBeNil) 452 So(singleRerun.EndTime.Unix(), ShouldEqual, 101) 453 So(singleRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_PASSED) 454 }) 455 }) 456 }) 457 } 458 459 func TestUpdateTestRerunStatus(t *testing.T) { 460 t.Parallel() 461 462 Convey("TestUpdateRerunStatus", t, func() { 463 c := memory.Use(context.Background()) 464 testutil.UpdateIndices(c) 465 cl := testclock.New(testclock.TestTimeUTC) 466 cl.Set(time.Unix(10000, 0).UTC()) 467 c = clock.Set(c, cl) 468 469 build := &bbpb.Build{ 470 Id: 1234, 471 Builder: &bbpb.BuilderID{ 472 Project: "chromium", 473 Bucket: "findit", 474 Builder: "luci-bisection-single-revision", 475 }, 476 Status: bbpb.Status_STARTED, 477 StartTime: ×tamppb.Timestamp{Seconds: 100}, 478 EndTime: ×tamppb.Timestamp{Seconds: 200}, 479 } 480 481 Convey("build starts", func() { 482 singleRerun := &model.TestSingleRerun{ 483 ID: 1234, 484 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 485 } 486 So(datastore.Put(c, singleRerun), ShouldBeNil) 487 datastore.GetTestable(c).CatchupIndexes() 488 So(UpdateTestRerunStatus(c, build), ShouldBeNil) 489 datastore.GetTestable(c).CatchupIndexes() 490 491 // Checking the start time 492 So(datastore.Get(c, singleRerun), ShouldBeNil) 493 So(singleRerun.LUCIBuild.StartTime.Unix(), ShouldEqual, 100) 494 So(singleRerun.LUCIBuild.Status, ShouldEqual, bbpb.Status_STARTED) 495 }) 496 497 Convey("build ends", func() { 498 build.Status = bbpb.Status_SUCCESS 499 Convey("rerun didn't end", func() { 500 tfa := testutil.CreateTestFailureAnalysis(c, &testutil.TestFailureAnalysisCreationOption{ 501 ID: 100, 502 }) 503 nsa := testutil.CreateTestNthSectionAnalysis(c, &testutil.TestNthSectionAnalysisCreationOption{ 504 ID: 1000, 505 ParentAnalysisKey: datastore.KeyForObj(c, tfa), 506 }) 507 singleRerun := testutil.CreateTestSingleRerun(c, &testutil.TestSingleRerunCreationOption{ 508 AnalysisKey: datastore.KeyForObj(c, tfa), 509 ID: 1234, 510 }) 511 So(datastore.Put(c, singleRerun), ShouldBeNil) 512 datastore.GetTestable(c).CatchupIndexes() 513 514 So(UpdateTestRerunStatus(c, build), ShouldBeNil) 515 datastore.GetTestable(c).CatchupIndexes() 516 517 // Checking the end time and status. 518 So(datastore.Get(c, singleRerun), ShouldBeNil) 519 So(singleRerun.LUCIBuild.EndTime.Unix(), ShouldEqual, 200) 520 So(singleRerun.LUCIBuild.Status, ShouldEqual, bbpb.Status_SUCCESS) 521 So(singleRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_INFRA_FAILED) 522 523 So(datastore.Get(c, nsa), ShouldBeNil) 524 So(nsa.Status, ShouldEqual, pb.AnalysisStatus_ERROR) 525 So(nsa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 526 So(nsa.EndTime.Unix(), ShouldEqual, 10000) 527 528 So(datastore.Get(c, tfa), ShouldBeNil) 529 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_ERROR) 530 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 531 So(tfa.EndTime.Unix(), ShouldEqual, 10000) 532 }) 533 534 Convey("rerun ends", func() { 535 singleRerun := &model.TestSingleRerun{ 536 ID: 1234, 537 Status: pb.RerunStatus_RERUN_STATUS_PASSED, 538 } 539 So(datastore.Put(c, singleRerun), ShouldBeNil) 540 datastore.GetTestable(c).CatchupIndexes() 541 542 So(UpdateTestRerunStatus(c, build), ShouldBeNil) 543 datastore.GetTestable(c).CatchupIndexes() 544 // Checking the end time and status. 545 So(datastore.Get(c, singleRerun), ShouldBeNil) 546 So(singleRerun.LUCIBuild.EndTime.Unix(), ShouldEqual, 200) 547 So(singleRerun.LUCIBuild.Status, ShouldEqual, bbpb.Status_SUCCESS) 548 So(singleRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_PASSED) 549 }) 550 }) 551 }) 552 }