go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/model/model.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 model contains the datastore model for LUCI Bisection.
    16  package model
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"strings"
    22  	"time"
    23  
    24  	pb "go.chromium.org/luci/bisection/proto/v1"
    25  	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
    26  	"go.chromium.org/luci/common/logging"
    27  	"go.chromium.org/luci/gae/service/datastore"
    28  )
    29  
    30  type RerunBuildType string
    31  
    32  const (
    33  	RerunBuildType_CulpritVerification RerunBuildType = "Culprit Verification"
    34  	RerunBuildType_NthSection          RerunBuildType = "NthSection"
    35  )
    36  
    37  type SuspectVerificationStatus string
    38  
    39  const (
    40  	// The suspect is not verified and no verification is happening
    41  	SuspectVerificationStatus_Unverified SuspectVerificationStatus = "Unverified"
    42  	// The suspect is scheduled to be verified (via a task queue)
    43  	SuspectVerificationStatus_VerificationScheduled SuspectVerificationStatus = "Verification Scheduled"
    44  	// The suspect is under verification
    45  	SuspectVerificationStatus_UnderVerification SuspectVerificationStatus = "Under Verification"
    46  	// The suspect is confirmed to be culprit
    47  	SuspectVerificationStatus_ConfirmedCulprit SuspectVerificationStatus = "Confirmed Culprit"
    48  	// This is a false positive - the suspect is not the culprit
    49  	SuspectVerificationStatus_Vindicated SuspectVerificationStatus = "Vindicated"
    50  	// Some error happened during verification
    51  	SuspectVerificationStatus_VerificationError SuspectVerificationStatus = "Verification Error"
    52  	// The verification is canceled
    53  	SuspectVerificationStatus_Canceled SuspectVerificationStatus = "Canceled"
    54  )
    55  
    56  type SuspectType string
    57  
    58  const (
    59  	SuspectType_Heuristic  SuspectType = "Heuristic"
    60  	SuspectType_NthSection SuspectType = "NthSection"
    61  )
    62  
    63  type Platform string
    64  
    65  const (
    66  	// The build didn't specified a platform
    67  	PlatformUnspecified Platform = "unspecified"
    68  	// Platform is not win, mac or linux
    69  	PlatformUnknown Platform = "unknown"
    70  	PlatformWindows Platform = "win"
    71  	PlatformMac     Platform = "mac"
    72  	PlatformLinux   Platform = "linux"
    73  )
    74  
    75  // LuciBuild represents one LUCI build.
    76  // Deprecated. Please use LUCIBuild model instead.
    77  // It is kept here because some old code is still using it.
    78  type LuciBuild struct {
    79  	BuildId     int64  `gae:"build_id"`
    80  	Project     string `gae:"project"`
    81  	Bucket      string `gae:"bucket"`
    82  	Builder     string `gae:"builder"`
    83  	BuildNumber int    `gae:"build_number"`
    84  	buildbucketpb.GitilesCommit
    85  	CreateTime time.Time            `gae:"create_time"`
    86  	EndTime    time.Time            `gae:"end_time"`
    87  	StartTime  time.Time            `gae:"start_time"`
    88  	Status     buildbucketpb.Status `gae:"status"`
    89  }
    90  
    91  type LUCIBuild struct {
    92  	BuildID       int64                        `gae:"build_id"`
    93  	Project       string                       `gae:"project"`
    94  	Bucket        string                       `gae:"bucket"`
    95  	Builder       string                       `gae:"builder"`
    96  	BuildNumber   int                          `gae:"build_number"`
    97  	GitilesCommit *buildbucketpb.GitilesCommit `gae:"gitiles_commit"`
    98  	CreateTime    time.Time                    `gae:"create_time"`
    99  	EndTime       time.Time                    `gae:"end_time"`
   100  	StartTime     time.Time                    `gae:"start_time"`
   101  	Status        buildbucketpb.Status         `gae:"status"`
   102  }
   103  
   104  type LuciFailedBuild struct {
   105  	// Id is the build Id
   106  	Id int64 `gae:"$id"`
   107  	LuciBuild
   108  	// Obsolete field - specify BuildFailureType instead
   109  	FailureType string `gae:"failure_type"`
   110  	// Failure type for the build
   111  	BuildFailureType pb.BuildFailureType `gae:"build_failure_type"`
   112  	// The platform of the failure
   113  	Platform Platform `gae:"platform"`
   114  	// The sheriff rotations that watch the builder.
   115  	SheriffRotations []string `gae:"sheriff_rotations"`
   116  }
   117  
   118  // CompileFailure represents a compile failure in one or more targets.
   119  type CompileFailure struct {
   120  	// Id is the build Id of the compile failure
   121  	Id int64 `gae:"$id"`
   122  	// The key to LuciFailedBuild that the failure belongs to.
   123  	Build *datastore.Key `gae:"$parent"`
   124  
   125  	// The list of output targets that failed to compile.
   126  	// This is to speed up the compilation process, as we only want to rerun failed targets.
   127  	OutputTargets []string `gae:"output_targets"`
   128  
   129  	// The list of source files resulting in compile failures
   130  	FailedFiles []string `gae:"failed_files"`
   131  
   132  	// Compile rule, e.g. ACTION, CXX, etc.
   133  	// For chromium builds, it can be found in json.output[ninja_info] log of
   134  	// compile step.
   135  	// For chromeos builds, it can be found in an output property 'compile_failure'
   136  	// of the build.
   137  	Rule string `gae:"rule"`
   138  
   139  	// Only for CC and CXX rules
   140  	// These are the source files that this compile failure uses as input
   141  	Dependencies []string `gae:"dependencies"`
   142  
   143  	// Key to the CompileFailure that this failure merges into.
   144  	// If this exists, no analysis on current failure, instead use the results
   145  	// of merged_failure.
   146  	MergedFailureKey *datastore.Key `gae:"merged_failure_key"`
   147  }
   148  
   149  // CompileFailureAnalysis is the analysis for CompileFailure.
   150  // This stores information that is needed during the analysis, and also
   151  // some metadata for the analysis.
   152  type CompileFailureAnalysis struct {
   153  	Id int64 `gae:"$id"`
   154  	// Key to the CompileFailure that this analysis analyses.
   155  	CompileFailure *datastore.Key `gae:"compile_failure"`
   156  	// Time when the analysis is created.
   157  	CreateTime time.Time `gae:"create_time"`
   158  	// Time when the analysis starts to run.
   159  	StartTime time.Time `gae:"start_time"`
   160  	// Time when the analysis ends, or canceled.
   161  	EndTime time.Time `gae:"end_time"`
   162  	// Status of the analysis
   163  	Status pb.AnalysisStatus `gae:"status"`
   164  	// Run status of the analysis
   165  	RunStatus pb.AnalysisRunStatus `gae:"run_status"`
   166  	// Id of the build in which the compile failures occurred the first time in
   167  	// a sequence of consecutive failed builds.
   168  	FirstFailedBuildId int64 `gae:"first_failed_build_id"`
   169  	// Id of the latest build in which the failures did not happen.
   170  	LastPassedBuildId int64 `gae:"last_passed_build_id"`
   171  	// Initial regression range to find the culprit
   172  	InitialRegressionRange *pb.RegressionRange `gae:"initial_regression_range"`
   173  	// Key to the heuristic suspects that was verified by Culprit verification
   174  	// In some rare cases, there are more than 1 culprit for the regression range.
   175  	VerifiedCulprits []*datastore.Key `gae:"verified_culprits"`
   176  	// Indicates whether the analysis should be cancelled or not,
   177  	// such as in the situation where the corresponding builder start passing again
   178  	ShouldCancel bool `gae:"should_cancel"`
   179  	// Is this analysis for a tree closer failure.
   180  	// If it is, all reruns of this analysis should have higher priority.
   181  	IsTreeCloser bool `gae:"is_tree_closer"`
   182  }
   183  
   184  // CompileRerunBuild is one rerun build for CompileFailureAnalysis.
   185  // The rerun build may be for nth-section analysis or for culprit verification.
   186  type CompileRerunBuild struct {
   187  	// Id is the buildbucket Id for the rerun build.
   188  	Id int64 `gae:"$id"`
   189  	// LUCI build data
   190  	LuciBuild
   191  	// For backward compatibility due to removed fields.
   192  	// See https://source.chromium.org/chromium/infra/infra/+/main:go/src/go.chromium.org/luci/gae/service/datastore/pls.go;l=100
   193  	_ datastore.PropertyMap `gae:"-,extra"`
   194  }
   195  
   196  // SingleRerun represents one rerun for a particular compile/test failures for a particular commit.
   197  // Initially, we wanted to trigger multiple reruns for different commits in the same build,
   198  // but it is not possible. We can only trigger one rerun per rerun build, i.e. the
   199  // relationship between single rerun : rerun build is 1:1.
   200  type SingleRerun struct {
   201  	Id int64 `gae:"$id"`
   202  	// Key to the parent CompileRerunBuild
   203  	RerunBuild *datastore.Key `gae:"rerun_build"`
   204  	// Type for the rerun build
   205  	// Either culprit verification or nth section run.
   206  	Type RerunBuildType `gae:"rerun_type"`
   207  	// Key to the CompileFailureAnalysis of this SingleRerun
   208  	// This is mainly used for getting all reruns for an analysis,
   209  	// for the purpose of nth-section analysis
   210  	Analysis *datastore.Key `gae:"analysis"`
   211  	// The commit that this SingleRerun runs on
   212  	buildbucketpb.GitilesCommit
   213  	// Time when the rerun was created
   214  	CreateTime time.Time `gae:"create_time"`
   215  	// Time when the rerun starts.
   216  	StartTime time.Time `gae:"start_time"`
   217  	// Time when the rerun ends.
   218  	EndTime time.Time `gae:"end_time"`
   219  	// Status of the rerun
   220  	Status pb.RerunStatus
   221  	// Key to the Suspect, if this is for culprit verification
   222  	Suspect *datastore.Key `gae:"suspect"`
   223  	// Key to NthSectionAnalysis, if this is for nthsection
   224  	NthSectionAnalysis *datastore.Key `gae:"nthsection_analysis"`
   225  	// Priority of this run
   226  	Priority int32 `gae:"priority"`
   227  	// The dimensions of the rerun build.
   228  	Dimensions *pb.Dimensions `gae:"dimensions"`
   229  }
   230  
   231  // ActionDetails encapsulate the details of actions performed by LUCI Bisection,
   232  // e.g. creating a revert, commenting on a culprit, etc.
   233  type ActionDetails struct {
   234  	// URL to the code review of the revert.
   235  	RevertURL string `gae:"revert_url"`
   236  
   237  	// Whether LUCI Bisection has created the revert
   238  	IsRevertCreated bool `gae:"is_revert_created"`
   239  
   240  	// Time when the revert was created
   241  	RevertCreateTime time.Time `gae:"revert_create_time"`
   242  
   243  	// Whether LUCI Bisection has committed the revert
   244  	IsRevertCommitted bool `gae:"is_revert_committed"`
   245  
   246  	// Time when the revert for the suspect was bot-committed
   247  	RevertCommitTime time.Time `gae:"revert_commit_time"`
   248  
   249  	// Whether LUCI Bisection has added a supporting comment to an existing revert
   250  	HasSupportRevertComment bool `gae:"has_support_revert_comment"`
   251  
   252  	// Time when LUCI Bisection added a supporting comment to an existing revert
   253  	SupportRevertCommentTime time.Time `gae:"support_revert_comment_time"`
   254  
   255  	// Whether LUCI Bisection has added a comment to the culprit CL
   256  	HasCulpritComment bool `gae:"has_culprit_comment"`
   257  
   258  	// Time when LUCI Bisection commented on the culprit
   259  	CulpritCommentTime time.Time `gae:"culprit_comment_time"`
   260  
   261  	// Optional explanation for when processing the culprit results in no action.
   262  	InactionReason pb.CulpritInactionReason `gae:"inaction_reason"`
   263  
   264  	// HasTakenActions indicates if the all actions for culprit
   265  	// (e.g. comment, revert...) have been taken.
   266  	// This field is only valid if a culprit is confirmed by culprit verification.
   267  	// If there is no action to be taken (e.g. if actions are disabled),
   268  	// then this field is set to true.
   269  	HasTakenActions bool `gae:"has_taken_actions"`
   270  }
   271  
   272  // Suspect is the suspect of heuristic analysis or nthsection.
   273  type Suspect struct {
   274  	Id int64 `gae:"$id"`
   275  
   276  	// Type of the suspect, either heuristic or nthsection
   277  	Type SuspectType `gae:"type"`
   278  
   279  	// Key to the CompileFailureHeuristicAnalysis or CompileFailureNthSectionAnalysis
   280  	// or TestNthSectionAnalysis that results in this suspect
   281  	ParentAnalysis *datastore.Key `gae:"$parent"`
   282  
   283  	// The commit of the suspect
   284  	buildbucketpb.GitilesCommit
   285  
   286  	// The Url where the suspect was reviewed
   287  	ReviewUrl string `gae:"review_url"`
   288  
   289  	// Title of the review for the suspect
   290  	ReviewTitle string `gae:"review_title"`
   291  
   292  	// Score is an integer representing the how confident we believe the suspect
   293  	// is indeed the culprit.
   294  	// A higher score means a stronger signal that the suspect is responsible for
   295  	// a failure.
   296  	// Only applies to Heuristic suspect
   297  	Score int `gae:"score"`
   298  
   299  	// A short, human-readable string that concisely describes a fact about the
   300  	// suspect. e.g. 'add a/b/x.cc'
   301  	// Only applies to Heuristic suspect
   302  	Justification string `gae:"justification,noindex"`
   303  
   304  	// Whether if a suspect has been verified
   305  	VerificationStatus SuspectVerificationStatus `gae:"verification_status"`
   306  
   307  	// Key to the CompileRerunBuild or TestSingleRerun of the suspect, for culprit verification purpose.
   308  	SuspectRerunBuild *datastore.Key `gae:"suspect_rerun_build"`
   309  
   310  	// Key to the CompileRerunBuild or TestSingleRerun of the parent commit of the suspect, for culprit verification purpose.
   311  	ParentRerunBuild *datastore.Key `gae:"parent_rerun_build"`
   312  
   313  	// Details of actions performed by LUCI Bisection for this suspect.
   314  	ActionDetails
   315  
   316  	// Type of the suspect.
   317  	AnalysisType pb.AnalysisType `gae:"analysis_type"`
   318  
   319  	// The time that this suspect was committed.
   320  	// For now, it is only populated for test failure suspects.
   321  	CommitTime time.Time `gae:"commit_time"`
   322  
   323  	// For backward compatibility due to removed fields.
   324  	// See https://source.chromium.org/chromium/infra/infra/+/main:go/src/go.chromium.org/luci/gae/service/datastore/pls.go;l=100
   325  	_ datastore.PropertyMap `gae:"-,extra"`
   326  }
   327  
   328  // CompileHeuristicAnalysis is heuristic analysis for compile failures.
   329  type CompileHeuristicAnalysis struct {
   330  	Id int64 `gae:"$id"`
   331  	// Key to the parent CompileFailureAnalysis
   332  	ParentAnalysis *datastore.Key `gae:"$parent"`
   333  	// Time when the analysis starts to run.
   334  	StartTime time.Time `gae:"start_time"`
   335  	// Time when the analysis ends, or canceled
   336  	EndTime time.Time `gae:"end_time"`
   337  	// Status of the analysis
   338  	Status pb.AnalysisStatus `gae:"status"`
   339  	// Run status of the analysis
   340  	RunStatus pb.AnalysisRunStatus `gae:"run_status"`
   341  }
   342  
   343  // CompileNthSectionAnalysis is nth-section analysis for compile failures.
   344  type CompileNthSectionAnalysis struct {
   345  	Id int64 `gae:"$id"`
   346  	// Key to the parent CompileFailureAnalysis
   347  	ParentAnalysis *datastore.Key `gae:"$parent"`
   348  	// Time when the analysis starts to run.
   349  	StartTime time.Time `gae:"start_time"`
   350  	// Time when the analysis ends, or canceled
   351  	EndTime time.Time `gae:"end_time"`
   352  	// Status of the analysis
   353  	Status pb.AnalysisStatus `gae:"status"`
   354  	// Run status of the analysis
   355  	RunStatus pb.AnalysisRunStatus `gae:"run_status"`
   356  
   357  	// When storing protobuf message, datastore will compress the data if it is big
   358  	// https://source.corp.google.com/chops_infra_internal/infra/go/src/go.chromium.org/luci/gae/service/datastore/protos.go;l=88
   359  	// We can also declare zstd compression here, but there seems to be a bug where
   360  	// the message size is 0
   361  	BlameList *pb.BlameList `gae:"blame_list"`
   362  
   363  	// Suspect is the result of nthsection analysis.
   364  	// Note: We call it "suspect" because it has not been verified (by culprit verification component)
   365  	Suspect *datastore.Key `gae:"suspect"`
   366  }
   367  
   368  // TestFailure represents a failure on a test variant.
   369  type TestFailure struct {
   370  	ID int64 `gae:"$id"`
   371  	// The LUCI project of this test variant.
   372  	Project string `gae:"project"`
   373  	// Test ID of the test variant.
   374  	TestID string `gae:"test_id"`
   375  	// Variant hash of the test variant.
   376  	VariantHash string `gae:"variant_hash"`
   377  	// The variant of the test.
   378  	Variant *pb.Variant `gae:"variant"`
   379  	// The name of the test (used in recipe). Note that it is different
   380  	// from TestID.
   381  	TestName string `gae:"test_name"`
   382  	// The name of the test suite that this test variant belongs to.
   383  	// For chromium, this information can be derived from Variant field.
   384  	TestSuiteName string `gae:"test_suite_name"`
   385  	// Hash of the ref to identify the branch in the source control.
   386  	RefHash string `gae:"ref_hash"`
   387  	// The LUCI bucket for the builder of this test failure.
   388  	Bucket string `gae:"bucket"`
   389  	// The name for the builder of this test failure.
   390  	Builder string `gae:"builder"`
   391  	// The branch where this failure happens.
   392  	Ref *pb.SourceRef `gae:"ref"`
   393  	// Start commit position of the regression range exclusive.
   394  	RegressionStartPosition int64 `gae:"regression_start_position"`
   395  	// End commit position of the regression range inclusive.
   396  	RegressionEndPosition int64 `gae:"regression_end_position"`
   397  	// Expected failure rate at regression_start_position, between 0 and 1 inclusive.
   398  	StartPositionFailureRate float64 `gae:"start_position_failure_rate"`
   399  	// Expected failure rate at regression_end_position, between 0 and 1 inclusive.
   400  	EndPositionFailureRate float64 `gae:"end_position_failure_rate"`
   401  	// When run multiple test variants in a bisection build, the bisection path
   402  	// follows the test variant of the primary test failure.
   403  	IsPrimary bool
   404  	// IsDiverged is true when the bisection path of this test failure diverges from
   405  	// the primary test failure. This suggests that this test failure has a different root cause.
   406  	// We will not attempt to re-bisect this test failure.
   407  	IsDiverged bool `gae:"is_diverged"`
   408  	// Key to the TestFailureAnalysis that analyses this TestFailure.
   409  	AnalysisKey *datastore.Key `gae:"analysis_key"`
   410  	// RedundancyScore of the test failure, between 0 and 1, larger score means more redundant.
   411  	// Only set for primary test failure.
   412  	RedundancyScore float64 `gae:"redundancy_score"`
   413  	// The time when the failure starts, truncated into hours.
   414  	StartHour time.Time `gae:"start_hour"`
   415  	// The time when we get last got the failure result, truncated into hours.
   416  	EndHour time.Time `gae:"end_hour"`
   417  }
   418  
   419  // TestFailureAnalysis is the analysis for test failure.
   420  // This stores information that is needed during the analysis, and also
   421  // some metadata for the analysis.
   422  type TestFailureAnalysis struct {
   423  	ID int64 `gae:"$id"`
   424  	// The LUCI project of the test variants this analysis analyses.
   425  	Project string `gae:"project"`
   426  	// The LUCI bucket for the builder that this analysis analyses.
   427  	Bucket string `gae:"bucket"`
   428  	// The name for the builder that this analysis analyses.
   429  	Builder string `gae:"builder"`
   430  	// Key to the primary TestFailure entity that this analysis analyses.
   431  	TestFailure *datastore.Key `gae:"test_failure"`
   432  	// Time when the entity is first created.
   433  	CreateTime time.Time `gae:"create_time"`
   434  	// Time when the analysis starts to run.
   435  	StartTime time.Time `gae:"start_time"`
   436  	// Time when the analysis ends, or canceled.
   437  	EndTime time.Time `gae:"end_time"`
   438  	// Status of the analysis
   439  	Status pb.AnalysisStatus `gae:"status"`
   440  	// Run status of the analysis
   441  	RunStatus pb.AnalysisRunStatus `gae:"run_status"`
   442  	// Key to the suspect that was verified by Culprit verification
   443  	VerifiedCulpritKey *datastore.Key `gae:"verified_culprit_key"`
   444  	// Priority of this run.
   445  	Priority int32 `gae:"priority"`
   446  	// The start commit hash (exclusive) of the regression range that this analysis analyses.
   447  	// It corresponds to the RegressionStartPosition.
   448  	StartCommitHash string `gae:"start_commit_hash"`
   449  	// The end commit hash (inclusive) of the regression range that this analysis analyses.
   450  	// It corresponds to the RegressionEndPosition.
   451  	EndCommitHash string `gae:"end_commit_hash"`
   452  	// An example Buildbucket ID in which the test failed.
   453  	FailedBuildID int64 `gae:"failed_build_id"`
   454  	// The sheriff rotations that watch the builder.
   455  	SheriffRotations []string `gae:"sheriff_rotations"`
   456  }
   457  
   458  // TestNthSectionAnalysis is nth-section analysis for test failures.
   459  type TestNthSectionAnalysis struct {
   460  	ID int64 `gae:"$id"`
   461  	// Key to the parent TestFailureAnalysis.
   462  	ParentAnalysisKey *datastore.Key `gae:"parent_analysis_key"`
   463  	// Time when the analysis starts to run.
   464  	StartTime time.Time `gae:"start_time"`
   465  	// Time when the analysis ends, or canceled.
   466  	EndTime time.Time `gae:"end_time"`
   467  	// Status of the analysis.
   468  	Status pb.AnalysisStatus `gae:"status"`
   469  	// Run status of the analysis.
   470  	RunStatus pb.AnalysisRunStatus `gae:"run_status"`
   471  
   472  	// When storing protobuf message, datastore will compress the data if it is big
   473  	// https://source.corp.google.com/chops_infra_internal/infra/go/src/go.chromium.org/luci/gae/service/datastore/protos.go;l=88
   474  	BlameList *pb.BlameList `gae:"blame_list"`
   475  
   476  	// Culprit is the result of nthsection analysis.
   477  	// Nthsection analysis follows the path of the primary test failure,
   478  	// so the culprit here is the culprit of the primary test failure.
   479  	CulpritKey *datastore.Key `gae:"culprit"`
   480  }
   481  
   482  // TestSingleRerun represents one rerun for test failures
   483  // at a particular commit.
   484  // A TestSingleRerun corresponds to one buildbucket run, and may
   485  // run multiple test failures at the same time.
   486  // A TestSingleRerun may be for nth-section or for culprit verification.
   487  type TestSingleRerun struct {
   488  	// The buildbucket ID of the rerun.
   489  	ID int64 `gae:"$id"`
   490  	// LUCI build data for the rerun build.
   491  	LUCIBuild `gae:"luci_build"`
   492  	// Type for the rerun build
   493  	// Either culprit verification or nth section run.
   494  	Type RerunBuildType `gae:"rerun_type"`
   495  	// Key to the TestFailureAnalysis of this SingleRerun
   496  	AnalysisKey *datastore.Key `gae:"analysis_key"`
   497  	// Time when the rerun send the result to bisection from recipe.
   498  	ReportTime time.Time `gae:"report_time"`
   499  	// The dimensions of the rerun build.
   500  	Dimensions *pb.Dimensions `gae:"dimensions"`
   501  	// Key to the culprit (Suspect model), if this is for culprit verification
   502  	CulpritKey *datastore.Key `gae:"culprit_key"`
   503  	// Key to TestNthSectionAnalysis, if this is for nthsection.
   504  	NthSectionAnalysisKey *datastore.Key `gae:"nthsection_analysis_key"`
   505  	// Priority of this run.
   506  	Priority int32 `gae:"priority"`
   507  	// Results of the test runs.
   508  	// The TestFailureKey field of test result will be populated when this model
   509  	// is first created.
   510  	// This is useful to know which test failures are running for this rerun without
   511  	// waiting for the result.
   512  	TestResults RerunTestResults `gae:"test_results"`
   513  	// Status of the rerun.
   514  	// If the rerun ended, this result will base on the result
   515  	// of the primary test failure. See pb.RerunStatus for more information.
   516  	Status pb.RerunStatus `gae:"status"`
   517  }
   518  
   519  // RerunTestResults captures test results of TestSingleRerun.
   520  type RerunTestResults struct {
   521  	// IsFinalized indicates whether the results have been finalized and
   522  	// is ready to be consumed.
   523  	IsFinalized bool                    `gae:"is_finalized"`
   524  	Results     []RerunSingleTestResult `gae:"results"`
   525  }
   526  
   527  // RerunSingleTestResult is the result for one TestFailure.
   528  type RerunSingleTestResult struct {
   529  	// Key to TestFailure model.
   530  	TestFailureKey *datastore.Key `gae:"test_failure_key"`
   531  
   532  	// TODO (nqmtuan): Consider breaking this to status level (e.g.
   533  	// unexpected pass count). But for now, keeping the total expected
   534  	// and unexpected count may be enough, as we only support bisection
   535  	// from expected to unexpected.
   536  	// We use number count instead of status to open for possibility
   537  	// to support flakiness bisection in the future (where a test may need
   538  	// to be rerun multiple times to get the flakiness level).
   539  	// The number of expected results. Skipped results are not counted.
   540  	ExpectedCount int64 `gae:"expected_count"`
   541  	// The number of unexpected results. Skipped results are not counted.
   542  	UnexpectedCount int64 `gae:"unexpected_count"`
   543  }
   544  
   545  func (cfa *CompileFailureAnalysis) HasEnded() bool {
   546  	return cfa.RunStatus == pb.AnalysisRunStatus_ENDED || cfa.RunStatus == pb.AnalysisRunStatus_CANCELED
   547  }
   548  
   549  func (tfa *TestFailureAnalysis) HasEnded() bool {
   550  	return tfa.RunStatus == pb.AnalysisRunStatus_ENDED || tfa.RunStatus == pb.AnalysisRunStatus_CANCELED
   551  }
   552  
   553  func (tfa *TestFailureAnalysis) HasStarted() bool {
   554  	return tfa.RunStatus == pb.AnalysisRunStatus_ENDED || tfa.RunStatus == pb.AnalysisRunStatus_CANCELED || tfa.RunStatus == pb.AnalysisRunStatus_STARTED
   555  }
   556  
   557  func (ha *CompileHeuristicAnalysis) HasEnded() bool {
   558  	return ha.RunStatus == pb.AnalysisRunStatus_ENDED || ha.RunStatus == pb.AnalysisRunStatus_CANCELED
   559  }
   560  
   561  func (nsa *CompileNthSectionAnalysis) HasEnded() bool {
   562  	return nsa.RunStatus == pb.AnalysisRunStatus_ENDED || nsa.RunStatus == pb.AnalysisRunStatus_CANCELED
   563  }
   564  
   565  func (rerun *SingleRerun) HasEnded() bool {
   566  	return rerun.Status == pb.RerunStatus_RERUN_STATUS_FAILED || rerun.Status == pb.RerunStatus_RERUN_STATUS_PASSED || rerun.Status == pb.RerunStatus_RERUN_STATUS_INFRA_FAILED || rerun.Status == pb.RerunStatus_RERUN_STATUS_CANCELED
   567  }
   568  
   569  func (rerun *TestSingleRerun) HasEnded() bool {
   570  	return rerun.Status == pb.RerunStatus_RERUN_STATUS_FAILED || rerun.Status == pb.RerunStatus_RERUN_STATUS_PASSED || rerun.Status == pb.RerunStatus_RERUN_STATUS_INFRA_FAILED || rerun.Status == pb.RerunStatus_RERUN_STATUS_CANCELED || rerun.Status == pb.RerunStatus_RERUN_STATUS_TEST_SKIPPED
   571  }
   572  
   573  func (rerun *TestSingleRerun) HasStarted() bool {
   574  	return rerun.LUCIBuild.Status != buildbucketpb.Status_STATUS_UNSPECIFIED && rerun.LUCIBuild.Status != buildbucketpb.Status_SCHEDULED
   575  }
   576  
   577  func (nsa *TestNthSectionAnalysis) HasEnded() bool {
   578  	return nsa.RunStatus == pb.AnalysisRunStatus_ENDED || nsa.RunStatus == pb.AnalysisRunStatus_CANCELED
   579  }
   580  
   581  // TestFailureBundle contains TestFailure models that will be bisected together.
   582  type TestFailureBundle struct {
   583  	// Primary test failure.
   584  	primaryFailure *TestFailure
   585  	// Non-primary test failures.
   586  	otherTestFailures []*TestFailure
   587  	// Contains metadata that is common for this bundle that
   588  	// is not suitable to put in TestFailure model.
   589  	Metadata *BundleMetaData
   590  }
   591  
   592  type BundleMetaData struct {
   593  	SheriffRotations []string
   594  }
   595  
   596  func (tfb *TestFailureBundle) Add(testFailures []*TestFailure) error {
   597  	for _, tf := range testFailures {
   598  		if tf.IsPrimary {
   599  			if tfb.primaryFailure != nil {
   600  				return errors.New("added more than 1 primary test failure in bundle")
   601  			}
   602  			tfb.primaryFailure = tf
   603  		} else {
   604  			tfb.otherTestFailures = append(tfb.otherTestFailures, tf)
   605  		}
   606  	}
   607  	return nil
   608  }
   609  
   610  // Primary returns the primary test failure for the bundle.
   611  func (tfb *TestFailureBundle) Primary() *TestFailure {
   612  	return tfb.primaryFailure
   613  }
   614  
   615  // Others returns other test failures for the bundle.
   616  func (tfb *TestFailureBundle) Others() []*TestFailure {
   617  	result := make([]*TestFailure, len(tfb.otherTestFailures))
   618  	// Copy here to prevent callers from adding things to the slice.
   619  	copy(result, tfb.otherTestFailures)
   620  	return result
   621  }
   622  
   623  // All return all test failures for the bundle.
   624  func (tfb *TestFailureBundle) All() []*TestFailure {
   625  	if tfb.primaryFailure == nil {
   626  		return tfb.Others()
   627  	}
   628  	return append(tfb.otherTestFailures, tfb.primaryFailure)
   629  }
   630  
   631  // NonDiverged returns all non-diverged test failures for the bundle.
   632  func (tfb *TestFailureBundle) NonDiverged() []*TestFailure {
   633  	result := []*TestFailure{}
   634  	if tfb.primaryFailure != nil {
   635  		result = append(result, tfb.primaryFailure)
   636  	}
   637  	for _, other := range tfb.otherTestFailures {
   638  		if !other.IsDiverged {
   639  			result = append(result, other)
   640  		}
   641  	}
   642  	return result
   643  }
   644  
   645  func SuspectStatus(rerunStatus pb.RerunStatus, parentRerunStatus pb.RerunStatus) SuspectVerificationStatus {
   646  	if rerunStatus == pb.RerunStatus_RERUN_STATUS_FAILED && parentRerunStatus == pb.RerunStatus_RERUN_STATUS_PASSED {
   647  		return SuspectVerificationStatus_ConfirmedCulprit
   648  	}
   649  	if rerunStatus == pb.RerunStatus_RERUN_STATUS_PASSED || parentRerunStatus == pb.RerunStatus_RERUN_STATUS_FAILED {
   650  		return SuspectVerificationStatus_Vindicated
   651  	}
   652  	if rerunStatus == pb.RerunStatus_RERUN_STATUS_INFRA_FAILED || parentRerunStatus == pb.RerunStatus_RERUN_STATUS_INFRA_FAILED {
   653  		return SuspectVerificationStatus_VerificationError
   654  	}
   655  	if rerunStatus == pb.RerunStatus_RERUN_STATUS_UNSPECIFIED || parentRerunStatus == pb.RerunStatus_RERUN_STATUS_UNSPECIFIED {
   656  		return SuspectVerificationStatus_Unverified
   657  	}
   658  	if rerunStatus == pb.RerunStatus_RERUN_STATUS_TEST_SKIPPED || parentRerunStatus == pb.RerunStatus_RERUN_STATUS_TEST_SKIPPED {
   659  		return SuspectVerificationStatus_VerificationError
   660  	}
   661  	return SuspectVerificationStatus_UnderVerification
   662  }
   663  
   664  func PlatformFromOS(ctx context.Context, os string) Platform {
   665  	val := strings.ToLower(os)
   666  	if strings.Contains(val, "linux") || strings.Contains(val, "ubuntu") {
   667  		return PlatformLinux
   668  	}
   669  	if strings.Contains(val, "win") {
   670  		return PlatformWindows
   671  	}
   672  	if strings.Contains(val, "mac") {
   673  		return PlatformMac
   674  	}
   675  	logging.Warningf(ctx, "Unknown OS platform: %s", val)
   676  	return PlatformUnknown
   677  }