go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/duplicate_run.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 changepoints 16 17 import ( 18 "context" 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/ingestion/control" 24 controlpb "go.chromium.org/luci/analysis/internal/ingestion/control/proto" 25 "go.chromium.org/luci/analysis/internal/ingestion/resultdb" 26 ) 27 28 // readDuplicateInvocations contructs an duplicate map for test variants. 29 // It returns a map of (invocationID, bool). If an invocation ID is found in 30 // the map keys, then it is from a duplicate run. If not, then the run is not 31 // duplicate. 32 // It also returns a slice of invocation IDs that are not in Invocations 33 // table in Spanner. This is used to insert new Invocations row to Spanner. 34 // Note: This function should be called with a transactional context. 35 func readDuplicateInvocations(ctx context.Context, tvs []*rdbpb.TestVariant, buildResult *controlpb.BuildResult) (map[string]bool, []string, error) { 36 invIDs, err := invocationIDsFromTestVariants(tvs) 37 if err != nil { 38 return nil, nil, errors.Annotate(err, "invocation ids from test variant").Err() 39 } 40 invMap, err := readInvocations(ctx, buildResult.Project, invIDs) 41 if err != nil { 42 return nil, nil, errors.Annotate(err, "read invocations").Err() 43 } 44 dupMap := map[string]bool{} 45 buildInvID := control.BuildInvocationID(buildResult.Id) 46 for invID, ingestedInvID := range invMap { 47 // If the ingested invocation ID stored in Spanner is different from the 48 // current invocation ID, it means this is a duplicate run. 49 if ingestedInvID != buildInvID { 50 dupMap[invID] = true 51 } 52 } 53 54 newInvIDs := []string{} 55 for _, invID := range invIDs { 56 if _, ok := invMap[invID]; !ok { 57 newInvIDs = append(newInvIDs, invID) 58 } 59 } 60 return dupMap, newInvIDs, nil 61 } 62 63 // invocationIDsFromTestVariants gets all runs' invocation IDs for given test 64 // variants. 65 // This is used for a batch query to spanner to check for duplicate runs. 66 func invocationIDsFromTestVariants(tvs []*rdbpb.TestVariant) ([]string, error) { 67 m := map[string]bool{} 68 for _, tv := range tvs { 69 for _, tr := range tv.Results { 70 invocationName, err := resultdb.InvocationFromTestResultName(tr.Result.Name) 71 if err != nil { 72 return nil, err 73 } 74 m[invocationName] = true 75 } 76 } 77 78 // Return the keys from m. 79 result := make([]string, len(m)) 80 i := 0 81 for invocationName := range m { 82 result[i] = invocationName 83 i++ 84 } 85 return result, nil 86 }