go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/clustering/ingestion/resultdb.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 ingestion 16 17 import ( 18 "fmt" 19 20 "google.golang.org/protobuf/proto" 21 "google.golang.org/protobuf/types/known/timestamppb" 22 23 rdbpb "go.chromium.org/luci/resultdb/proto/v1" 24 25 cpb "go.chromium.org/luci/analysis/internal/clustering/proto" 26 "go.chromium.org/luci/analysis/internal/ingestion/resultdb" 27 "go.chromium.org/luci/analysis/pbutil" 28 pb "go.chromium.org/luci/analysis/proto/v1" 29 ) 30 31 func failuresFromTestVariant(opts Options, tv TestVerdict) []*cpb.Failure { 32 var failures []*cpb.Failure 33 if tv.Verdict.Status == rdbpb.TestVariantStatus_EXPECTED { 34 // Short circuit: There will be nothing in the test variant to 35 // ingest, as everything is expected. 36 return nil 37 } 38 39 // Whether there were any (non-skip) passed or expected results. 40 var hasPass bool 41 for _, tr := range tv.Verdict.Results { 42 if tr.Result.Status != rdbpb.TestStatus_SKIP && 43 (tr.Result.Status == rdbpb.TestStatus_PASS || 44 tr.Result.Expected) { 45 hasPass = true 46 } 47 } 48 49 // Group test results by run and sort in order of start time. 50 resultsByRun := resultdb.GroupAndOrderTestResults(tv.Verdict.Results) 51 52 resultIndex := 0 53 for _, run := range resultsByRun { 54 // Whether there were any passed or expected results in the run. 55 var testRunHasPass bool 56 for _, tr := range run { 57 if tr.Result.Status != rdbpb.TestStatus_SKIP && 58 (tr.Result.Status == rdbpb.TestStatus_PASS || 59 tr.Result.Expected) { 60 testRunHasPass = true 61 } 62 } 63 64 for i, tr := range run { 65 if tr.Result.Expected || !isFailure(tr.Result.Status) { 66 // Only unexpected failures are ingested for clustering. 67 resultIndex++ 68 continue 69 } 70 71 failure := failureFromResult(tr.Result, tv, opts) 72 failure.IngestedInvocationResultIndex = int64(resultIndex) 73 failure.IngestedInvocationResultCount = int64(len(tv.Verdict.Results)) 74 failure.IsIngestedInvocationBlocked = !hasPass 75 failure.TestRunResultIndex = int64(i) 76 failure.TestRunResultCount = int64(len(run)) 77 failure.IsTestRunBlocked = !testRunHasPass 78 failures = append(failures, failure) 79 80 resultIndex++ 81 } 82 } 83 return failures 84 } 85 86 func isFailure(s rdbpb.TestStatus) bool { 87 return (s == rdbpb.TestStatus_ABORT || 88 s == rdbpb.TestStatus_CRASH || 89 s == rdbpb.TestStatus_FAIL) 90 } 91 92 func failureFromResult(tr *rdbpb.TestResult, tv TestVerdict, opts Options) *cpb.Failure { 93 exonerations := make([]*cpb.TestExoneration, 0, len(tv.Verdict.Exonerations)) 94 for _, e := range tv.Verdict.Exonerations { 95 exonerations = append(exonerations, exonerationFromResultDB(e)) 96 } 97 98 var presubmitRun *cpb.PresubmitRun 99 var buildCritical *bool 100 if opts.PresubmitRun != nil { 101 presubmitRun = &cpb.PresubmitRun{ 102 PresubmitRunId: opts.PresubmitRun.ID, 103 Owner: opts.PresubmitRun.Owner, 104 Mode: opts.PresubmitRun.Mode, 105 Status: opts.PresubmitRun.Status, 106 } 107 buildCritical = &opts.BuildCritical 108 } 109 110 testRunID, err := resultdb.InvocationFromTestResultName(tr.Name) 111 if err != nil { 112 // Should never happen, as the result name from ResultDB 113 // should be valid. 114 panic(err) 115 } 116 117 result := &cpb.Failure{ 118 TestResultId: pbutil.TestResultIDFromResultDB(tr.Name), 119 PartitionTime: timestamppb.New(opts.PartitionTime), 120 ChunkIndex: 0, // To be populated by chunking. 121 Realm: opts.Realm, 122 TestId: tv.Verdict.TestId, // Get from variant, as it is not populated on each result. 123 Variant: pbutil.VariantFromResultDB(tv.Verdict.Variant), // Get from variant, as it is not populated on each result. 124 Tags: pbutil.StringPairFromResultDB(tr.Tags), 125 VariantHash: tv.Verdict.VariantHash, // Get from variant, as it is not populated on each result. 126 FailureReason: pbutil.FailureReasonFromResultDB(tr.FailureReason), 127 BugTrackingComponent: extractBugTrackingComponent(tr.Tags, tv.Verdict.TestMetadata, opts.PreferBuganizerComponents), 128 StartTime: tr.StartTime, 129 Duration: tr.Duration, 130 Exonerations: exonerations, 131 PresubmitRun: presubmitRun, 132 BuildStatus: opts.BuildStatus, 133 BuildCritical: buildCritical, 134 IngestedInvocationId: opts.InvocationID, 135 IngestedInvocationResultIndex: -1, // To be populated by caller. 136 IngestedInvocationResultCount: -1, // To be populated by caller. 137 IsIngestedInvocationBlocked: false, // To be populated by caller. 138 TestRunId: testRunID, 139 TestRunResultIndex: -1, // To be populated by caller. 140 TestRunResultCount: -1, // To be populated by caller. 141 IsTestRunBlocked: false, // To be populated by caller. 142 Sources: tv.Sources, 143 BuildGardenerRotations: opts.BuildGardenerRotations, 144 TestVariantBranch: tv.TestVariantBranch, 145 } 146 147 // Copy the result to avoid the result aliasing any of the protos used as input. 148 return proto.Clone(result).(*cpb.Failure) 149 } 150 151 func exonerationFromResultDB(e *rdbpb.TestExoneration) *cpb.TestExoneration { 152 return &cpb.TestExoneration{ 153 Reason: pbutil.ExonerationReasonFromResultDB(e.Reason), 154 } 155 } 156 157 func extractBugTrackingComponent(tags []*rdbpb.StringPair, testMetadata *rdbpb.TestMetadata, preferBuganizerComponents bool) *pb.BugTrackingComponent { 158 if testMetadata != nil && testMetadata.BugComponent != nil { 159 bc := testMetadata.BugComponent 160 switch bcSystem := bc.System.(type) { 161 case *rdbpb.BugComponent_IssueTracker: 162 return &pb.BugTrackingComponent{ 163 System: "buganizer", 164 Component: fmt.Sprint(bcSystem.IssueTracker.ComponentId), 165 } 166 case *rdbpb.BugComponent_Monorail: 167 return &pb.BugTrackingComponent{ 168 System: "monorail", 169 Component: bcSystem.Monorail.Value, 170 } 171 } 172 } else { 173 var buganizerComponent string 174 for _, tag := range tags { 175 if tag.Key == "public_buganizer_component" { 176 buganizerComponent = tag.Value 177 break 178 } 179 } 180 var monorailComponent string 181 for _, tag := range tags { 182 if tag.Key == "monorail_component" { 183 monorailComponent = tag.Value 184 break 185 } 186 } 187 188 if preferBuganizerComponents && buganizerComponent != "" { 189 return &pb.BugTrackingComponent{ 190 System: "buganizer", 191 Component: buganizerComponent, 192 } 193 } 194 195 if monorailComponent != "" { 196 return &pb.BugTrackingComponent{ 197 System: "monorail", 198 Component: monorailComponent, 199 } 200 } 201 202 if buganizerComponent != "" { 203 return &pb.BugTrackingComponent{ 204 System: "buganizer", 205 Component: buganizerComponent, 206 } 207 } 208 } 209 210 return nil 211 }