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  }