go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/util/protoutil/proto_util_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 protoutil 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 . "github.com/smartystreets/goconvey/convey" 23 "google.golang.org/protobuf/types/known/fieldmaskpb" 24 "google.golang.org/protobuf/types/known/timestamppb" 25 26 "go.chromium.org/luci/bisection/model" 27 pb "go.chromium.org/luci/bisection/proto/v1" 28 "go.chromium.org/luci/bisection/util/testutil" 29 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 30 "go.chromium.org/luci/common/proto/mask" 31 . "go.chromium.org/luci/common/testing/assertions" 32 "go.chromium.org/luci/gae/impl/memory" 33 "go.chromium.org/luci/gae/service/datastore" 34 ) 35 36 func TestConvertTestFailureAnalysisToPb(t *testing.T) { 37 t.Parallel() 38 Convey("TestConvertTestFailureAnalysisToPb", t, func() { 39 ctx := memory.Use(context.Background()) 40 testutil.UpdateIndices(ctx) 41 tfa := testutil.CreateTestFailureAnalysis(ctx, &testutil.TestFailureAnalysisCreationOption{ 42 ID: int64(100), 43 Project: "chromium", 44 Bucket: "ci", 45 Builder: "linux-rel", 46 StartCommitHash: "start_commit_hash", 47 EndCommitHash: "end_commit_hash", 48 FailedBuildID: 8000, 49 CreateTime: time.Unix(int64(100), 0).UTC(), 50 StartTime: time.Unix(int64(110), 0).UTC(), 51 EndTime: time.Unix(int64(120), 0).UTC(), 52 TestFailureKey: datastore.MakeKey(ctx, "TestFailure", 100), 53 Status: pb.AnalysisStatus_FOUND, 54 RunStatus: pb.AnalysisRunStatus_ENDED, 55 }) 56 // non-primary test failure. 57 testutil.CreateTestFailure(ctx, &testutil.TestFailureCreationOption{ 58 ID: int64(99), 59 Analysis: tfa, 60 TestID: "testID2", 61 StartHour: time.Unix(int64(100), 0).UTC(), 62 Ref: &pb.SourceRef{ 63 System: &pb.SourceRef_Gitiles{ 64 Gitiles: &pb.GitilesRef{ 65 Host: "chromium.googlesource.com", 66 Project: "chromium/src", 67 Ref: "ref", 68 }, 69 }, 70 }, 71 Project: "chromium", 72 Variant: map[string]string{ 73 "key2": "val2", 74 }, 75 VariantHash: "vhash2", 76 RefHash: "refhash", 77 StartPosition: 100, 78 EndPosition: 199, 79 StartFailureRate: 0, 80 EndFailureRate: 1.0, 81 IsDiverged: true, 82 }) 83 primaryTf := testutil.CreateTestFailure(ctx, &testutil.TestFailureCreationOption{ 84 ID: int64(100), 85 Analysis: tfa, 86 IsPrimary: true, 87 TestID: "testID1", 88 StartHour: time.Unix(int64(100), 0).UTC(), 89 Ref: &pb.SourceRef{ 90 System: &pb.SourceRef_Gitiles{ 91 Gitiles: &pb.GitilesRef{ 92 Host: "chromium.googlesource.com", 93 Project: "chromium/src", 94 Ref: "ref", 95 }, 96 }, 97 }, 98 Project: "chromium", 99 Variant: map[string]string{ 100 "key1": "val1", 101 }, 102 VariantHash: "vhash1", 103 RefHash: "refhash", 104 StartPosition: 100, 105 EndPosition: 199, 106 StartFailureRate: 0, 107 EndFailureRate: 1.0, 108 }) 109 nsa := testutil.CreateTestNthSectionAnalysis(ctx, &testutil.TestNthSectionAnalysisCreationOption{ 110 ID: 200, 111 ParentAnalysisKey: datastore.KeyForObj(ctx, tfa), 112 BlameList: testutil.CreateBlamelist(4), 113 Status: pb.AnalysisStatus_SUSPECTFOUND, 114 RunStatus: pb.AnalysisRunStatus_ENDED, 115 StartTime: time.Unix(int64(100), 0).UTC(), 116 EndTime: time.Unix(int64(109), 0).UTC(), 117 }) 118 culprit := testutil.CreateSuspect(ctx, &testutil.SuspectCreationOption{ 119 ID: 500, 120 ParentKey: datastore.KeyForObj(ctx, nsa), 121 CommitID: "culprit_commit_id", 122 ReviewURL: "review_url", 123 ReviewTitle: "review_title", 124 SuspectRerunKey: datastore.MakeKey(ctx, "Suspect", 3000), 125 ParentRerunKey: datastore.MakeKey(ctx, "Suspect", 3001), 126 VerificationStatus: model.SuspectVerificationStatus_ConfirmedCulprit, 127 ActionDetails: model.ActionDetails{ 128 IsRevertCreated: true, 129 RevertURL: "http://revert", 130 RevertCreateTime: time.Unix(120, 0).UTC(), 131 }, 132 }) 133 134 // Suspect verification rerun. 135 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 136 ID: 3000, 137 AnalysisKey: datastore.KeyForObj(ctx, tfa), 138 CulpritKey: datastore.KeyForObj(ctx, culprit), 139 Type: model.RerunBuildType_CulpritVerification, 140 CreateTime: time.Unix(103, 0).UTC(), 141 StartTime: time.Unix(104, 0).UTC(), 142 ReportTime: time.Unix(105, 0).UTC(), 143 EndTime: time.Unix(106, 0).UTC(), 144 Status: pb.RerunStatus_RERUN_STATUS_FAILED, 145 BuildStatus: buildbucketpb.Status_SUCCESS, 146 TestResult: model.RerunTestResults{ 147 IsFinalized: true, 148 Results: []model.RerunSingleTestResult{ 149 { 150 TestFailureKey: datastore.KeyForObj(ctx, primaryTf), 151 UnexpectedCount: 1, 152 }, 153 }, 154 }, 155 GitilesCommit: &buildbucketpb.GitilesCommit{ 156 Host: "chromium.googlesource.com", 157 Project: "chromium/src", 158 Ref: "ref", 159 Id: "commit2", 160 }, 161 }) 162 163 // Parent verification rerun. 164 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 165 ID: 3001, 166 AnalysisKey: datastore.KeyForObj(ctx, tfa), 167 CulpritKey: datastore.KeyForObj(ctx, culprit), 168 Type: model.RerunBuildType_CulpritVerification, 169 CreateTime: time.Unix(103, 0).UTC(), 170 StartTime: time.Unix(104, 0).UTC(), 171 ReportTime: time.Unix(105, 0).UTC(), 172 EndTime: time.Unix(106, 0).UTC(), 173 Status: pb.RerunStatus_RERUN_STATUS_PASSED, 174 BuildStatus: buildbucketpb.Status_SUCCESS, 175 TestResult: model.RerunTestResults{ 176 IsFinalized: true, 177 Results: []model.RerunSingleTestResult{ 178 { 179 TestFailureKey: datastore.KeyForObj(ctx, primaryTf), 180 ExpectedCount: 1, 181 }, 182 }, 183 }, 184 GitilesCommit: &buildbucketpb.GitilesCommit{ 185 Host: "chromium.googlesource.com", 186 Project: "chromium/src", 187 Ref: "ref", 188 Id: "commit3", 189 }, 190 }) 191 192 // Nthsection rerun1. 193 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 194 ID: 2998, 195 AnalysisKey: datastore.KeyForObj(ctx, tfa), 196 NthSectionAnalysisKey: datastore.KeyForObj(ctx, nsa), 197 Type: model.RerunBuildType_NthSection, 198 CreateTime: time.Unix(103, 0).UTC(), 199 StartTime: time.Unix(104, 0).UTC(), 200 ReportTime: time.Unix(105, 0).UTC(), 201 EndTime: time.Unix(106, 0).UTC(), 202 Status: pb.RerunStatus_RERUN_STATUS_FAILED, 203 BuildStatus: buildbucketpb.Status_SUCCESS, 204 TestResult: model.RerunTestResults{ 205 IsFinalized: true, 206 Results: []model.RerunSingleTestResult{ 207 { 208 TestFailureKey: datastore.KeyForObj(ctx, primaryTf), 209 UnexpectedCount: 1, 210 }, 211 }, 212 }, 213 GitilesCommit: &buildbucketpb.GitilesCommit{ 214 Host: "chromium.googlesource.com", 215 Project: "chromium/src", 216 Ref: "ref", 217 Id: "commit2", 218 }, 219 }) 220 221 // Nthsection rerun2. 222 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 223 ID: 2999, 224 AnalysisKey: datastore.KeyForObj(ctx, tfa), 225 NthSectionAnalysisKey: datastore.KeyForObj(ctx, nsa), 226 Type: model.RerunBuildType_NthSection, 227 CreateTime: time.Unix(104, 0).UTC(), 228 StartTime: time.Unix(105, 0).UTC(), 229 ReportTime: time.Unix(106, 0).UTC(), 230 EndTime: time.Unix(107, 0).UTC(), 231 Status: pb.RerunStatus_RERUN_STATUS_PASSED, 232 BuildStatus: buildbucketpb.Status_SUCCESS, 233 TestResult: model.RerunTestResults{ 234 IsFinalized: true, 235 Results: []model.RerunSingleTestResult{ 236 { 237 TestFailureKey: datastore.KeyForObj(ctx, primaryTf), 238 ExpectedCount: 1, 239 }, 240 }, 241 }, 242 GitilesCommit: &buildbucketpb.GitilesCommit{ 243 Host: "chromium.googlesource.com", 244 Project: "chromium/src", 245 Ref: "ref", 246 Id: "commit3", 247 }, 248 }) 249 250 tfa.VerifiedCulpritKey = datastore.KeyForObj(ctx, culprit) 251 So(datastore.Put(ctx, tfa), ShouldBeNil) 252 nsa.CulpritKey = datastore.KeyForObj(ctx, culprit) 253 So(datastore.Put(ctx, nsa), ShouldBeNil) 254 datastore.GetTestable(ctx).CatchupIndexes() 255 256 Convey("TestFailureAnalysisToPb", func() { 257 fieldMask := &fieldmaskpb.FieldMask{ 258 Paths: []string{"*"}, 259 } 260 mask, err := mask.FromFieldMask(fieldMask, &pb.TestAnalysis{}, false, false) 261 So(err, ShouldBeNil) 262 263 tfaProto, err := TestFailureAnalysisToPb(ctx, tfa, mask) 264 So(err, ShouldBeNil) 265 266 pbSuspectRerun := &pb.TestSingleRerun{ 267 Bbid: 3000, 268 CreateTime: timestamppb.New(time.Unix(103, 0).UTC()), 269 StartTime: timestamppb.New(time.Unix(104, 0).UTC()), 270 ReportTime: timestamppb.New(time.Unix(105, 0).UTC()), 271 EndTime: timestamppb.New(time.Unix(106, 0).UTC()), 272 Index: "2", 273 Commit: &buildbucketpb.GitilesCommit{ 274 Host: "chromium.googlesource.com", 275 Project: "chromium/src", 276 Ref: "ref", 277 Id: "commit2", 278 }, 279 RerunResult: &pb.RerunTestResults{ 280 RerunStatus: pb.RerunStatus_RERUN_STATUS_FAILED, 281 Results: []*pb.RerunTestSingleResult{ 282 { 283 TestId: "testID1", 284 VariantHash: "vhash1", 285 UnexpectedCount: 1, 286 }, 287 }, 288 }, 289 } 290 291 pbParentRerun := &pb.TestSingleRerun{ 292 Bbid: 3001, 293 CreateTime: timestamppb.New(time.Unix(103, 0).UTC()), 294 StartTime: timestamppb.New(time.Unix(104, 0).UTC()), 295 ReportTime: timestamppb.New(time.Unix(105, 0).UTC()), 296 EndTime: timestamppb.New(time.Unix(106, 0).UTC()), 297 Index: "3", 298 Commit: &buildbucketpb.GitilesCommit{ 299 Host: "chromium.googlesource.com", 300 Project: "chromium/src", 301 Ref: "ref", 302 Id: "commit3", 303 }, 304 RerunResult: &pb.RerunTestResults{ 305 RerunStatus: pb.RerunStatus_RERUN_STATUS_PASSED, 306 Results: []*pb.RerunTestSingleResult{ 307 { 308 TestId: "testID1", 309 VariantHash: "vhash1", 310 ExpectedCount: 1, 311 }, 312 }, 313 }, 314 } 315 316 pbNthsectionRerun1 := &pb.TestSingleRerun{ 317 Bbid: 2998, 318 CreateTime: timestamppb.New(time.Unix(103, 0).UTC()), 319 StartTime: timestamppb.New(time.Unix(104, 0).UTC()), 320 ReportTime: timestamppb.New(time.Unix(105, 0).UTC()), 321 EndTime: timestamppb.New(time.Unix(106, 0).UTC()), 322 Index: "2", 323 Commit: &buildbucketpb.GitilesCommit{ 324 Host: "chromium.googlesource.com", 325 Project: "chromium/src", 326 Ref: "ref", 327 Id: "commit2", 328 }, 329 RerunResult: &pb.RerunTestResults{ 330 RerunStatus: pb.RerunStatus_RERUN_STATUS_FAILED, 331 Results: []*pb.RerunTestSingleResult{ 332 { 333 TestId: "testID1", 334 VariantHash: "vhash1", 335 UnexpectedCount: 1, 336 }, 337 }, 338 }, 339 } 340 341 pbNthsectionRerun2 := &pb.TestSingleRerun{ 342 Bbid: 2999, 343 CreateTime: timestamppb.New(time.Unix(104, 0).UTC()), 344 StartTime: timestamppb.New(time.Unix(105, 0).UTC()), 345 ReportTime: timestamppb.New(time.Unix(106, 0).UTC()), 346 EndTime: timestamppb.New(time.Unix(107, 0).UTC()), 347 Index: "3", 348 Commit: &buildbucketpb.GitilesCommit{ 349 Host: "chromium.googlesource.com", 350 Project: "chromium/src", 351 Ref: "ref", 352 Id: "commit3", 353 }, 354 RerunResult: &pb.RerunTestResults{ 355 RerunStatus: pb.RerunStatus_RERUN_STATUS_PASSED, 356 Results: []*pb.RerunTestSingleResult{ 357 { 358 TestId: "testID1", 359 VariantHash: "vhash1", 360 ExpectedCount: 1, 361 }, 362 }, 363 }, 364 } 365 366 culpritPb := &pb.TestCulprit{ 367 ReviewUrl: "review_url", 368 ReviewTitle: "review_title", 369 Commit: &buildbucketpb.GitilesCommit{ 370 Host: "chromium.googlesource.com", 371 Project: "chromium/src", 372 Ref: "ref", 373 Id: "culprit_commit_id", 374 }, 375 CulpritAction: []*pb.CulpritAction{ 376 { 377 ActionType: pb.CulpritActionType_REVERT_CL_CREATED, 378 RevertClUrl: "http://revert", 379 ActionTime: timestamppb.New(time.Unix(120, 0)), 380 }, 381 }, 382 VerificationDetails: &pb.TestSuspectVerificationDetails{ 383 Status: pb.SuspectVerificationStatus_CONFIRMED_CULPRIT, 384 SuspectRerun: pbSuspectRerun, 385 ParentRerun: pbParentRerun, 386 }, 387 } 388 389 So(tfaProto, ShouldResembleProto, &pb.TestAnalysis{ 390 AnalysisId: 100, 391 Builder: &buildbucketpb.BuilderID{ 392 Project: "chromium", 393 Bucket: "ci", 394 Builder: "linux-rel", 395 }, 396 CreatedTime: timestamppb.New(time.Unix(int64(100), 0).UTC()), 397 StartTime: timestamppb.New(time.Unix(int64(110), 0).UTC()), 398 EndTime: timestamppb.New(time.Unix(int64(120), 0).UTC()), 399 Status: pb.AnalysisStatus_FOUND, 400 RunStatus: pb.AnalysisRunStatus_ENDED, 401 StartCommit: &buildbucketpb.GitilesCommit{ 402 Host: "chromium.googlesource.com", 403 Project: "chromium/src", 404 Ref: "ref", 405 Id: "start_commit_hash", 406 Position: 100, 407 }, 408 EndCommit: &buildbucketpb.GitilesCommit{ 409 Host: "chromium.googlesource.com", 410 Project: "chromium/src", 411 Ref: "ref", 412 Id: "end_commit_hash", 413 Position: 199, 414 }, 415 SampleBbid: 8000, 416 StartFailureRate: 0, 417 EndFailureRate: 1.0, 418 TestFailures: []*pb.TestFailure{ 419 { 420 TestId: "testID1", 421 VariantHash: "vhash1", 422 RefHash: "refhash", 423 Variant: &pb.Variant{ 424 Def: map[string]string{ 425 "key1": "val1", 426 }, 427 }, 428 IsPrimary: true, 429 StartHour: timestamppb.New(time.Unix(int64(100), 0).UTC()), 430 }, 431 { 432 TestId: "testID2", 433 VariantHash: "vhash2", 434 RefHash: "refhash", 435 Variant: &pb.Variant{ 436 Def: map[string]string{ 437 "key2": "val2", 438 }, 439 }, 440 IsDiverged: true, 441 StartHour: timestamppb.New(time.Unix(int64(100), 0).UTC()), 442 }, 443 }, 444 NthSectionResult: &pb.TestNthSectionAnalysisResult{ 445 Status: pb.AnalysisStatus_SUSPECTFOUND, 446 RunStatus: pb.AnalysisRunStatus_ENDED, 447 StartTime: timestamppb.New(time.Unix(int64(100), 0).UTC()), 448 EndTime: timestamppb.New(time.Unix(int64(109), 0).UTC()), 449 BlameList: testutil.CreateBlamelist(4), 450 Suspect: culpritPb, 451 Reruns: []*pb.TestSingleRerun{ 452 pbNthsectionRerun1, pbNthsectionRerun2, 453 }, 454 }, 455 Culprit: culpritPb, 456 }) 457 }) 458 459 Convey("Bisection overview field mask", func() { 460 fieldMask := &fieldmaskpb.FieldMask{ 461 Paths: []string{ 462 "analysis_id", 463 "created_time", 464 "start_time", 465 "end_time", 466 "status", 467 "run_status", 468 "builder", 469 "culprit.commit", 470 "culprit.review_url", 471 "culprit.review_title", 472 "culprit.culprit_action", 473 "test_failures.*.is_primary", 474 "test_failures.*.start_hour", 475 }, 476 } 477 mask, err := mask.FromFieldMask(fieldMask, &pb.TestAnalysis{}, false, false) 478 So(err, ShouldBeNil) 479 480 tfaProto, err := TestFailureAnalysisToPb(ctx, tfa, mask) 481 So(err, ShouldBeNil) 482 483 culpritPb := &pb.TestCulprit{ 484 ReviewUrl: "review_url", 485 ReviewTitle: "review_title", 486 Commit: &buildbucketpb.GitilesCommit{ 487 Host: "chromium.googlesource.com", 488 Project: "chromium/src", 489 Ref: "ref", 490 Id: "culprit_commit_id", 491 }, 492 CulpritAction: []*pb.CulpritAction{ 493 { 494 ActionType: pb.CulpritActionType_REVERT_CL_CREATED, 495 RevertClUrl: "http://revert", 496 ActionTime: timestamppb.New(time.Unix(120, 0)), 497 }, 498 }, 499 } 500 501 So(tfaProto, ShouldResembleProto, &pb.TestAnalysis{ 502 AnalysisId: 100, 503 Builder: &buildbucketpb.BuilderID{ 504 Project: "chromium", 505 Bucket: "ci", 506 Builder: "linux-rel", 507 }, 508 CreatedTime: timestamppb.New(time.Unix(int64(100), 0).UTC()), 509 StartTime: timestamppb.New(time.Unix(int64(110), 0).UTC()), 510 EndTime: timestamppb.New(time.Unix(int64(120), 0).UTC()), 511 Status: pb.AnalysisStatus_FOUND, 512 RunStatus: pb.AnalysisRunStatus_ENDED, 513 TestFailures: []*pb.TestFailure{ 514 { 515 IsPrimary: true, 516 StartHour: timestamppb.New(time.Unix(int64(100), 0).UTC()), 517 }, 518 { 519 StartHour: timestamppb.New(time.Unix(int64(100), 0).UTC()), 520 }, 521 }, 522 Culprit: culpritPb, 523 }) 524 }) 525 }) 526 } 527 528 func TestRegressionRange(t *testing.T) { 529 t.Parallel() 530 ctx := memory.Use(context.Background()) 531 testutil.UpdateIndices(ctx) 532 533 Convey("TestRegressionRange", t, func() { 534 tfa := testutil.CreateTestFailureAnalysis(ctx, &testutil.TestFailureAnalysisCreationOption{}) 535 nsa := testutil.CreateTestNthSectionAnalysis(ctx, &testutil.TestNthSectionAnalysisCreationOption{ 536 ID: 200, 537 ParentAnalysisKey: datastore.KeyForObj(ctx, tfa), 538 BlameList: testutil.CreateBlamelist(4), 539 Status: pb.AnalysisStatus_RUNNING, 540 RunStatus: pb.AnalysisRunStatus_STARTED, 541 }) 542 sourceRef := &pb.SourceRef{ 543 System: &pb.SourceRef_Gitiles{ 544 Gitiles: &pb.GitilesRef{ 545 Host: "chromium.googlesource.com", 546 Project: "chromium/src", 547 Ref: "ref", 548 }, 549 }, 550 } 551 nsaFieldMask := fieldmaskpb.FieldMask{ 552 Paths: []string{"*"}, 553 } 554 nsaMask, err := mask.FromFieldMask(&nsaFieldMask, &pb.TestNthSectionAnalysisResult{}, false, false) 555 So(err, ShouldBeNil) 556 pbNsa, err := NthSectionAnalysisToPb(ctx, tfa, nsa, sourceRef, nsaMask) 557 So(err, ShouldBeNil) 558 So(pbNsa.RemainingNthSectionRange, ShouldResembleProto, &pb.RegressionRange{ 559 LastPassed: &buildbucketpb.GitilesCommit{ 560 Host: "chromium.googlesource.com", 561 Project: "chromium/src", 562 Ref: "ref", 563 Id: "commit4", 564 }, 565 FirstFailed: &buildbucketpb.GitilesCommit{ 566 Host: "chromium.googlesource.com", 567 Project: "chromium/src", 568 Ref: "ref", 569 Id: "commit0", 570 }, 571 }) 572 573 // Add a rerun, regression should update. 574 testutil.CreateTestSingleRerun(ctx, &testutil.TestSingleRerunCreationOption{ 575 AnalysisKey: datastore.KeyForObj(ctx, tfa), 576 NthSectionAnalysisKey: datastore.KeyForObj(ctx, nsa), 577 Type: model.RerunBuildType_NthSection, 578 Status: pb.RerunStatus_RERUN_STATUS_PASSED, 579 BuildStatus: buildbucketpb.Status_SUCCESS, 580 GitilesCommit: &buildbucketpb.GitilesCommit{ 581 Host: "chromium.googlesource.com", 582 Project: "chromium/src", 583 Ref: "ref", 584 Id: "commit2", 585 }, 586 }) 587 588 pbNsa, err = NthSectionAnalysisToPb(ctx, tfa, nsa, sourceRef, nsaMask) 589 So(err, ShouldBeNil) 590 So(pbNsa.RemainingNthSectionRange, ShouldResembleProto, &pb.RegressionRange{ 591 LastPassed: &buildbucketpb.GitilesCommit{ 592 Host: "chromium.googlesource.com", 593 Project: "chromium/src", 594 Ref: "ref", 595 Id: "commit2", 596 }, 597 FirstFailed: &buildbucketpb.GitilesCommit{ 598 Host: "chromium.googlesource.com", 599 Project: "chromium/src", 600 Ref: "ref", 601 Id: "commit0", 602 }, 603 }) 604 }) 605 }