go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/util/datastoreutil/analysis_result_queries.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 datastoreutil contains utility functions related to datastore entities
    16  package datastoreutil
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  
    22  	"go.chromium.org/luci/bisection/model"
    23  	pb "go.chromium.org/luci/bisection/proto/v1"
    24  	"go.chromium.org/luci/common/errors"
    25  	"go.chromium.org/luci/common/logging"
    26  	"go.chromium.org/luci/gae/service/datastore"
    27  )
    28  
    29  // GetBuild returns the failed build in the datastore with the given Buildbucket ID
    30  // Note: if the build is not found, this will return (nil, nil)
    31  func GetBuild(c context.Context, bbid int64) (*model.LuciFailedBuild, error) {
    32  	build := &model.LuciFailedBuild{Id: bbid}
    33  	switch err := datastore.Get(c, build); {
    34  	case err == datastore.ErrNoSuchEntity:
    35  		return nil, nil
    36  	case err != nil:
    37  		return nil, err
    38  	}
    39  
    40  	return build, nil
    41  }
    42  
    43  // GetAnalysisForBuild returns the failure analysis associated with the given Buildbucket ID
    44  // Note: if the build or its analysis is not found, this will return (nil, nil)
    45  func GetAnalysisForBuild(c context.Context, bbid int64) (*model.CompileFailureAnalysis, error) {
    46  	buildModel, err := GetBuild(c, bbid)
    47  	if (err != nil) || (buildModel == nil) {
    48  		return nil, err
    49  	}
    50  
    51  	cfModel := &model.CompileFailure{
    52  		Id:    bbid,
    53  		Build: datastore.KeyForObj(c, buildModel),
    54  	}
    55  	switch err := datastore.Get(c, cfModel); {
    56  	case err == datastore.ErrNoSuchEntity:
    57  		return nil, nil
    58  	case err != nil:
    59  		return nil, err
    60  	default:
    61  		//continue
    62  	}
    63  
    64  	// If the compile failure was "merged" into another compile failure,
    65  	// use the merged one instead.
    66  	cfKey := datastore.KeyForObj(c, cfModel)
    67  	if cfModel.MergedFailureKey != nil {
    68  		cfKey = cfModel.MergedFailureKey
    69  	}
    70  
    71  	// Get the analysis for the compile failure
    72  	q := datastore.NewQuery("CompileFailureAnalysis").Eq("compile_failure", cfKey)
    73  	analyses := []*model.CompileFailureAnalysis{}
    74  	err = datastore.GetAll(c, q, &analyses)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	if len(analyses) == 0 {
    79  		return nil, nil
    80  	}
    81  	if len(analyses) > 1 {
    82  		logging.Warningf(c, "Found more than one analysis for build %d", bbid)
    83  	}
    84  	return analyses[0], nil
    85  }
    86  
    87  // GetHeuristicAnalysis returns the heuristic analysis associated with the given failure analysis
    88  func GetHeuristicAnalysis(c context.Context, analysis *model.CompileFailureAnalysis) (*model.CompileHeuristicAnalysis, error) {
    89  	// Gets heuristic analysis results.
    90  	q := datastore.NewQuery("CompileHeuristicAnalysis").Ancestor(datastore.KeyForObj(c, analysis))
    91  	heuristicAnalyses := []*model.CompileHeuristicAnalysis{}
    92  	err := datastore.GetAll(c, q, &heuristicAnalyses)
    93  
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	if len(heuristicAnalyses) == 0 {
    99  		// No heuristic analysis
   100  		return nil, nil
   101  	}
   102  
   103  	if len(heuristicAnalyses) > 1 {
   104  		logging.Warningf(c, "Found multiple heuristic analysis for analysis %d", analysis.Id)
   105  	}
   106  
   107  	heuristicAnalysis := heuristicAnalyses[0]
   108  	return heuristicAnalysis, nil
   109  }
   110  
   111  // GetSuspectsForHeuristicAnalysis returns the heuristic suspects identified by the given heuristic analysis
   112  func GetSuspectsForHeuristicAnalysis(c context.Context, heuristicAnalysis *model.CompileHeuristicAnalysis) ([]*model.Suspect, error) {
   113  	// Getting the suspects for heuristic analysis
   114  	suspects := []*model.Suspect{}
   115  	q := datastore.NewQuery("Suspect").Ancestor(datastore.KeyForObj(c, heuristicAnalysis)).Order("-score")
   116  	err := datastore.GetAll(c, q, &suspects)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	return suspects, nil
   122  }
   123  
   124  // GetSuspectForNthSectionAnalysis returns the heuristic suspects identified by the given heuristic analysis
   125  func GetSuspectForNthSectionAnalysis(c context.Context, nthsectionAnalysis *model.CompileNthSectionAnalysis) (*model.Suspect, error) {
   126  	// Getting the suspects for nthsection analysis
   127  	suspects := []*model.Suspect{}
   128  	q := datastore.NewQuery("Suspect").Ancestor(datastore.KeyForObj(c, nthsectionAnalysis))
   129  	err := datastore.GetAll(c, q, &suspects)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	if len(suspects) == 0 {
   134  		return nil, nil
   135  	}
   136  	if len(suspects) > 0 {
   137  		logging.Warningf(c, "nthsectionAnalysis has more than 1 suspect %d", len(suspects))
   138  	}
   139  	return suspects[0], nil
   140  }
   141  
   142  // GetCompileFailureForAnalysisID gets CompileFailure for analysisID.
   143  func GetCompileFailureForAnalysisID(c context.Context, analysisID int64) (*model.CompileFailure, error) {
   144  	cfa, err := GetCompileFailureAnalysis(c, analysisID)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	return GetCompileFailureForAnalysis(c, cfa)
   149  }
   150  
   151  // GetCompileFailureForAnalysis gets CompileFailure for analysis
   152  func GetCompileFailureForAnalysis(c context.Context, cfa *model.CompileFailureAnalysis) (*model.CompileFailure, error) {
   153  	compileFailure := &model.CompileFailure{
   154  		Id: cfa.CompileFailure.IntID(),
   155  		// We need to specify the parent here because this is a multi-part key.
   156  		Build: cfa.CompileFailure.Parent(),
   157  	}
   158  	err := datastore.Get(c, compileFailure)
   159  	if err != nil {
   160  		return nil, errors.Annotate(err, "getting compile failure for analysis %d", cfa.Id).Err()
   161  	}
   162  	return compileFailure, nil
   163  }
   164  
   165  // GetFailedBuildForAnalysis gets LuciFailedBuild for analysis.
   166  func GetFailedBuildForAnalysis(c context.Context, cfa *model.CompileFailureAnalysis) (*model.LuciFailedBuild, error) {
   167  	cf, err := GetCompileFailureForAnalysis(c, cfa)
   168  	if err != nil {
   169  		return nil, errors.Annotate(err, "getting compile failure for analysis %d", cfa.Id).Err()
   170  	}
   171  	build := &model.LuciFailedBuild{Id: cf.Build.IntID()}
   172  	err = datastore.Get(c, build)
   173  	if err != nil {
   174  		return nil, errors.Annotate(err, "getting failed build for analysis %d", cfa.Id).Err()
   175  	}
   176  	return build, nil
   177  }
   178  
   179  // GetRerunsForRerunBuild returns all SingleRerun for a rerunBuild
   180  func GetRerunsForRerunBuild(c context.Context, rerunBuild *model.CompileRerunBuild) ([]*model.SingleRerun, error) {
   181  	q := datastore.NewQuery("SingleRerun").Eq("rerun_build", datastore.KeyForObj(c, rerunBuild)).Order("start_time")
   182  	singleReruns := []*model.SingleRerun{}
   183  	err := datastore.GetAll(c, q, &singleReruns)
   184  	return singleReruns, errors.Annotate(err, "get reruns for rerun build %d", rerunBuild.Id).Err()
   185  }
   186  
   187  // GetLastRerunForRerunBuild returns the last SingleRerun for a rerunBuild (based on start_time)
   188  func GetLastRerunForRerunBuild(c context.Context, rerunBuild *model.CompileRerunBuild) (*model.SingleRerun, error) {
   189  	reruns, err := GetRerunsForRerunBuild(c, rerunBuild)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	if len(reruns) == 0 {
   194  		return nil, fmt.Errorf("got no SingleRerun for build %d", rerunBuild.Id)
   195  	}
   196  	return reruns[len(reruns)-1], nil
   197  }
   198  
   199  // GetNthSectionAnalysis returns the nthsection analysis associated with the given failure analysis
   200  func GetNthSectionAnalysis(c context.Context, analysis *model.CompileFailureAnalysis) (*model.CompileNthSectionAnalysis, error) {
   201  	q := datastore.NewQuery("CompileNthSectionAnalysis").Ancestor(datastore.KeyForObj(c, analysis))
   202  	nthSectionAnalyses := []*model.CompileNthSectionAnalysis{}
   203  	err := datastore.GetAll(c, q, &nthSectionAnalyses)
   204  
   205  	if err != nil {
   206  		return nil, errors.Annotate(err, "couldn't get nthsection analysis for analysis %d", analysis.Id).Err()
   207  	}
   208  
   209  	if len(nthSectionAnalyses) == 0 {
   210  		return nil, nil
   211  	}
   212  
   213  	if len(nthSectionAnalyses) > 1 {
   214  		return nil, fmt.Errorf("found more than 1 nth section analysis for analysis %d", analysis.Id)
   215  	}
   216  
   217  	return nthSectionAnalyses[0], nil
   218  }
   219  
   220  // GetCompileFailureAnalysis gets compile failure analysis by its id
   221  func GetCompileFailureAnalysis(c context.Context, analysisID int64) (*model.CompileFailureAnalysis, error) {
   222  	analysis := &model.CompileFailureAnalysis{
   223  		Id: analysisID,
   224  	}
   225  	err := datastore.Get(c, analysis)
   226  	if err != nil {
   227  		return nil, errors.Annotate(err, "couldn't get CompileFailureAnalysis %d", analysis.Id).Err()
   228  	}
   229  	return analysis, err
   230  }
   231  
   232  // GetOtherSuspectsWithSameCL returns the list of Suspect(from different analyses)
   233  // that has the same reviewURL as this suspect.
   234  // It is meant to check if the same CL is the suspects for multiple failures.
   235  func GetOtherSuspectsWithSameCL(c context.Context, suspect *model.Suspect) ([]*model.Suspect, error) {
   236  	suspects := []*model.Suspect{}
   237  	q := datastore.NewQuery("Suspect").Eq("review_url", suspect.ReviewUrl)
   238  	err := datastore.GetAll(c, q, &suspects)
   239  	if err != nil {
   240  		return nil, errors.Annotate(err, "failed GetSameSuspects").Err()
   241  	}
   242  
   243  	// Remove this suspect
   244  	for i, s := range suspects {
   245  		if s.Id == suspect.Id {
   246  			return append(suspects[:i], suspects[i+1:]...), nil
   247  		}
   248  	}
   249  	return suspects, nil
   250  }
   251  
   252  // GetLatestBuildFailureForBuilder returns the latest LuciFailedBuild model for a builderID
   253  // If there is no build failure, return (nil, nil)
   254  func GetLatestBuildFailureForBuilder(c context.Context, project string, bucket string, builder string) (*model.LuciFailedBuild, error) {
   255  	builds := []*model.LuciFailedBuild{}
   256  	q := datastore.NewQuery("LuciFailedBuild").Eq("project", project).Eq("bucket", bucket).Eq("builder", builder).Order("-end_time").Limit(1)
   257  	err := datastore.GetAll(c, q, &builds)
   258  	if err != nil {
   259  		return nil, errors.Annotate(err, "failed querying LuciFailedBuild").Err()
   260  	}
   261  
   262  	if len(builds) == 0 {
   263  		return nil, nil
   264  	}
   265  	return builds[0], nil
   266  }
   267  
   268  // GetLatestAnalysisForBuilder returns the latest CompileFailureAnalysis for a builderID
   269  // If there is no analysis, return (nil, nil)
   270  func GetLatestAnalysisForBuilder(c context.Context, project string, bucket string, builder string) (*model.CompileFailureAnalysis, error) {
   271  	build, err := GetLatestBuildFailureForBuilder(c, project, bucket, builder)
   272  	if err != nil {
   273  		return nil, errors.Annotate(err, "cannot GetLatestBuildFailureForBuilder").Err()
   274  	}
   275  	if build == nil {
   276  		return nil, nil
   277  	}
   278  	return GetAnalysisForBuild(c, build.Id)
   279  }
   280  
   281  // GetRerunsForAnalysis returns all reruns for an analysis
   282  // The result is sorted by start_time
   283  func GetRerunsForAnalysis(c context.Context, cfa *model.CompileFailureAnalysis) ([]*model.SingleRerun, error) {
   284  	q := datastore.NewQuery("SingleRerun").Eq("analysis", datastore.KeyForObj(c, cfa)).Order("start_time")
   285  	reruns := []*model.SingleRerun{}
   286  	err := datastore.GetAll(c, q, &reruns)
   287  	if err != nil {
   288  		return nil, errors.Annotate(err, "getting reruns for analysis %d", cfa.Id).Err()
   289  	}
   290  	return reruns, nil
   291  }
   292  
   293  func GetRerunsForNthSectionAnalysis(c context.Context, nsa *model.CompileNthSectionAnalysis) ([]*model.SingleRerun, error) {
   294  	q := datastore.NewQuery("SingleRerun").Eq("analysis", nsa.ParentAnalysis).Eq("rerun_type", model.RerunBuildType_NthSection)
   295  	reruns := []*model.SingleRerun{}
   296  	err := datastore.GetAll(c, q, &reruns)
   297  	if err != nil {
   298  		return nil, errors.Annotate(err, "getting reruns for analysis %d", nsa.ParentAnalysis.IntID()).Err()
   299  	}
   300  	return reruns, nil
   301  }
   302  
   303  // GetTestFailureAnalysis gets test failure analysis by its ID.
   304  func GetTestFailureAnalysis(ctx context.Context, analysisID int64) (*model.TestFailureAnalysis, error) {
   305  	analysis := &model.TestFailureAnalysis{
   306  		ID: analysisID,
   307  	}
   308  	err := datastore.Get(ctx, analysis)
   309  	if err != nil {
   310  		return nil, errors.Annotate(err, "get TestFailureAnalysis with id %d", analysis.ID).Err()
   311  	}
   312  	return analysis, err
   313  }
   314  
   315  // GetPrimaryTestFailure gets the primary TestFailure model for a TestFailureAnalysis.
   316  func GetPrimaryTestFailure(ctx context.Context, analysis *model.TestFailureAnalysis) (*model.TestFailure, error) {
   317  	if analysis.TestFailure == nil {
   318  		return nil, errors.New("no TestFailure for analysis")
   319  	}
   320  	testFailure := &model.TestFailure{
   321  		ID: analysis.TestFailure.IntID(),
   322  	}
   323  	err := datastore.Get(ctx, testFailure)
   324  	if err != nil {
   325  		return nil, errors.Annotate(err, "get TestFailure from datastore %d", analysis.TestFailure.IntID()).Err()
   326  	}
   327  	return testFailure, nil
   328  }
   329  
   330  // GetTestFailureBundle returns a TestFailureBundle for a TestFailureAnalysis.
   331  func GetTestFailureBundle(ctx context.Context, tfa *model.TestFailureAnalysis) (*model.TestFailureBundle, error) {
   332  	return getTestFailureBundleWithAnalysisKey(ctx, datastore.KeyForObj(ctx, tfa))
   333  }
   334  
   335  func getTestFailureBundleWithAnalysisKey(ctx context.Context, analysisKey *datastore.Key) (*model.TestFailureBundle, error) {
   336  	q := datastore.NewQuery("TestFailure").Eq("analysis_key", analysisKey)
   337  	tfs := []*model.TestFailure{}
   338  	err := datastore.GetAll(ctx, q, &tfs)
   339  	if err != nil {
   340  		return nil, errors.Annotate(err, "get test failures for analysis").Err()
   341  	}
   342  	if len(tfs) == 0 {
   343  		return nil, errors.New("no test failure for analysis")
   344  	}
   345  	bundle := &model.TestFailureBundle{}
   346  	err = bundle.Add(tfs)
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  	if bundle.Primary() == nil {
   351  		return nil, errors.New("no primary test failure for analysis")
   352  	}
   353  	return bundle, nil
   354  }
   355  
   356  // GetTestSingleRerun gets test single rerun by its ID.
   357  func GetTestSingleRerun(ctx context.Context, rerunID int64) (*model.TestSingleRerun, error) {
   358  	rerun := &model.TestSingleRerun{
   359  		ID: rerunID,
   360  	}
   361  	err := datastore.Get(ctx, rerun)
   362  	if err != nil {
   363  		return nil, errors.Annotate(err, "get test single rerun with id %d", rerunID).Err()
   364  	}
   365  	return rerun, err
   366  }
   367  
   368  // GetTestFailure gets test failure by its ID.
   369  func GetTestFailure(ctx context.Context, failureID int64) (*model.TestFailure, error) {
   370  	failure := &model.TestFailure{
   371  		ID: failureID,
   372  	}
   373  	err := datastore.Get(ctx, failure)
   374  	if err != nil {
   375  		return nil, errors.Annotate(err, "get TestFailure with id %d", failure.ID).Err()
   376  	}
   377  	return failure, err
   378  }
   379  
   380  // GetTestNthSectionAnalysis gets test nthsection analysis by its ID.
   381  func GetTestNthSectionAnalysis(ctx context.Context, analysisID int64) (*model.TestNthSectionAnalysis, error) {
   382  	nsa := &model.TestNthSectionAnalysis{
   383  		ID: analysisID,
   384  	}
   385  	err := datastore.Get(ctx, nsa)
   386  	if err != nil {
   387  		return nil, errors.Annotate(err, "get Nthsection analysis with id %d", nsa.ID).Err()
   388  	}
   389  	return nsa, err
   390  }
   391  
   392  // GetTestNthSectionForAnalysis gets test nthsection analysis for a test failure analysis.
   393  // This may return nil if the nthsection analysis has not been created yet.
   394  func GetTestNthSectionForAnalysis(ctx context.Context, tfa *model.TestFailureAnalysis) (*model.TestNthSectionAnalysis, error) {
   395  	q := datastore.NewQuery("TestNthSectionAnalysis").Eq("parent_analysis_key", datastore.KeyForObj(ctx, tfa))
   396  	analyses := []*model.TestNthSectionAnalysis{}
   397  	err := datastore.GetAll(ctx, q, &analyses)
   398  	if err != nil {
   399  		return nil, errors.Annotate(err, "get all").Err()
   400  	}
   401  	if len(analyses) == 0 {
   402  		return nil, nil
   403  	}
   404  	if len(analyses) > 1 {
   405  		return nil, errors.Annotate(err, "found more than 1 nthsection analysis: %d", len(analyses)).Err()
   406  	}
   407  	return analyses[0], nil
   408  }
   409  
   410  // GetInProgressReruns returns the reruns which are in progress.
   411  func GetInProgressReruns(ctx context.Context, tfa *model.TestFailureAnalysis) ([]*model.TestSingleRerun, error) {
   412  	q := datastore.NewQuery("TestSingleRerun").
   413  		Eq("analysis_key", datastore.KeyForObj(ctx, tfa)).
   414  		Eq("status", pb.RerunStatus_RERUN_STATUS_IN_PROGRESS)
   415  
   416  	reruns := []*model.TestSingleRerun{}
   417  	err := datastore.GetAll(ctx, q, &reruns)
   418  	if err != nil {
   419  		return nil, errors.Annotate(err, "get test reruns").Err()
   420  	}
   421  	return reruns, nil
   422  }
   423  
   424  func GetVerificationRerunsForTestCulprit(ctx context.Context, culprit *model.Suspect) (culpritRerun *model.TestSingleRerun, parentRerun *model.TestSingleRerun, reterr error) {
   425  	var err error
   426  	if culprit.SuspectRerunBuild != nil {
   427  		culpritRerun, err = GetTestSingleRerun(ctx, culprit.SuspectRerunBuild.IntID())
   428  		if err != nil {
   429  			return nil, nil, errors.Annotate(err, "get suspect rerun").Err()
   430  		}
   431  	}
   432  	if culprit.ParentRerunBuild != nil {
   433  		parentRerun, err = GetTestSingleRerun(ctx, culprit.ParentRerunBuild.IntID())
   434  		if err != nil {
   435  			return nil, nil, errors.Annotate(err, "get parent rerun").Err()
   436  		}
   437  	}
   438  	return culpritRerun, parentRerun, nil
   439  }
   440  
   441  // GetTestNthSectionReruns returns the nthsection reruns.
   442  // The reruns are ordered by create time.
   443  func GetTestNthSectionReruns(ctx context.Context, nsa *model.TestNthSectionAnalysis) ([]*model.TestSingleRerun, error) {
   444  	q := datastore.NewQuery("TestSingleRerun").Eq("nthsection_analysis_key", datastore.KeyForObj(ctx, nsa)).Order("luci_build.create_time")
   445  	reruns := []*model.TestSingleRerun{}
   446  	err := datastore.GetAll(ctx, q, &reruns)
   447  	if err != nil {
   448  		return nil, errors.Annotate(err, "get test reruns").Err()
   449  	}
   450  	return reruns, nil
   451  }
   452  
   453  func GetProjectForCompileFailureAnalysisID(ctx context.Context, analysisID int64) (string, error) {
   454  	cfa, err := GetCompileFailureAnalysis(ctx, analysisID)
   455  	if err != nil {
   456  		return "", errors.Annotate(err, "get compile failure analysis").Err()
   457  	}
   458  	return GetProjectForCompileFailureAnalysis(ctx, cfa)
   459  }
   460  
   461  func GetProjectForCompileFailureAnalysis(ctx context.Context, cfa *model.CompileFailureAnalysis) (string, error) {
   462  	cf, err := GetCompileFailureForAnalysis(ctx, cfa)
   463  	if err != nil {
   464  		return "", errors.Annotate(err, "get compile failure for analysis").Err()
   465  	}
   466  	build, err := GetBuild(ctx, cf.Build.IntID())
   467  	if err != nil {
   468  		return "", errors.Annotate(err, "get build").Err()
   469  	}
   470  	return build.Project, nil
   471  }