go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/culpritverification/verify_culprit_test.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package culpritverification
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/golang/mock/gomock"
    24  	. "github.com/smartystreets/goconvey/convey"
    25  	bbpb "go.chromium.org/luci/buildbucket/proto"
    26  	"go.chromium.org/luci/common/clock"
    27  	"go.chromium.org/luci/common/clock/testclock"
    28  	"go.chromium.org/luci/gae/impl/memory"
    29  	"go.chromium.org/luci/gae/service/datastore"
    30  	"google.golang.org/protobuf/types/known/timestamppb"
    31  
    32  	"go.chromium.org/luci/bisection/internal/buildbucket"
    33  	"go.chromium.org/luci/bisection/internal/config"
    34  	"go.chromium.org/luci/bisection/internal/gitiles"
    35  	"go.chromium.org/luci/bisection/model"
    36  	configpb "go.chromium.org/luci/bisection/proto/config"
    37  	pb "go.chromium.org/luci/bisection/proto/v1"
    38  	"go.chromium.org/luci/bisection/util/testutil"
    39  )
    40  
    41  func TestVerifySuspect(t *testing.T) {
    42  	t.Parallel()
    43  
    44  	Convey("Verify Suspect", t, func() {
    45  		c := memory.Use(context.Background())
    46  		testutil.UpdateIndices(c)
    47  
    48  		cl := testclock.New(testclock.TestTimeUTC)
    49  		c = clock.Set(c, cl)
    50  
    51  		// Setup config.
    52  		projectCfg := config.CreatePlaceholderProjectConfig()
    53  		cfg := map[string]*configpb.ProjectConfig{"chromium": projectCfg}
    54  		So(config.SetTestProjectConfig(c, cfg), ShouldBeNil)
    55  
    56  		Convey("Verify Suspect triggers rerun", func() {
    57  			// Setup mock for buildbucket
    58  			ctl := gomock.NewController(t)
    59  			defer ctl.Finish()
    60  			mc := buildbucket.NewMockedClient(c, ctl)
    61  			c = mc.Ctx
    62  			res1 := &bbpb.Build{
    63  				Builder: &bbpb.BuilderID{
    64  					Project: "chromium",
    65  					Bucket:  "findit",
    66  					Builder: "single-revision",
    67  				},
    68  				Input: &bbpb.Build_Input{
    69  					GitilesCommit: &bbpb.GitilesCommit{
    70  						Host:    "host",
    71  						Project: "proj",
    72  						Id:      "id1",
    73  						Ref:     "ref",
    74  					},
    75  				},
    76  				Id:         123,
    77  				Status:     bbpb.Status_STARTED,
    78  				CreateTime: &timestamppb.Timestamp{Seconds: 100},
    79  				StartTime:  &timestamppb.Timestamp{Seconds: 101},
    80  			}
    81  			mc.Client.EXPECT().ScheduleBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res1, nil).Times(1)
    82  
    83  			res2 := &bbpb.Build{
    84  				Builder: &bbpb.BuilderID{
    85  					Project: "chromium",
    86  					Bucket:  "findit",
    87  					Builder: "single-revision",
    88  				},
    89  				Input: &bbpb.Build_Input{
    90  					GitilesCommit: &bbpb.GitilesCommit{
    91  						Host:    "host",
    92  						Project: "proj",
    93  						Id:      "id2",
    94  						Ref:     "ref",
    95  					},
    96  				},
    97  				Id:         456,
    98  				Status:     bbpb.Status_STARTED,
    99  				CreateTime: &timestamppb.Timestamp{Seconds: 200},
   100  				StartTime:  &timestamppb.Timestamp{Seconds: 201},
   101  			}
   102  			mc.Client.EXPECT().ScheduleBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(res2, nil).Times(1)
   103  			mc.Client.EXPECT().GetBuild(gomock.Any(), gomock.Any(), gomock.Any()).Return(&bbpb.Build{}, nil).AnyTimes()
   104  			gitilesResponse := model.ChangeLogResponse{
   105  				Log: []*model.ChangeLog{
   106  					{
   107  						Commit: "3424",
   108  					},
   109  				},
   110  			}
   111  
   112  			// Set up gitiles.
   113  			gitilesResponseStr, _ := json.Marshal(gitilesResponse)
   114  			c = gitiles.MockedGitilesClientContext(c, map[string]string{
   115  				"https://chromium.googlesource.com/chromium/src/+log/3425~2..3425^": string(gitilesResponseStr),
   116  			})
   117  
   118  			fb := &model.LuciFailedBuild{}
   119  			So(datastore.Put(c, fb), ShouldBeNil)
   120  			datastore.GetTestable(c).CatchupIndexes()
   121  
   122  			compileFailure := &model.CompileFailure{
   123  				Id:            111,
   124  				Build:         datastore.KeyForObj(c, fb),
   125  				OutputTargets: []string{"target1"},
   126  			}
   127  			So(datastore.Put(c, compileFailure), ShouldBeNil)
   128  			datastore.GetTestable(c).CatchupIndexes()
   129  
   130  			analysis := &model.CompileFailureAnalysis{
   131  				Id:                 444,
   132  				CompileFailure:     datastore.KeyForObj(c, compileFailure),
   133  				FirstFailedBuildId: 1000,
   134  			}
   135  			So(datastore.Put(c, analysis), ShouldBeNil)
   136  			datastore.GetTestable(c).CatchupIndexes()
   137  
   138  			heuristicAnalysis := &model.CompileHeuristicAnalysis{
   139  				ParentAnalysis: datastore.KeyForObj(c, analysis),
   140  			}
   141  			So(datastore.Put(c, heuristicAnalysis), ShouldBeNil)
   142  			datastore.GetTestable(c).CatchupIndexes()
   143  
   144  			suspect := &model.Suspect{
   145  				Score:          10,
   146  				ParentAnalysis: datastore.KeyForObj(c, heuristicAnalysis),
   147  				GitilesCommit: bbpb.GitilesCommit{
   148  					Host:    "chromium.googlesource.com",
   149  					Project: "chromium/src",
   150  					Id:      "3425",
   151  				},
   152  			}
   153  			So(datastore.Put(c, suspect), ShouldBeNil)
   154  			datastore.GetTestable(c).CatchupIndexes()
   155  
   156  			err := processCulpritVerificationTask(c, 444, suspect.Id, suspect.ParentAnalysis.Encode())
   157  			So(err, ShouldBeNil)
   158  			So(datastore.Get(c, suspect), ShouldBeNil)
   159  			So(suspect.VerificationStatus, ShouldEqual, model.SuspectVerificationStatus_UnderVerification)
   160  			datastore.GetTestable(c).CatchupIndexes()
   161  
   162  			// Check that 2 rerun builds were created, and linked to suspect
   163  			rerun1 := &model.CompileRerunBuild{
   164  				Id: suspect.SuspectRerunBuild.IntID(),
   165  			}
   166  			err = datastore.Get(c, rerun1)
   167  			So(err, ShouldBeNil)
   168  			So(rerun1, ShouldResemble, &model.CompileRerunBuild{
   169  				Id: 123,
   170  				LuciBuild: model.LuciBuild{
   171  					BuildId: 123,
   172  					Project: "chromium",
   173  					Bucket:  "findit",
   174  					Builder: "single-revision",
   175  					Status:  bbpb.Status_STARTED,
   176  					GitilesCommit: bbpb.GitilesCommit{
   177  						Host:    res1.Input.GitilesCommit.Host,
   178  						Project: res1.Input.GitilesCommit.Project,
   179  						Id:      res1.Input.GitilesCommit.Id,
   180  						Ref:     res1.Input.GitilesCommit.Ref,
   181  					},
   182  					CreateTime: res1.CreateTime.AsTime(),
   183  					StartTime:  res1.StartTime.AsTime(),
   184  				},
   185  			})
   186  
   187  			rerun2 := &model.CompileRerunBuild{
   188  				Id: suspect.ParentRerunBuild.IntID(),
   189  			}
   190  			err = datastore.Get(c, rerun2)
   191  			So(err, ShouldBeNil)
   192  			So(rerun2, ShouldResemble, &model.CompileRerunBuild{
   193  				Id: 456,
   194  				LuciBuild: model.LuciBuild{
   195  					BuildId: 456,
   196  					Project: "chromium",
   197  					Bucket:  "findit",
   198  					Builder: "single-revision",
   199  					Status:  bbpb.Status_STARTED,
   200  					GitilesCommit: bbpb.GitilesCommit{
   201  						Host:    res2.Input.GitilesCommit.Host,
   202  						Project: res2.Input.GitilesCommit.Project,
   203  						Id:      res2.Input.GitilesCommit.Id,
   204  						Ref:     res2.Input.GitilesCommit.Ref,
   205  					},
   206  					CreateTime: res2.CreateTime.AsTime(),
   207  					StartTime:  res2.StartTime.AsTime(),
   208  				},
   209  			})
   210  
   211  			// Check that 2 SingleRerun model was created
   212  			q := datastore.NewQuery("SingleRerun").Eq("rerun_build", datastore.KeyForObj(c, rerun1))
   213  			singleReruns := []*model.SingleRerun{}
   214  			err = datastore.GetAll(c, q, &singleReruns)
   215  			So(err, ShouldBeNil)
   216  			So(len(singleReruns), ShouldEqual, 1)
   217  
   218  			q = datastore.NewQuery("SingleRerun").Eq("rerun_build", datastore.KeyForObj(c, rerun2))
   219  			singleReruns = []*model.SingleRerun{}
   220  			err = datastore.GetAll(c, q, &singleReruns)
   221  			So(err, ShouldBeNil)
   222  			So(len(singleReruns), ShouldEqual, 1)
   223  		})
   224  
   225  		Convey("Verify Suspect should not trigger any rerun if culprit found", func() {
   226  			_, _, cfa := testutil.CreateCompileFailureAnalysisAnalysisChain(c, 8001, "chromium", 555)
   227  
   228  			suspect := &model.Suspect{
   229  				VerificationStatus: model.SuspectVerificationStatus_VerificationScheduled,
   230  			}
   231  			So(datastore.Put(c, suspect), ShouldBeNil)
   232  			datastore.GetTestable(c).CatchupIndexes()
   233  			cfa.VerifiedCulprits = []*datastore.Key{
   234  				datastore.KeyForObj(c, suspect),
   235  			}
   236  			So(datastore.Put(c, cfa), ShouldBeNil)
   237  			datastore.GetTestable(c).CatchupIndexes()
   238  			err := VerifySuspect(c, suspect, 8001, 555)
   239  			So(err, ShouldBeNil)
   240  			datastore.GetTestable(c).CatchupIndexes()
   241  
   242  			// Check that no rerun was created
   243  			q := datastore.NewQuery("SingleRerun").Eq("analysis", datastore.KeyForObj(c, cfa))
   244  			singleReruns := []*model.SingleRerun{}
   245  			err = datastore.GetAll(c, q, &singleReruns)
   246  			So(err, ShouldBeNil)
   247  			So(len(singleReruns), ShouldEqual, 0)
   248  			So(suspect.VerificationStatus, ShouldEqual, model.SuspectVerificationStatus_Unverified)
   249  		})
   250  
   251  		Convey("Verify Suspect should also update analysis status", func() {
   252  			_, _, cfa := testutil.CreateCompileFailureAnalysisAnalysisChain(c, 8001, "chromium", 666)
   253  			cfa.Status = pb.AnalysisStatus_SUSPECTFOUND
   254  			cfa.RunStatus = pb.AnalysisRunStatus_STARTED
   255  			So(datastore.Put(c, cfa), ShouldBeNil)
   256  			datastore.GetTestable(c).CatchupIndexes()
   257  
   258  			nsa := testutil.CreateNthSectionAnalysis(c, cfa)
   259  			nsa.Status = pb.AnalysisStatus_SUSPECTFOUND
   260  			nsa.RunStatus = pb.AnalysisRunStatus_ENDED
   261  			So(datastore.Put(c, nsa), ShouldBeNil)
   262  			datastore.GetTestable(c).CatchupIndexes()
   263  
   264  			suspect := &model.Suspect{
   265  				VerificationStatus: model.SuspectVerificationStatus_VerificationScheduled,
   266  				ParentAnalysis:     datastore.KeyForObj(c, nsa),
   267  				GitilesCommit: bbpb.GitilesCommit{
   268  					Host:    "host",
   269  					Project: "proj",
   270  					Ref:     "ref",
   271  					Id:      "id",
   272  				},
   273  			}
   274  			// Create another suspect with same gitiles commit, so that no rerun build
   275  			// is triggered
   276  			suspect1 := &model.Suspect{
   277  				VerificationStatus: model.SuspectVerificationStatus_Vindicated,
   278  				ParentAnalysis:     datastore.KeyForObj(c, nsa),
   279  				GitilesCommit: bbpb.GitilesCommit{
   280  					Host:    "host",
   281  					Project: "proj",
   282  					Ref:     "ref",
   283  					Id:      "id",
   284  				},
   285  			}
   286  			So(datastore.Put(c, suspect), ShouldBeNil)
   287  			So(datastore.Put(c, suspect1), ShouldBeNil)
   288  			datastore.GetTestable(c).CatchupIndexes()
   289  
   290  			err := VerifySuspect(c, suspect, 8001, 666)
   291  			So(err, ShouldBeNil)
   292  			datastore.GetTestable(c).CatchupIndexes()
   293  
   294  			// Check that no rerun was created
   295  			q := datastore.NewQuery("SingleRerun").Eq("analysis", datastore.KeyForObj(c, cfa))
   296  			singleReruns := []*model.SingleRerun{}
   297  			err = datastore.GetAll(c, q, &singleReruns)
   298  			So(err, ShouldBeNil)
   299  			So(len(singleReruns), ShouldEqual, 0)
   300  			So(datastore.Get(c, suspect), ShouldBeNil)
   301  			So(suspect.VerificationStatus, ShouldEqual, model.SuspectVerificationStatus_Unverified)
   302  
   303  			// Verify the status is updated
   304  			So(datastore.Get(c, cfa), ShouldBeNil)
   305  			So(cfa.Status, ShouldEqual, pb.AnalysisStatus_SUSPECTFOUND)
   306  			So(cfa.RunStatus, ShouldEqual, pb.AnalysisRunStatus_ENDED)
   307  		})
   308  	})
   309  }
   310  
   311  func TestHasNewTargets(t *testing.T) {
   312  	cls := &model.ChangeLog{
   313  		ChangeLogDiffs: []model.ChangeLogDiff{
   314  			{
   315  				Type:    model.ChangeType_ADD,
   316  				NewPath: "src/device/bluetooth/floss/bluetooth_gatt_service_floss.h",
   317  			},
   318  			{
   319  				Type:    model.ChangeType_RENAME,
   320  				NewPath: "src/device/bluetooth/floss/bluetooth_gatt_service_floss_1.h",
   321  			},
   322  			{
   323  				Type:    model.ChangeType_COPY,
   324  				NewPath: "src/device/bluetooth/floss/bluetooth_gatt_service_floss_2.h",
   325  			},
   326  			{
   327  				Type:    model.ChangeType_MODIFY,
   328  				NewPath: "src/device/bluetooth/floss/bluetooth_gatt_service_floss_3.h",
   329  			},
   330  		},
   331  	}
   332  
   333  	c := context.Background()
   334  
   335  	Convey("Has New Targets", t, func() {
   336  		So(hasNewTarget(c, []string{"device/bluetooth/floss/bluetooth_gatt_service_floss.h"}, cls), ShouldBeTrue)
   337  		So(hasNewTarget(c, []string{"device/bluetooth/floss/bluetooth_gatt_service_floss_1.h"}, cls), ShouldBeTrue)
   338  		So(hasNewTarget(c, []string{"device/bluetooth/floss/bluetooth_gatt_service_floss_2.h"}, cls), ShouldBeTrue)
   339  		So(hasNewTarget(c, []string{"device/bluetooth/floss/bluetooth_gatt_service_floss_3.h"}, cls), ShouldBeFalse)
   340  	})
   341  }
   342  
   343  func TestGetPriority(t *testing.T) {
   344  	t.Parallel()
   345  	c := memory.Use(context.Background())
   346  	cl := testclock.New(testclock.TestTimeUTC)
   347  	c = clock.Set(c, cl)
   348  
   349  	Convey("GetPriority", t, func() {
   350  		now := clock.Now(c)
   351  		fb := &model.LuciFailedBuild{
   352  			Id: 123,
   353  			LuciBuild: model.LuciBuild{
   354  				StartTime: now,
   355  				EndTime:   now.Add(9 * time.Minute),
   356  			},
   357  		}
   358  		So(datastore.Put(c, fb), ShouldBeNil)
   359  		datastore.GetTestable(c).CatchupIndexes()
   360  
   361  		cf := &model.CompileFailure{
   362  			Build: datastore.KeyForObj(c, fb),
   363  		}
   364  		So(datastore.Put(c, cf), ShouldBeNil)
   365  		datastore.GetTestable(c).CatchupIndexes()
   366  
   367  		cfa := &model.CompileFailureAnalysis{
   368  			CompileFailure: datastore.KeyForObj(c, cf),
   369  		}
   370  		So(datastore.Put(c, cfa), ShouldBeNil)
   371  		datastore.GetTestable(c).CatchupIndexes()
   372  
   373  		ha := &model.CompileHeuristicAnalysis{
   374  			ParentAnalysis: datastore.KeyForObj(c, cfa),
   375  		}
   376  		So(datastore.Put(c, ha), ShouldBeNil)
   377  		datastore.GetTestable(c).CatchupIndexes()
   378  
   379  		suspect := &model.Suspect{
   380  			ParentAnalysis: datastore.KeyForObj(c, ha),
   381  			Score:          1,
   382  			Id:             123,
   383  			ReviewUrl:      "reviewUrl",
   384  		}
   385  		So(datastore.Put(c, suspect), ShouldBeNil)
   386  		datastore.GetTestable(c).CatchupIndexes()
   387  		pri, err := getSuspectPriority(c, suspect)
   388  		So(err, ShouldBeNil)
   389  		So(pri, ShouldEqual, 120)
   390  		suspect.Score = 5
   391  		pri, err = getSuspectPriority(c, suspect)
   392  		So(err, ShouldBeNil)
   393  		So(pri, ShouldEqual, 100)
   394  		suspect.Score = 15
   395  		pri, err = getSuspectPriority(c, suspect)
   396  		So(err, ShouldBeNil)
   397  		So(pri, ShouldEqual, 80)
   398  
   399  		// Add another suspect
   400  		suspect1 := &model.Suspect{
   401  			Score:              1,
   402  			Id:                 124,
   403  			ReviewUrl:          "reviewUrl",
   404  			VerificationStatus: model.SuspectVerificationStatus_UnderVerification,
   405  		}
   406  		So(datastore.Put(c, suspect1), ShouldBeNil)
   407  		datastore.GetTestable(c).CatchupIndexes()
   408  		pri, err = getSuspectPriority(c, suspect)
   409  		So(err, ShouldBeNil)
   410  		So(pri, ShouldEqual, 100)
   411  
   412  		cfa.IsTreeCloser = true
   413  		So(datastore.Put(c, cfa), ShouldBeNil)
   414  		datastore.GetTestable(c).CatchupIndexes()
   415  		pri, err = getSuspectPriority(c, suspect)
   416  		So(err, ShouldBeNil)
   417  		So(pri, ShouldEqual, 30)
   418  	})
   419  }
   420  
   421  func TestCheckSuspectWithSameCommitExist(t *testing.T) {
   422  	t.Parallel()
   423  	c := memory.Use(context.Background())
   424  
   425  	Convey("CheckSuspectWithSameCommitExist", t, func() {
   426  		_, _, cfa := testutil.CreateCompileFailureAnalysisAnalysisChain(c, 8000, "chromium", 555)
   427  		nsa := testutil.CreateNthSectionAnalysis(c, cfa)
   428  		suspect := testutil.CreateNthSectionSuspect(c, nsa)
   429  
   430  		exist, err := checkSuspectWithSameCommitExist(c, cfa, suspect)
   431  		So(err, ShouldBeNil)
   432  		So(exist, ShouldBeFalse)
   433  
   434  		ha := testutil.CreateHeuristicAnalysis(c, cfa)
   435  		s1 := testutil.CreateHeuristicSuspect(c, ha, model.SuspectVerificationStatus_Unverified)
   436  
   437  		exist, err = checkSuspectWithSameCommitExist(c, cfa, suspect)
   438  		So(err, ShouldBeNil)
   439  		So(exist, ShouldBeFalse)
   440  
   441  		s1.VerificationStatus = model.SuspectVerificationStatus_UnderVerification
   442  		So(datastore.Put(c, s1), ShouldBeNil)
   443  		datastore.GetTestable(c).CatchupIndexes()
   444  		exist, err = checkSuspectWithSameCommitExist(c, cfa, suspect)
   445  		So(err, ShouldBeNil)
   446  		So(exist, ShouldBeTrue)
   447  	})
   448  }