go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/testvariantbranch/test_variant_branch_conversion.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 testvariantbranch 16 17 import ( 18 "sort" 19 20 "go.chromium.org/luci/common/errors" 21 rdbpb "go.chromium.org/luci/resultdb/proto/v1" 22 23 "go.chromium.org/luci/analysis/internal/changepoints/inputbuffer" 24 "go.chromium.org/luci/analysis/internal/changepoints/sources" 25 "go.chromium.org/luci/analysis/internal/ingestion/resultdb" 26 "go.chromium.org/luci/analysis/internal/tasks/taskspb" 27 pb "go.chromium.org/luci/analysis/proto/v1" 28 ) 29 30 func ToPositionVerdict(tv *rdbpb.TestVariant, payload *taskspb.IngestTestResults, duplicateMap map[string]bool, src *pb.Sources) (inputbuffer.PositionVerdict, error) { 31 isSimpleExpectedPassed := (tv.Status == rdbpb.TestVariantStatus_EXPECTED && len(tv.Results) == 1 && tv.Results[0].Result.Status == rdbpb.TestStatus_PASS) 32 33 verdict := inputbuffer.PositionVerdict{ 34 CommitPosition: sources.CommitPosition(src), 35 IsSimpleExpectedPass: isSimpleExpectedPassed, 36 Hour: payload.PartitionTime.AsTime(), 37 } 38 39 // Add verdict details only if verdict is not simple expected passed. 40 if !isSimpleExpectedPassed { 41 vd, err := toVerdictDetails(tv, duplicateMap) 42 if err != nil { 43 return inputbuffer.PositionVerdict{}, errors.Annotate(err, "to verdict details").Err() 44 } 45 verdict.Details = vd 46 } 47 return verdict, nil 48 } 49 50 // toVerdictDetails converts a test variant to verdict details. 51 // The runs in verdict details are ordered by: 52 // - IsDuplicate, in which non-duplicate runs come first, then 53 // - UnexpectedCount, descendingly, then 54 // - ExpectedCount, descendingly. 55 func toVerdictDetails(tv *rdbpb.TestVariant, duplicateMap map[string]bool) (inputbuffer.VerdictDetails, error) { 56 isExonerated := (tv.Status == rdbpb.TestVariantStatus_EXONERATED) 57 vd := inputbuffer.VerdictDetails{ 58 IsExonerated: isExonerated, 59 } 60 // runData maps invocation name to run data. 61 runData := map[string]*inputbuffer.Run{} 62 for _, r := range tv.Results { 63 tr := r.GetResult() 64 invocationName, err := resultdb.InvocationFromTestResultName(tr.Name) 65 if err != nil { 66 return vd, errors.Annotate(err, "invocation from test result name").Err() 67 } 68 if _, ok := runData[invocationName]; !ok { 69 _, isDuplicate := duplicateMap[invocationName] 70 runData[invocationName] = &inputbuffer.Run{ 71 IsDuplicate: isDuplicate, 72 } 73 } 74 if tr.Expected { 75 if tr.Status == rdbpb.TestStatus_PASS { 76 runData[invocationName].Expected.PassCount++ 77 } 78 if tr.Status == rdbpb.TestStatus_FAIL { 79 runData[invocationName].Expected.FailCount++ 80 } 81 if tr.Status == rdbpb.TestStatus_CRASH { 82 runData[invocationName].Expected.CrashCount++ 83 } 84 if tr.Status == rdbpb.TestStatus_ABORT { 85 runData[invocationName].Expected.AbortCount++ 86 } 87 } else { 88 if tr.Status == rdbpb.TestStatus_PASS { 89 runData[invocationName].Unexpected.PassCount++ 90 } 91 if tr.Status == rdbpb.TestStatus_FAIL { 92 runData[invocationName].Unexpected.FailCount++ 93 } 94 if tr.Status == rdbpb.TestStatus_CRASH { 95 runData[invocationName].Unexpected.CrashCount++ 96 } 97 if tr.Status == rdbpb.TestStatus_ABORT { 98 runData[invocationName].Unexpected.AbortCount++ 99 } 100 } 101 } 102 103 vd.Runs = make([]inputbuffer.Run, len(runData)) 104 i := 0 105 for _, run := range runData { 106 vd.Runs[i] = *run 107 i++ 108 } 109 // Sort the run to make a fixed order. 110 // Sort by duplicate (non-duplicate first), then by unexpected count (desc), 111 // then by expected count. 112 sort.Slice(vd.Runs, func(i, j int) bool { 113 if !vd.Runs[i].IsDuplicate && vd.Runs[j].IsDuplicate { 114 return true 115 } 116 if vd.Runs[i].IsDuplicate && !vd.Runs[j].IsDuplicate { 117 return false 118 } 119 if vd.Runs[i].Unexpected.Count() < vd.Runs[j].Unexpected.Count() { 120 return false 121 } 122 if vd.Runs[i].Unexpected.Count() > vd.Runs[j].Unexpected.Count() { 123 return true 124 } 125 return vd.Runs[i].Expected.Count() > vd.Runs[j].Expected.Count() 126 }) 127 return vd, nil 128 }