go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/buildbucket/instrumented_test.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 buildbucket 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 "google.golang.org/grpc" 24 "google.golang.org/grpc/codes" 25 "google.golang.org/grpc/status" 26 27 bbpb "go.chromium.org/luci/buildbucket/proto" 28 "go.chromium.org/luci/common/clock" 29 "go.chromium.org/luci/common/clock/testclock" 30 "go.chromium.org/luci/common/logging" 31 "go.chromium.org/luci/common/logging/gologger" 32 "go.chromium.org/luci/common/tsmon" 33 "go.chromium.org/luci/common/tsmon/distribution" 34 "go.chromium.org/luci/common/tsmon/store" 35 "go.chromium.org/luci/common/tsmon/target" 36 "go.chromium.org/luci/common/tsmon/types" 37 "go.chromium.org/luci/gae/impl/memory" 38 "go.chromium.org/luci/gae/service/datastore" 39 40 "go.chromium.org/luci/cv/internal/metrics" 41 42 . "github.com/smartystreets/goconvey/convey" 43 ) 44 45 func TestInstrumentedFactory(t *testing.T) { 46 t.Parallel() 47 48 Convey("InstrumentedFactory works", t, func() { 49 ctx := context.Background() 50 if testing.Verbose() { 51 ctx = logging.SetLevel(gologger.StdConfig.Use(ctx), logging.Debug) 52 } 53 ctx = memory.Use(ctx) 54 ctx, _, _ = tsmon.WithFakes(ctx) 55 tsmon.GetState(ctx).SetStore(store.NewInMemory(&target.Task{})) 56 epoch := datastore.RoundTime(testclock.TestRecentTimeUTC) 57 ctx, _ = testclock.UseTime(ctx, epoch) 58 59 const ( 60 bbHost = "buildbucket.example.come" 61 lProject = "test_proj" 62 ) 63 mockBBClient := &mockBBClient{ 64 grpcCode: codes.OK, 65 latency: 100 * time.Millisecond, 66 } 67 f := makeInstrumentedFactory(&mockBBClientFactory{ 68 client: mockBBClient, 69 }) 70 instrumentedClient, err := f.MakeClient(ctx, bbHost, lProject) 71 So(err, ShouldBeNil) 72 73 Convey("OK response", func() { 74 _, err := instrumentedClient.GetBuild(ctx, &bbpb.GetBuildRequest{ 75 Id: 123, 76 }) 77 So(err, ShouldBeNil) 78 So(tsmonSentCounter(ctx, metrics.Internal.BuildbucketRPCCount, lProject, bbHost, "GetBuild", "OK"), ShouldEqual, 1) 79 So(tsmonSentDistr(ctx, metrics.Internal.BuildbucketRPCDurations, lProject, bbHost, "GetBuild", "OK").Sum(), ShouldAlmostEqual, 100) 80 81 Convey("Aware of Batch operation", func() { 82 _, err := instrumentedClient.Batch(ctx, &bbpb.BatchRequest{ 83 Requests: []*bbpb.BatchRequest_Request{ 84 { 85 Request: &bbpb.BatchRequest_Request_GetBuild{ 86 GetBuild: &bbpb.GetBuildRequest{ 87 Id: 123, 88 }, 89 }, 90 }, 91 }, 92 }) 93 So(err, ShouldBeNil) 94 So(tsmonSentCounter(ctx, metrics.Internal.BuildbucketRPCCount, lProject, bbHost, "Batch.GetBuild", "OK"), ShouldEqual, 1) 95 So(tsmonSentDistr(ctx, metrics.Internal.BuildbucketRPCDurations, lProject, bbHost, "Batch.GetBuild", "OK").Sum(), ShouldAlmostEqual, 100) 96 }) 97 }) 98 99 Convey("Error response", func() { 100 mockBBClient.grpcCode = codes.NotFound 101 mockBBClient.latency = 10 * time.Millisecond 102 _, err := instrumentedClient.GetBuild(ctx, &bbpb.GetBuildRequest{ 103 Id: 123, 104 }) 105 So(err, ShouldNotBeNil) 106 So(tsmonSentCounter(ctx, metrics.Internal.BuildbucketRPCCount, lProject, bbHost, "GetBuild", "NOT_FOUND"), ShouldEqual, 1) 107 So(tsmonSentDistr(ctx, metrics.Internal.BuildbucketRPCDurations, lProject, bbHost, "GetBuild", "NOT_FOUND").Sum(), ShouldAlmostEqual, 10) 108 109 }) 110 }) 111 } 112 113 func tsmonSentCounter(ctx context.Context, m types.Metric, fieldVals ...any) int64 { 114 resetTime := time.Time{} 115 v, ok := tsmon.GetState(ctx).Store().Get(ctx, m, resetTime, fieldVals).(int64) 116 if !ok { 117 panic(fmt.Errorf("either metric isn't a Counter or nothing sent with metric fields %s", fieldVals)) 118 } 119 return v 120 } 121 122 func tsmonSentDistr(ctx context.Context, m types.Metric, fieldVals ...any) *distribution.Distribution { 123 resetTime := time.Time{} 124 d, ok := tsmon.GetState(ctx).Store().Get(ctx, m, resetTime, fieldVals).(*distribution.Distribution) 125 if !ok { 126 panic(fmt.Errorf("either metric isn't a Distribution or nothing sent with metric fields %s", fieldVals)) 127 } 128 return d 129 } 130 131 type mockBBClientFactory struct { 132 client Client 133 } 134 135 func (m *mockBBClientFactory) MakeClient(ctx context.Context, host, luciProject string) (Client, error) { 136 return m.client, nil 137 } 138 139 type mockBBClient struct { 140 grpcCode codes.Code 141 latency time.Duration 142 } 143 144 func (m *mockBBClient) GetBuild(ctx context.Context, in *bbpb.GetBuildRequest, opts ...grpc.CallOption) (*bbpb.Build, error) { 145 clock.Get(ctx).(testclock.TestClock).Add(m.latency) 146 if m.grpcCode != codes.OK { 147 return nil, status.Error(m.grpcCode, "something wrong") 148 } 149 return &bbpb.Build{}, nil 150 } 151 func (m *mockBBClient) SearchBuilds(ctx context.Context, in *bbpb.SearchBuildsRequest, opts ...grpc.CallOption) (*bbpb.SearchBuildsResponse, error) { 152 clock.Get(ctx).(testclock.TestClock).Add(m.latency) 153 if m.grpcCode != codes.OK { 154 return nil, status.Error(m.grpcCode, "something wrong") 155 } 156 return &bbpb.SearchBuildsResponse{}, nil 157 } 158 func (m *mockBBClient) CancelBuild(ctx context.Context, in *bbpb.CancelBuildRequest, opts ...grpc.CallOption) (*bbpb.Build, error) { 159 clock.Get(ctx).(testclock.TestClock).Add(m.latency) 160 if m.grpcCode != codes.OK { 161 return nil, status.Error(m.grpcCode, "something wrong") 162 } 163 return &bbpb.Build{}, nil 164 165 } 166 func (m *mockBBClient) Batch(ctx context.Context, in *bbpb.BatchRequest, opts ...grpc.CallOption) (*bbpb.BatchResponse, error) { 167 clock.Get(ctx).(testclock.TestClock).Add(m.latency) 168 if m.grpcCode != codes.OK { 169 return nil, status.Error(m.grpcCode, "something wrong") 170 } 171 return &bbpb.BatchResponse{}, nil 172 }