go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/testverdicts/read_client.go (about) 1 // Copyright 2024 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 testverdicts 16 17 import ( 18 "context" 19 "time" 20 21 "cloud.google.com/go/bigquery" 22 "google.golang.org/api/iterator" 23 24 "go.chromium.org/luci/common/errors" 25 26 "go.chromium.org/luci/analysis/internal/bqutil" 27 ) 28 29 // NewReadClient creates a new client for reading test verdicts. 30 func NewReadClient(ctx context.Context, gcpProject string) (*ReadClient, error) { 31 client, err := bqutil.Client(ctx, gcpProject) 32 if err != nil { 33 return nil, err 34 } 35 return &ReadClient{client: client}, nil 36 } 37 38 // ReadClient represents a client to read test verdicts from BigQuery. 39 type ReadClient struct { 40 client *bigquery.Client 41 } 42 43 // Close releases any resources held by the client. 44 func (c *ReadClient) Close() error { 45 return c.client.Close() 46 } 47 48 type ReadTestVerdictsPerSourcePositionOptions struct { 49 Project string 50 TestID string 51 VariantHash string 52 RefHash string 53 // Only test verdicts with allowed invocation realms can be returned. 54 AllowedRealms []string 55 // All returned commits has source position greater than PositionMustGreater. 56 PositionMustGreater int64 57 // The maximum number of commits to return. 58 NumCommits int64 59 } 60 61 // CommitWithVerdicts represents a commit with test verdicts. 62 type CommitWithVerdicts struct { 63 // Source position of this commit. 64 Position int64 65 // Commit hash of this commit. 66 CommitHash string 67 // Represent a branch in the source control. 68 Ref *Ref 69 // Returns at most 20 test verdicts at this commit. 70 TestVerdicts []*TestVerdict 71 } 72 type TestVerdict struct { 73 TestID string 74 VariantHash string 75 RefHash string 76 InvocationID string 77 Status string 78 PartitionTime time.Time 79 PassedAvgDurationUsec bigquery.NullFloat64 80 Changelists []*Changelist 81 // Whether the caller has access to this test verdict. 82 HasAccess bool 83 } 84 85 type Ref struct { 86 Gitiles *Gitiles 87 } 88 type Gitiles struct { 89 Host bigquery.NullString 90 Project bigquery.NullString 91 Ref bigquery.NullString 92 } 93 94 type Changelist struct { 95 Host bigquery.NullString 96 Change bigquery.NullInt64 97 Patchset bigquery.NullInt64 98 OwnerKind bigquery.NullString 99 } 100 101 // ReadTestVerdictsPerSourcePosition returns commits with test verdicts in source position ascending order. 102 func (c *ReadClient) ReadTestVerdictsPerSourcePosition(ctx context.Context, options ReadTestVerdictsPerSourcePositionOptions) ([]*CommitWithVerdicts, error) { 103 query := ` 104 SELECT 105 sources.gitiles_commit.position as Position, 106 ANY_VALUE(sources.gitiles_commit.commit_hash) as CommitHash, 107 ANY_VALUE(source_ref) as Ref, 108 ARRAY_AGG(STRUCT(test_id as TestID , 109 variant_hash as VariantHash, 110 source_ref_hash as RefHash, 111 invocation.id as InvocationID, 112 status as Status, 113 (SELECT AVG(IF(r.status = "PASS",r.duration , NULL)) FROM UNNEST(results) as r) as PassedAvgDurationUsec, 114 (SELECT ARRAY_AGG(STRUCT(host as Host, change as Change, patchset as Patchset, owner_kind as OwnerKind)) FROM UNNEST(sources.changelists)) as Changelists, 115 invocation.realm IN UNNEST(@allowedRealms) as HasAccess, 116 partition_time as PartitionTime) ORDER BY partition_time DESC LIMIT 20) as TestVerdicts 117 FROM internal.test_verdicts 118 WHERE project = @project 119 AND test_id = @testID 120 AND variant_hash = @variantHash 121 AND source_ref_hash = @refHash 122 AND sources.gitiles_commit.position > @positionMustGreater 123 GROUP BY sources.gitiles_commit.position 124 ORDER BY sources.gitiles_commit.position 125 LIMIT @limit 126 ` 127 q := c.client.Query(query) 128 q.DefaultDatasetID = "internal" 129 q.Parameters = []bigquery.QueryParameter{ 130 {Name: "project", Value: options.Project}, 131 {Name: "testID", Value: options.TestID}, 132 {Name: "variantHash", Value: options.VariantHash}, 133 {Name: "refHash", Value: options.RefHash}, 134 {Name: "positionMustGreater", Value: options.PositionMustGreater}, 135 {Name: "limit", Value: options.NumCommits}, 136 {Name: "allowedRealms", Value: options.AllowedRealms}, 137 } 138 job, err := q.Run(ctx) 139 if err != nil { 140 return nil, errors.Annotate(err, "running query").Err() 141 } 142 it, err := job.Read(ctx) 143 if err != nil { 144 return nil, errors.Annotate(err, "read").Err() 145 } 146 results := []*CommitWithVerdicts{} 147 for { 148 row := &CommitWithVerdicts{} 149 err := it.Next(row) 150 if errors.Is(err, iterator.Done) { 151 break 152 } 153 if err != nil { 154 return nil, errors.Annotate(err, "obtain next commit with test verdicts row").Err() 155 } 156 results = append(results, row) 157 } 158 return results, nil 159 }