go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/server/updatetestrerun/update_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 updatetestrerun 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 "github.com/golang/mock/gomock" 24 . "github.com/smartystreets/goconvey/convey" 25 "google.golang.org/grpc/codes" 26 "google.golang.org/grpc/status" 27 "google.golang.org/protobuf/types/known/structpb" 28 "google.golang.org/protobuf/types/known/timestamppb" 29 30 "go.chromium.org/luci/bisection/culpritverification" 31 "go.chromium.org/luci/bisection/internal/buildbucket" 32 "go.chromium.org/luci/bisection/internal/config" 33 "go.chromium.org/luci/bisection/model" 34 "go.chromium.org/luci/bisection/util/testutil" 35 "go.chromium.org/luci/server/tq" 36 37 configpb "go.chromium.org/luci/bisection/proto/config" 38 pb "go.chromium.org/luci/bisection/proto/v1" 39 tpb "go.chromium.org/luci/bisection/task/proto" 40 bbpb "go.chromium.org/luci/buildbucket/proto" 41 "go.chromium.org/luci/common/clock" 42 "go.chromium.org/luci/common/clock/testclock" 43 "go.chromium.org/luci/common/proto" 44 . "go.chromium.org/luci/common/testing/assertions" 45 "go.chromium.org/luci/gae/impl/memory" 46 "go.chromium.org/luci/gae/service/datastore" 47 ) 48 49 func TestUpdate(t *testing.T) { 50 t.Parallel() 51 ctx := memory.Use(context.Background()) 52 53 Convey("Invalid request", t, func() { 54 req := &pb.UpdateTestAnalysisProgressRequest{} 55 err := Update(ctx, req) 56 So(err, ShouldNotBeNil) 57 So(status.Convert(err).Code(), ShouldEqual, codes.InvalidArgument) 58 59 req.Bbid = 888 60 err = Update(ctx, req) 61 So(err, ShouldNotBeNil) 62 So(status.Convert(err).Code(), ShouldEqual, codes.InvalidArgument) 63 64 req.Bbid = 0 65 req.BotId = "" 66 err = Update(ctx, req) 67 So(err, ShouldNotBeNil) 68 So(status.Convert(err).Code(), ShouldEqual, codes.InvalidArgument) 69 }) 70 71 Convey("Update", t, func() { 72 ctx := memory.Use(context.Background()) 73 // We need this because without this, updates on TestSingleRerun status 74 // will not be reflected in subsequent queries, due to the eventual 75 // consistency. 76 // LUCI Bisection is running on Firestore on Datastore mode in dev and prod, 77 // which uses strong consistency, so this should reflect the real world. 78 datastore.GetTestable(ctx).Consistent(true) 79 testutil.UpdateIndices(ctx) 80 81 cl := testclock.New(testclock.TestTimeUTC) 82 cl.Set(time.Unix(10000, 0).UTC()) 83 ctx = clock.Set(ctx, cl) 84 85 ctl := gomock.NewController(t) 86 defer ctl.Finish() 87 mc := buildbucket.NewMockedClient(ctx, ctl) 88 ctx = mc.Ctx 89 mockBuildBucket(mc, false) 90 91 tfa := testutil.CreateTestFailureAnalysis(ctx, &testutil.TestFailureAnalysisCreationOption{ 92 ID: 100, 93 }) 94 95 Convey("No rerun", func() { 96 req := &pb.UpdateTestAnalysisProgressRequest{ 97 Bbid: 800, 98 BotId: "bot", 99 } 100 err := Update(ctx, req) 101 So(err, ShouldNotBeNil) 102 So(status.Convert(err).Code(), ShouldEqual, codes.NotFound) 103 }) 104 105 Convey("Rerun ended", func() { 106 req := &pb.UpdateTestAnalysisProgressRequest{ 107 Bbid: 801, 108 BotId: "bot", 109 } 110 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 111 ID: 801, 112 Status: pb.RerunStatus_RERUN_STATUS_FAILED, 113 }) 114 err := Update(ctx, req) 115 So(err, ShouldNotBeNil) 116 So(status.Convert(err).Code(), ShouldEqual, codes.Internal) 117 So(err.Error(), ShouldContainSubstring, "rerun has ended") 118 }) 119 120 Convey("Invalid rerun type", func() { 121 req := &pb.UpdateTestAnalysisProgressRequest{ 122 Bbid: 802, 123 BotId: "bot", 124 } 125 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 126 ID: 802, 127 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 128 AnalysisKey: datastore.KeyForObj(ctx, tfa), 129 }) 130 131 err := Update(ctx, req) 132 So(err, ShouldNotBeNil) 133 So(status.Convert(err).Code(), ShouldEqual, codes.Internal) 134 So(err.Error(), ShouldContainSubstring, "invalid rerun type") 135 }) 136 137 Convey("No analysis", func() { 138 req := &pb.UpdateTestAnalysisProgressRequest{ 139 Bbid: 803, 140 BotId: "bot", 141 } 142 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 143 ID: 803, 144 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 145 AnalysisKey: datastore.MakeKey(ctx, "TestFailureAnalysis", 1), 146 Type: model.RerunBuildType_CulpritVerification, 147 }) 148 149 err := Update(ctx, req) 150 So(err, ShouldNotBeNil) 151 So(status.Convert(err).Code(), ShouldEqual, codes.Internal) 152 So(err.Error(), ShouldContainSubstring, "get test failure analysis") 153 }) 154 155 Convey("Tests did not run", func() { 156 tfa, _, rerun, _ := setupTestAnalysisForTesting(ctx, 1) 157 req := &pb.UpdateTestAnalysisProgressRequest{ 158 Bbid: 8000, 159 BotId: "bot", 160 RunSucceeded: false, 161 } 162 enableBisection(ctx, true, tfa.Project) 163 164 err := Update(ctx, req) 165 So(err, ShouldBeNil) 166 datastore.GetTestable(ctx).CatchupIndexes() 167 err = datastore.Get(ctx, rerun) 168 So(err, ShouldBeNil) 169 So(rerun.ReportTime, ShouldEqual, time.Unix(10000, 0).UTC()) 170 So(rerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_INFRA_FAILED) 171 }) 172 173 Convey("No primary test failure", func() { 174 req := &pb.UpdateTestAnalysisProgressRequest{ 175 Bbid: 805, 176 BotId: "bot", 177 RunSucceeded: true, 178 } 179 nsa := testutil.CreateTestNthSectionAnalysis(ctx, &testutil.TestNthSectionAnalysisCreationOption{ 180 ParentAnalysisKey: datastore.KeyForObj(ctx, tfa), 181 }) 182 rerun := testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 183 ID: 805, 184 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 185 AnalysisKey: datastore.KeyForObj(ctx, tfa), 186 Type: model.RerunBuildType_NthSection, 187 }) 188 err := Update(ctx, req) 189 So(err, ShouldNotBeNil) 190 So(status.Convert(err).Code(), ShouldEqual, codes.Internal) 191 So(err.Error(), ShouldContainSubstring, "get primary test failure") 192 193 datastore.GetTestable(ctx).CatchupIndexes() 194 err = datastore.Get(ctx, rerun) 195 So(err, ShouldBeNil) 196 So(rerun.ReportTime, ShouldEqual, time.Unix(10000, 0).UTC()) 197 So(rerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_INFRA_FAILED) 198 199 So(datastore.Get(ctx, nsa), ShouldBeNil) 200 So(nsa.Status, ShouldEqual, pb.AnalysisStatus_ERROR) 201 So(nsa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 202 So(nsa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 203 204 So(datastore.Get(ctx, tfa), ShouldBeNil) 205 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_ERROR) 206 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 207 So(tfa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 208 }) 209 210 Convey("No result for primary failure", func() { 211 tfa := testutil.CreateTestFailureAnalysis(ctx, &testutil.TestFailureAnalysisCreationOption{ 212 ID: 106, 213 TestFailureKey: datastore.MakeKey(ctx, "TestFailure", 1061), 214 }) 215 nsa := testutil.CreateTestNthSectionAnalysis(ctx, &testutil.TestNthSectionAnalysisCreationOption{ 216 ParentAnalysisKey: datastore.KeyForObj(ctx, tfa), 217 }) 218 219 req := &pb.UpdateTestAnalysisProgressRequest{ 220 Bbid: 806, 221 BotId: "bot", 222 RunSucceeded: true, 223 } 224 rerun := testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 225 ID: 806, 226 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 227 AnalysisKey: datastore.KeyForObj(ctx, tfa), 228 Type: model.RerunBuildType_NthSection, 229 }) 230 231 // Set up test failures 232 testutil.CreateTestFailure(ctx, &testutil.TestFailureCreationOption{ 233 ID: 1061, 234 IsPrimary: true, 235 TestID: "test1", 236 VariantHash: "hash1", 237 Analysis: tfa, 238 }) 239 testutil.CreateTestFailure(ctx, &testutil.TestFailureCreationOption{ 240 ID: 1062, 241 TestID: "test2", 242 VariantHash: "hash2", 243 Analysis: tfa, 244 }) 245 246 err := Update(ctx, req) 247 So(err, ShouldNotBeNil) 248 So(status.Convert(err).Code(), ShouldEqual, codes.Internal) 249 So(err.Error(), ShouldContainSubstring, "no result for primary failure") 250 datastore.GetTestable(ctx).CatchupIndexes() 251 err = datastore.Get(ctx, rerun) 252 So(err, ShouldBeNil) 253 So(rerun.ReportTime, ShouldEqual, time.Unix(10000, 0).UTC()) 254 So(rerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_INFRA_FAILED) 255 256 So(datastore.Get(ctx, nsa), ShouldBeNil) 257 So(nsa.Status, ShouldEqual, pb.AnalysisStatus_ERROR) 258 So(nsa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 259 So(nsa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 260 261 So(datastore.Get(ctx, tfa), ShouldBeNil) 262 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_ERROR) 263 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 264 So(tfa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 265 }) 266 267 Convey("Primary test failure skipped", func() { 268 tfa, tfs, rerun, nsa := setupTestAnalysisForTesting(ctx, 2) 269 enableBisection(ctx, false, tfa.Project) 270 271 req := &pb.UpdateTestAnalysisProgressRequest{ 272 Bbid: 8000, 273 BotId: "bot", 274 RunSucceeded: true, 275 Results: []*pb.TestResult{ 276 { 277 TestId: "test0", 278 VariantHash: "hash0", 279 IsExpected: true, 280 Status: pb.TestResultStatus_SKIP, 281 }, 282 { 283 TestId: "test1", 284 VariantHash: "hash1", 285 IsExpected: true, 286 Status: pb.TestResultStatus_PASS, 287 }, 288 }, 289 } 290 err := Update(ctx, req) 291 So(err, ShouldBeNil) 292 datastore.GetTestable(ctx).CatchupIndexes() 293 err = datastore.Get(ctx, rerun) 294 So(err, ShouldBeNil) 295 So(rerun.ReportTime, ShouldEqual, time.Unix(10000, 0).UTC()) 296 So(rerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_TEST_SKIPPED) 297 So(rerun.TestResults, ShouldResembleProto, model.RerunTestResults{ 298 IsFinalized: true, 299 Results: []model.RerunSingleTestResult{ 300 { 301 TestFailureKey: datastore.KeyForObj(ctx, tfs[0]), 302 }, 303 { 304 TestFailureKey: datastore.KeyForObj(ctx, tfs[1]), 305 ExpectedCount: 1, 306 }, 307 }, 308 }) 309 err = datastore.Get(ctx, tfs[1]) 310 So(err, ShouldBeNil) 311 So(tfs[1].IsDiverged, ShouldBeFalse) 312 // Check that a new rerun is not scheduled, because primary test was skipped. 313 q := datastore.NewQuery("TestSingleRerun").Eq("nthsection_analysis_key", datastore.KeyForObj(ctx, nsa)) 314 reruns := []*model.TestSingleRerun{} 315 So(datastore.GetAll(ctx, q, &reruns), ShouldBeNil) 316 So(len(reruns), ShouldEqual, 1) 317 }) 318 319 Convey("Primary test failure expected", func() { 320 tfa, tfs, rerun, nsa := setupTestAnalysisForTesting(ctx, 4) 321 enableBisection(ctx, true, tfa.Project) 322 323 req := &pb.UpdateTestAnalysisProgressRequest{ 324 Bbid: 8000, 325 BotId: "bot", 326 RunSucceeded: true, 327 Results: []*pb.TestResult{ 328 { 329 TestId: "test0", 330 VariantHash: "hash0", 331 IsExpected: true, 332 Status: pb.TestResultStatus_PASS, 333 }, 334 { 335 TestId: "test1", 336 VariantHash: "hash1", 337 IsExpected: true, 338 Status: pb.TestResultStatus_PASS, 339 }, 340 { 341 TestId: "test2", 342 VariantHash: "hash2", 343 IsExpected: false, 344 Status: pb.TestResultStatus_FAIL, 345 }, 346 { 347 TestId: "test3", 348 VariantHash: "hash3", 349 IsExpected: false, 350 Status: pb.TestResultStatus_SKIP, 351 }, 352 }, 353 } 354 355 err := Update(ctx, req) 356 So(err, ShouldBeNil) 357 datastore.GetTestable(ctx).CatchupIndexes() 358 err = datastore.Get(ctx, rerun) 359 So(err, ShouldBeNil) 360 So(rerun.ReportTime, ShouldEqual, time.Unix(10000, 0).UTC()) 361 So(rerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_PASSED) 362 So(rerun.TestResults, ShouldResembleProto, model.RerunTestResults{ 363 IsFinalized: true, 364 Results: []model.RerunSingleTestResult{ 365 { 366 TestFailureKey: datastore.KeyForObj(ctx, tfs[0]), 367 ExpectedCount: 1, 368 }, 369 { 370 TestFailureKey: datastore.KeyForObj(ctx, tfs[1]), 371 ExpectedCount: 1, 372 }, 373 { 374 TestFailureKey: datastore.KeyForObj(ctx, tfs[2]), 375 UnexpectedCount: 1, 376 }, 377 { 378 TestFailureKey: datastore.KeyForObj(ctx, tfs[3]), 379 }, 380 }, 381 }) 382 err = datastore.Get(ctx, tfs[1]) 383 So(err, ShouldBeNil) 384 So(tfs[1].IsDiverged, ShouldBeFalse) 385 err = datastore.Get(ctx, tfs[2]) 386 So(err, ShouldBeNil) 387 So(tfs[2].IsDiverged, ShouldBeTrue) 388 err = datastore.Get(ctx, tfs[3]) 389 So(err, ShouldBeNil) 390 So(tfs[3].IsDiverged, ShouldBeTrue) 391 // Check that a new rerun is scheduled. 392 q := datastore.NewQuery("TestSingleRerun").Eq("nthsection_analysis_key", datastore.KeyForObj(ctx, nsa)) 393 reruns := []*model.TestSingleRerun{} 394 So(datastore.GetAll(ctx, q, &reruns), ShouldBeNil) 395 So(len(reruns), ShouldEqual, 2) 396 }) 397 398 Convey("Primary test failure unexpected", func() { 399 tfa, tfs, rerun, nsa := setupTestAnalysisForTesting(ctx, 4) 400 enableBisection(ctx, true, tfa.Project) 401 402 req := &pb.UpdateTestAnalysisProgressRequest{ 403 Bbid: 8000, 404 BotId: "bot", 405 RunSucceeded: true, 406 Results: []*pb.TestResult{ 407 { 408 TestId: "test0", 409 VariantHash: "hash0", 410 IsExpected: false, 411 Status: pb.TestResultStatus_FAIL, 412 }, 413 { 414 TestId: "test1", 415 VariantHash: "hash1", 416 IsExpected: true, 417 Status: pb.TestResultStatus_PASS, 418 }, 419 { 420 TestId: "test2", 421 VariantHash: "hash2", 422 IsExpected: false, 423 Status: pb.TestResultStatus_FAIL, 424 }, 425 { 426 TestId: "test3", 427 VariantHash: "hash3", 428 IsExpected: false, 429 Status: pb.TestResultStatus_SKIP, 430 }, 431 }, 432 } 433 434 err := Update(ctx, req) 435 So(err, ShouldBeNil) 436 datastore.GetTestable(ctx).CatchupIndexes() 437 err = datastore.Get(ctx, rerun) 438 So(err, ShouldBeNil) 439 So(rerun.ReportTime, ShouldEqual, time.Unix(10000, 0).UTC()) 440 So(rerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_FAILED) 441 So(rerun.TestResults, ShouldResembleProto, model.RerunTestResults{ 442 IsFinalized: true, 443 Results: []model.RerunSingleTestResult{ 444 { 445 TestFailureKey: datastore.KeyForObj(ctx, tfs[0]), 446 UnexpectedCount: 1, 447 }, 448 { 449 TestFailureKey: datastore.KeyForObj(ctx, tfs[1]), 450 ExpectedCount: 1, 451 }, 452 { 453 TestFailureKey: datastore.KeyForObj(ctx, tfs[2]), 454 UnexpectedCount: 1, 455 }, 456 { 457 TestFailureKey: datastore.KeyForObj(ctx, tfs[3]), 458 }, 459 }, 460 }) 461 err = datastore.Get(ctx, tfs[1]) 462 So(err, ShouldBeNil) 463 So(tfs[1].IsDiverged, ShouldBeTrue) 464 err = datastore.Get(ctx, tfs[2]) 465 So(err, ShouldBeNil) 466 So(tfs[2].IsDiverged, ShouldBeFalse) 467 err = datastore.Get(ctx, tfs[3]) 468 So(err, ShouldBeNil) 469 So(tfs[3].IsDiverged, ShouldBeTrue) 470 // Check that a new rerun is scheduled. 471 q := datastore.NewQuery("TestSingleRerun").Eq("nthsection_analysis_key", datastore.KeyForObj(ctx, nsa)) 472 reruns := []*model.TestSingleRerun{} 473 So(datastore.GetAll(ctx, q, &reruns), ShouldBeNil) 474 So(len(reruns), ShouldEqual, 2) 475 }) 476 477 Convey("Ended nthsection should not get updated", func() { 478 tfa, _, _, nsa := setupTestAnalysisForTesting(ctx, 1) 479 enableBisection(ctx, true, tfa.Project) 480 481 // Set nthsection to end. 482 nsa.RunStatus = pb.AnalysisRunStatus_ENDED 483 So(datastore.Put(ctx, nsa), ShouldBeNil) 484 datastore.GetTestable(ctx).CatchupIndexes() 485 486 req := &pb.UpdateTestAnalysisProgressRequest{ 487 Bbid: 8000, 488 BotId: "bot", 489 RunSucceeded: true, 490 Results: []*pb.TestResult{ 491 { 492 TestId: "test0", 493 VariantHash: "hash0", 494 IsExpected: false, 495 Status: pb.TestResultStatus_FAIL, 496 }, 497 }, 498 } 499 500 err := Update(ctx, req) 501 So(err, ShouldBeNil) 502 datastore.GetTestable(ctx).CatchupIndexes() 503 504 // Check that a new rerun is not scheduled. 505 q := datastore.NewQuery("TestSingleRerun").Eq("nthsection_analysis_key", datastore.KeyForObj(ctx, nsa)) 506 reruns := []*model.TestSingleRerun{} 507 So(datastore.GetAll(ctx, q, &reruns), ShouldBeNil) 508 // 1 because of the rerun created in setupTestAnalysisForTesting. 509 So(len(reruns), ShouldEqual, 1) 510 511 // Check that no suspect is created. 512 q = datastore.NewQuery("Suspect") 513 suspects := []*model.Suspect{} 514 So(datastore.GetAll(ctx, q, &suspects), ShouldBeNil) 515 So(len(suspects), ShouldEqual, 0) 516 }) 517 }) 518 519 Convey("process culprit verification update", t, func() { 520 cl := testclock.New(testclock.TestTimeUTC) 521 cl.Set(time.Unix(10000, 0).UTC()) 522 ctx = clock.Set(ctx, cl) 523 // set up tfa, rerun, suspect 524 tfa := testutil.CreateTestFailureAnalysis(ctx, &testutil.TestFailureAnalysisCreationOption{ 525 ID: 100, 526 TestFailureKey: datastore.MakeKey(ctx, "TestFailure", 1000), 527 Status: pb.AnalysisStatus_SUSPECTFOUND, 528 RunStatus: pb.AnalysisRunStatus_STARTED, 529 }) 530 testFailure := testutil.CreateTestFailure(ctx, &testutil.TestFailureCreationOption{ 531 ID: 1000, 532 IsPrimary: true, 533 TestID: "test0", 534 VariantHash: "hash0", 535 Analysis: tfa, 536 }) 537 suspect := testutil.CreateSuspect(ctx, nil) 538 suspectRerun := testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 539 ID: 8000, 540 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 541 AnalysisKey: datastore.KeyForObj(ctx, tfa), 542 Type: model.RerunBuildType_CulpritVerification, 543 TestResult: model.RerunTestResults{ 544 Results: []model.RerunSingleTestResult{ 545 {TestFailureKey: datastore.KeyForObj(ctx, testFailure)}, 546 }, 547 }, 548 CulpritKey: datastore.KeyForObj(ctx, suspect), 549 }) 550 parentRerun := testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 551 ID: 8001, 552 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 553 AnalysisKey: datastore.KeyForObj(ctx, tfa), 554 Type: model.RerunBuildType_CulpritVerification, 555 TestResult: model.RerunTestResults{ 556 Results: []model.RerunSingleTestResult{ 557 {TestFailureKey: datastore.KeyForObj(ctx, testFailure)}, 558 }, 559 }, 560 CulpritKey: datastore.KeyForObj(ctx, suspect), 561 }) 562 suspect.SuspectRerunBuild = datastore.KeyForObj(ctx, suspectRerun) 563 suspect.ParentRerunBuild = datastore.KeyForObj(ctx, parentRerun) 564 So(datastore.Put(ctx, suspect), ShouldBeNil) 565 datastore.GetTestable(ctx).CatchupIndexes() 566 567 req := &pb.UpdateTestAnalysisProgressRequest{ 568 Bbid: 8000, 569 BotId: "bot", 570 RunSucceeded: true, 571 Results: []*pb.TestResult{ 572 { 573 TestId: "test0", 574 VariantHash: "hash0", 575 IsExpected: false, 576 Status: pb.TestResultStatus_FAIL, 577 }, 578 }, 579 } 580 Convey("suspect under verification", func() { 581 err := Update(ctx, req) 582 So(err, ShouldBeNil) 583 datastore.GetTestable(ctx).CatchupIndexes() 584 So(datastore.Get(ctx, suspectRerun), ShouldBeNil) 585 So(suspectRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_FAILED) 586 // Check suspect status. 587 So(datastore.Get(ctx, suspect), ShouldBeNil) 588 So(suspect.VerificationStatus, ShouldEqual, model.SuspectVerificationStatus_UnderVerification) 589 // Check analysis - no update. 590 So(datastore.Get(ctx, tfa), ShouldBeNil) 591 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_SUSPECTFOUND) 592 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_STARTED) 593 So(tfa.VerifiedCulpritKey, ShouldBeNil) 594 }) 595 596 Convey("suspect verified", func() { 597 // ParentSuspect finished running. 598 parentRerun.Status = pb.RerunStatus_RERUN_STATUS_PASSED 599 So(datastore.Put(ctx, parentRerun), ShouldBeNil) 600 601 err := Update(ctx, req) 602 So(err, ShouldBeNil) 603 datastore.GetTestable(ctx).CatchupIndexes() 604 So(datastore.Get(ctx, suspectRerun), ShouldBeNil) 605 So(suspectRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_FAILED) 606 // Check suspect status. 607 So(datastore.Get(ctx, suspect), ShouldBeNil) 608 So(suspect.VerificationStatus, ShouldEqual, model.SuspectVerificationStatus_ConfirmedCulprit) 609 // Check analysis. 610 So(datastore.Get(ctx, tfa), ShouldBeNil) 611 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_FOUND) 612 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 613 So(tfa.VerifiedCulpritKey, ShouldEqual, datastore.KeyForObj(ctx, suspect)) 614 }) 615 616 Convey("suspect not verified", func() { 617 // ParentSuspect finished running. 618 parentRerun.Status = pb.RerunStatus_RERUN_STATUS_FAILED 619 So(datastore.Put(ctx, parentRerun), ShouldBeNil) 620 621 err := Update(ctx, req) 622 So(err, ShouldBeNil) 623 datastore.GetTestable(ctx).CatchupIndexes() 624 So(datastore.Get(ctx, suspectRerun), ShouldBeNil) 625 So(suspectRerun.Status, ShouldEqual, pb.RerunStatus_RERUN_STATUS_FAILED) 626 // Check suspect status. 627 So(datastore.Get(ctx, suspect), ShouldBeNil) 628 So(suspect.VerificationStatus, ShouldEqual, model.SuspectVerificationStatus_Vindicated) 629 // Check analysis. 630 So(datastore.Get(ctx, tfa), ShouldBeNil) 631 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_SUSPECTFOUND) 632 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 633 So(tfa.VerifiedCulpritKey, ShouldBeNil) 634 }) 635 }) 636 } 637 638 func TestScheduleNewRerun(t *testing.T) { 639 t.Parallel() 640 ctx := memory.Use(context.Background()) 641 testutil.UpdateIndices(ctx) 642 643 cl := testclock.New(testclock.TestTimeUTC) 644 cl.Set(time.Unix(10000, 0).UTC()) 645 ctx = clock.Set(ctx, cl) 646 647 ctl := gomock.NewController(t) 648 defer ctl.Finish() 649 mc := buildbucket.NewMockedClient(ctx, ctl) 650 ctx = mc.Ctx 651 mockBuildBucket(mc, false) 652 653 Convey("Nth section found culprit", t, func() { 654 culpritverification.RegisterTaskClass() 655 ctx, skdr := tq.TestingContext(ctx, nil) 656 tfa, _, rerun, nsa := setupTestAnalysisForTesting(ctx, 1) 657 enableBisection(ctx, true, tfa.Project) 658 // Commit 1 pass -> commit 0 is the culprit. 659 rerun.LUCIBuild.GitilesCommit = &bbpb.GitilesCommit{ 660 Id: "commit1", 661 Host: "chromium.googlesource.com", 662 Project: "chromium/src", 663 Ref: "ref", 664 } 665 rerun.Status = pb.RerunStatus_RERUN_STATUS_PASSED 666 So(datastore.Put(ctx, rerun), ShouldBeNil) 667 datastore.GetTestable(ctx).CatchupIndexes() 668 err := processNthSectionUpdate(ctx, rerun, tfa, &pb.UpdateTestAnalysisProgressRequest{}) 669 So(err, ShouldBeNil) 670 datastore.GetTestable(ctx).CatchupIndexes() 671 672 // Check suspect being stored. 673 q := datastore.NewQuery("Suspect") 674 suspects := []*model.Suspect{} 675 So(datastore.GetAll(ctx, q, &suspects), ShouldBeNil) 676 So(len(suspects), ShouldEqual, 1) 677 // Check the field individually because ShouldResembleProto does not work here. 678 So(&suspects[0].GitilesCommit, ShouldResembleProto, &bbpb.GitilesCommit{ 679 Id: "commit0", 680 Host: "chromium.googlesource.com", 681 Project: "chromium/src", 682 Ref: "ref", 683 }) 684 So(suspects[0].ParentAnalysis, ShouldEqual, datastore.KeyForObj(ctx, nsa)) 685 So(suspects[0].Type, ShouldEqual, model.SuspectType_NthSection) 686 So(suspects[0].AnalysisType, ShouldEqual, pb.AnalysisType_TEST_FAILURE_ANALYSIS) 687 688 // Check nsa. 689 So(datastore.Get(ctx, nsa), ShouldBeNil) 690 So(nsa.Status, ShouldEqual, pb.AnalysisStatus_SUSPECTFOUND) 691 So(nsa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 692 So(nsa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 693 So(nsa.CulpritKey, ShouldEqual, datastore.KeyForObj(ctx, suspects[0])) 694 695 // Check tfa. 696 So(datastore.Get(ctx, tfa), ShouldBeNil) 697 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_SUSPECTFOUND) 698 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_STARTED) 699 700 // Culprit verification task scheduled. 701 So(len(skdr.Tasks().Payloads()), ShouldEqual, 1) 702 resultsTask := skdr.Tasks().Payloads()[0].(*tpb.TestFailureCulpritVerificationTask) 703 So(resultsTask, ShouldResembleProto, &tpb.TestFailureCulpritVerificationTask{ 704 AnalysisId: tfa.ID, 705 }) 706 }) 707 708 Convey("Nth section not found", t, func() { 709 tfa, _, rerun, nsa := setupTestAnalysisForTesting(ctx, 1) 710 enableBisection(ctx, true, tfa.Project) 711 // Commit 1 pass -> commit 0 is the culprit. 712 rerun.LUCIBuild.GitilesCommit = &bbpb.GitilesCommit{ 713 Id: "commit1", 714 Host: "chromium.googlesource.com", 715 Project: "chromium/src", 716 Ref: "ref", 717 } 718 rerun.Status = pb.RerunStatus_RERUN_STATUS_TEST_SKIPPED 719 So(datastore.Put(ctx, rerun), ShouldBeNil) 720 datastore.GetTestable(ctx).CatchupIndexes() 721 err := processNthSectionUpdate(ctx, rerun, tfa, &pb.UpdateTestAnalysisProgressRequest{}) 722 So(err, ShouldBeNil) 723 datastore.GetTestable(ctx).CatchupIndexes() 724 725 // Check nsa. 726 So(datastore.Get(ctx, nsa), ShouldBeNil) 727 So(nsa.Status, ShouldEqual, pb.AnalysisStatus_NOTFOUND) 728 So(nsa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 729 So(nsa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 730 731 // Check tfa. 732 So(datastore.Get(ctx, tfa), ShouldBeNil) 733 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_NOTFOUND) 734 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 735 So(tfa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 736 }) 737 738 Convey("regression range conflicts", t, func() { 739 tfa, _, rerun, nsa := setupTestAnalysisForTesting(ctx, 1) 740 enableBisection(ctx, true, tfa.Project) 741 // Commit 0 pass -> no culprit. 742 rerun.LUCIBuild.GitilesCommit = &bbpb.GitilesCommit{ 743 Id: "commit0", 744 Host: "chromium.googlesource.com", 745 Project: "chromium/src", 746 Ref: "ref", 747 } 748 rerun.Status = pb.RerunStatus_RERUN_STATUS_PASSED 749 So(datastore.Put(ctx, rerun), ShouldBeNil) 750 datastore.GetTestable(ctx).CatchupIndexes() 751 err := processNthSectionUpdate(ctx, rerun, tfa, &pb.UpdateTestAnalysisProgressRequest{}) 752 So(err, ShouldBeNil) 753 datastore.GetTestable(ctx).CatchupIndexes() 754 755 // Check nsa. 756 So(datastore.Get(ctx, nsa), ShouldBeNil) 757 So(nsa.Status, ShouldEqual, pb.AnalysisStatus_NOTFOUND) 758 So(nsa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 759 So(nsa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 760 761 // Check tfa. 762 So(datastore.Get(ctx, tfa), ShouldBeNil) 763 So(tfa.Status, ShouldEqual, pb.AnalysisStatus_NOTFOUND) 764 So(tfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED) 765 So(tfa.EndTime, ShouldEqual, time.Unix(10000, 0).UTC()) 766 }) 767 768 Convey("Nth section should schedule another run", t, func() { 769 mc := buildbucket.NewMockedClient(ctx, ctl) 770 ctx = mc.Ctx 771 mockBuildBucket(mc, true) 772 tfa, tfs, rerun, nsa := setupTestAnalysisForTesting(ctx, 1) 773 enableBisection(ctx, true, tfa.Project) 774 // Commit 1 pass -> commit 0 is the culprit. 775 rerun.LUCIBuild.GitilesCommit = &bbpb.GitilesCommit{ 776 Id: "commit2", 777 Host: "chromium.googlesource.com", 778 Project: "chromium/src", 779 Ref: "ref", 780 } 781 rerun.Status = pb.RerunStatus_RERUN_STATUS_PASSED 782 So(datastore.Put(ctx, rerun), ShouldBeNil) 783 datastore.GetTestable(ctx).CatchupIndexes() 784 req := &pb.UpdateTestAnalysisProgressRequest{ 785 BotId: "bot", 786 } 787 err := processNthSectionUpdate(ctx, rerun, tfa, req) 788 So(err, ShouldBeNil) 789 datastore.GetTestable(ctx).CatchupIndexes() 790 791 // Check that a new rerun is scheduled. 792 q := datastore.NewQuery("TestSingleRerun").Eq("nthsection_analysis_key", datastore.KeyForObj(ctx, nsa)).Eq("status", pb.RerunStatus_RERUN_STATUS_IN_PROGRESS) 793 reruns := []*model.TestSingleRerun{} 794 So(datastore.GetAll(ctx, q, &reruns), ShouldBeNil) 795 So(len(reruns), ShouldEqual, 1) 796 So(reruns[0], ShouldResembleProto, &model.TestSingleRerun{ 797 ID: reruns[0].ID, 798 Type: model.RerunBuildType_NthSection, 799 AnalysisKey: datastore.KeyForObj(ctx, tfa), 800 NthSectionAnalysisKey: datastore.KeyForObj(ctx, nsa), 801 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 802 LUCIBuild: model.LUCIBuild{ 803 BuildID: 8765, 804 Project: "chromium", 805 Bucket: "findit", 806 Builder: "test-single-revision", 807 BuildNumber: 10, 808 Status: bbpb.Status_SCHEDULED, 809 GitilesCommit: &bbpb.GitilesCommit{ 810 Host: "chromium.googlesource.com", 811 Project: "chromium/src", 812 Ref: "refs/heads/main", 813 Id: "hash", 814 }, 815 CreateTime: time.Unix(1000, 0), 816 }, 817 Dimensions: &pb.Dimensions{ 818 Dimensions: []*pb.Dimension{ 819 { 820 Key: "key", 821 Value: "val", 822 }, 823 }, 824 }, 825 TestResults: model.RerunTestResults{ 826 Results: []model.RerunSingleTestResult{ 827 { 828 TestFailureKey: datastore.KeyForObj(ctx, tfs[0]), 829 }, 830 }, 831 }, 832 }) 833 }) 834 } 835 836 func setupTestAnalysisForTesting(ctx context.Context, numTest int) (*model.TestFailureAnalysis, []*model.TestFailure, *model.TestSingleRerun, *model.TestNthSectionAnalysis) { 837 tfa := testutil.CreateTestFailureAnalysis(ctx, &testutil.TestFailureAnalysisCreationOption{ 838 ID: 100, 839 TestFailureKey: datastore.MakeKey(ctx, "TestFailure", 1000), 840 }) 841 842 nsa := testutil.CreateTestNthSectionAnalysis(ctx, &testutil.TestNthSectionAnalysisCreationOption{ 843 ID: 200, 844 ParentAnalysisKey: datastore.KeyForObj(ctx, tfa), 845 BlameList: testutil.CreateBlamelist(4), 846 }) 847 848 // Set up test failures 849 tfs := make([]*model.TestFailure, numTest) 850 results := make([]model.RerunSingleTestResult, numTest) 851 for i := 0; i < numTest; i++ { 852 isPrimary := (i == 0) 853 tfs[i] = testutil.CreateTestFailure(ctx, &testutil.TestFailureCreationOption{ 854 ID: 1000 + int64(i), 855 IsPrimary: isPrimary, 856 TestID: fmt.Sprintf("test%d", i), 857 VariantHash: fmt.Sprintf("hash%d", i), 858 Analysis: tfa, 859 Ref: &pb.SourceRef{ 860 System: &pb.SourceRef_Gitiles{ 861 Gitiles: &pb.GitilesRef{ 862 Host: "chromium.googlesource.com", 863 Project: "chromium/src", 864 Ref: "ref", 865 }, 866 }, 867 }, 868 }) 869 results[i] = model.RerunSingleTestResult{ 870 TestFailureKey: datastore.KeyForObj(ctx, tfs[i]), 871 } 872 } 873 rerun := testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 874 ID: 8000, 875 Status: pb.RerunStatus_RERUN_STATUS_IN_PROGRESS, 876 AnalysisKey: datastore.KeyForObj(ctx, tfa), 877 Type: model.RerunBuildType_NthSection, 878 TestResult: model.RerunTestResults{ 879 Results: results, 880 }, 881 NthSectionAnalysisKey: datastore.KeyForObj(ctx, nsa), 882 }) 883 return tfa, tfs, rerun, nsa 884 } 885 886 func enableBisection(ctx context.Context, enabled bool, project string) { 887 projectCfg := config.CreatePlaceholderProjectConfig() 888 projectCfg.TestAnalysisConfig.BisectorEnabled = enabled 889 cfg := map[string]*configpb.ProjectConfig{project: projectCfg} 890 So(config.SetTestProjectConfig(ctx, cfg), ShouldBeNil) 891 } 892 893 func mockBuildBucket(mc *buildbucket.MockedClient, withBotID bool) { 894 bootstrapProperties := &structpb.Struct{ 895 Fields: map[string]*structpb.Value{ 896 "bs_key_1": structpb.NewStringValue("bs_val_1"), 897 }, 898 } 899 900 getBuildRes := &bbpb.Build{ 901 Builder: &bbpb.BuilderID{ 902 Project: "chromium", 903 Bucket: "ci", 904 Builder: "linux-test", 905 }, 906 Input: &bbpb.Build_Input{ 907 Properties: &structpb.Struct{ 908 Fields: map[string]*structpb.Value{ 909 "builder_group": structpb.NewStringValue("buildergroup1"), 910 "$bootstrap/properties": structpb.NewStructValue(bootstrapProperties), 911 "another_prop": structpb.NewStringValue("another_val"), 912 }, 913 }, 914 }, 915 Infra: &bbpb.BuildInfra{ 916 Swarming: &bbpb.BuildInfra_Swarming{ 917 TaskDimensions: []*bbpb.RequestedDimension{ 918 { 919 Key: "key", 920 Value: "val", 921 }, 922 }, 923 }, 924 }, 925 } 926 927 scheduleBuildRes := &bbpb.Build{ 928 Id: 8765, 929 Builder: &bbpb.BuilderID{ 930 Project: "chromium", 931 Bucket: "findit", 932 Builder: "test-single-revision", 933 }, 934 Number: 10, 935 Status: bbpb.Status_SCHEDULED, 936 CreateTime: timestamppb.New(time.Unix(1000, 0)), 937 Input: &bbpb.Build_Input{ 938 GitilesCommit: &bbpb.GitilesCommit{ 939 Host: "chromium.googlesource.com", 940 Project: "chromium/src", 941 Ref: "refs/heads/main", 942 Id: "hash", 943 }, 944 }, 945 } 946 947 mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(getBuildRes, nil).AnyTimes() 948 if !withBotID { 949 mc.Client.EXPECT().ScheduleBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(scheduleBuildRes, nil).AnyTimes() 950 } else { 951 mc.Client.EXPECT().ScheduleBuild(gomock.Any(), proto.MatcherEqual(&bbpb.ScheduleBuildRequest{ 952 Builder: &bbpb.BuilderID{ 953 Project: "chromium", 954 Bucket: "findit", 955 Builder: "test-single-revision", 956 }, 957 Dimensions: []*bbpb.RequestedDimension{ 958 { 959 Key: "id", 960 Value: "bot", 961 }, 962 }, 963 Tags: []*bbpb.StringPair{ 964 { 965 Key: "analyzed_build_id", 966 Value: "8000", 967 }, 968 }, 969 GitilesCommit: &bbpb.GitilesCommit{ 970 Host: "chromium.googlesource.com", 971 Project: "chromium/src", 972 Id: "commit1", 973 Ref: "ref", 974 }, 975 Properties: &structpb.Struct{ 976 Fields: map[string]*structpb.Value{ 977 "$bootstrap/properties": structpb.NewStructValue(bootstrapProperties), 978 "analysis_id": structpb.NewNumberValue(100), 979 "bisection_host": structpb.NewStringValue("app.appspot.com"), 980 "builder_group": structpb.NewStringValue("buildergroup1"), 981 "target_builder": structpb.NewStructValue(&structpb.Struct{ 982 Fields: map[string]*structpb.Value{ 983 "builder": structpb.NewStringValue("linux-test"), 984 "group": structpb.NewStringValue("buildergroup1"), 985 }, 986 }), 987 "tests_to_run": structpb.NewListValue(&structpb.ListValue{ 988 Values: []*structpb.Value{ 989 structpb.NewStructValue(&structpb.Struct{ 990 Fields: map[string]*structpb.Value{ 991 "test_id": structpb.NewStringValue("test0"), 992 "test_name": structpb.NewStringValue(""), 993 "test_suite_name": structpb.NewStringValue(""), 994 "variant_hash": structpb.NewStringValue("hash0"), 995 }, 996 }), 997 }, 998 }), 999 }, 1000 }, 1001 }), gomock.Any()).Return(scheduleBuildRes, nil).Times(1) 1002 } 1003 }