go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/aggrmetrics/driver_test.go (about)

     1  // Copyright 2020 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 aggrmetrics
    16  
    17  import (
    18  	"context"
    19  	"sync/atomic"
    20  	"testing"
    21  	"time"
    22  
    23  	"go.chromium.org/luci/common/errors"
    24  	"go.chromium.org/luci/common/logging"
    25  	"go.chromium.org/luci/common/retry/transient"
    26  	"go.chromium.org/luci/common/tsmon/field"
    27  	"go.chromium.org/luci/common/tsmon/metric"
    28  	"go.chromium.org/luci/common/tsmon/types"
    29  
    30  	cfgpb "go.chromium.org/luci/cv/api/config/v2"
    31  	"go.chromium.org/luci/cv/internal/configs/prjcfg/prjcfgtest"
    32  	"go.chromium.org/luci/cv/internal/cvtesting"
    33  
    34  	. "github.com/smartystreets/goconvey/convey"
    35  	. "go.chromium.org/luci/common/testing/assertions"
    36  )
    37  
    38  func TestDriver(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	Convey("Driver smoke test", t, func() {
    42  		ct := cvtesting.Test{}
    43  		_, cancel := ct.SetUp(t)
    44  		defer cancel()
    45  		_ = New(ct.Env)
    46  	})
    47  
    48  	Convey("Driver works", t, func() {
    49  		ct := cvtesting.Test{}
    50  		ctx, cancel := ct.SetUp(t)
    51  		defer cancel()
    52  
    53  		mSent := func(fields ...any) any {
    54  			return ct.TSMonSentValue(ctx, testMetric, fields...)
    55  		}
    56  
    57  		d := Driver{
    58  			aggregators: []aggregator{
    59  				&testAggregator{name: "agg"},
    60  			},
    61  		}
    62  
    63  		Convey("No projects", func() {
    64  			So(d.Cron(ctx), ShouldBeNil)
    65  			So(ct.TSMonStore.GetAll(ctx), ShouldBeEmpty)
    66  		})
    67  
    68  		Convey("With one active project", func() {
    69  			prjcfgtest.Create(ctx, "first", &cfgpb.Config{})
    70  			So(d.Cron(ctx), ShouldBeNil)
    71  
    72  			Convey("Reports new data", func() {
    73  				So(ct.TSMonStore.GetAll(ctx), ShouldHaveLength, 1)
    74  				So(mSent("first", "agg"), ShouldEqual, 1001)
    75  			})
    76  
    77  			Convey("Calling again won't report old data", func() {
    78  				prjcfgtest.Disable(ctx, "first")
    79  				prjcfgtest.Create(ctx, "second", &cfgpb.Config{})
    80  				ct.Clock.Add(1 * time.Hour)
    81  				So(d.Cron(ctx), ShouldBeNil)
    82  				So(ct.TSMonStore.GetAll(ctx), ShouldHaveLength, 1)
    83  				So(mSent("second", "agg"), ShouldEqual, 1001)
    84  			})
    85  		})
    86  
    87  		Convey("Multiple aggregators", func() {
    88  			aggregatorFoo := &testAggregator{name: "foo"}
    89  			aggregatorBar := &testAggregator{name: "bar"}
    90  			d.aggregators = []aggregator{
    91  				aggregatorFoo,
    92  				aggregatorBar,
    93  			}
    94  
    95  			prjcfgtest.Create(ctx, "first", &cfgpb.Config{})
    96  
    97  			Convey("All succeeds", func() {
    98  				So(d.Cron(ctx), ShouldBeNil)
    99  				So(ct.TSMonStore.GetAll(ctx), ShouldHaveLength, 2)
   100  				So(mSent("first", "foo"), ShouldEqual, 1001)
   101  				So(mSent("first", "bar"), ShouldEqual, 1001)
   102  			})
   103  
   104  			Convey("Fails but report partially", func() {
   105  				aggregatorBar.err = errors.New("something wrong")
   106  				So(d.Cron(ctx), ShouldErrLike, "something wrong")
   107  				So(ct.TSMonStore.GetAll(ctx), ShouldHaveLength, 1)
   108  				So(mSent("first", "foo"), ShouldEqual, 1001)
   109  			})
   110  
   111  			Convey("All failed", func() {
   112  				aggregatorFoo.err = transient.Tag.Apply(errors.New("foo went wrong"))
   113  				aggregatorBar.err = errors.New("bar went wrong")
   114  				So(d.Cron(ctx), ShouldErrLike, "bar went wrong") // use most serve error
   115  				So(ct.TSMonStore.GetAll(ctx), ShouldBeEmpty)
   116  			})
   117  		})
   118  	})
   119  }
   120  
   121  var testMetric = metric.NewInt("test/aggrmetrics", "test only", nil, field.String("project"), field.String("name"))
   122  
   123  type testAggregator struct {
   124  	name string
   125  	err  error
   126  	cnt  int32
   127  }
   128  
   129  func (t *testAggregator) metrics() []types.Metric {
   130  	return []types.Metric{testMetric}
   131  }
   132  
   133  func (t *testAggregator) report(ctx context.Context, projects []string) error {
   134  	cnt := atomic.AddInt32(&t.cnt, 1)
   135  	if t.err != nil {
   136  		return t.err
   137  	}
   138  	vals := make(map[string]int64, len(projects))
   139  	for rank, p := range projects {
   140  		testMetric.Set(ctx, int64(1000+1+rank), p, t.name)
   141  	}
   142  	logging.Debugf(ctx, "testAggregator %q [%d]: reported %d values", t.name, cnt, len(vals))
   143  	return nil
   144  }