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  }