go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/baselineupdater/baselineupdater_test.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 baselineupdater
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"cloud.google.com/go/spanner"
    23  	. "github.com/smartystreets/goconvey/convey"
    24  
    25  	. "go.chromium.org/luci/common/testing/assertions"
    26  
    27  	"go.chromium.org/luci/resultdb/internal/baselines"
    28  	btv "go.chromium.org/luci/resultdb/internal/baselines/testvariants"
    29  	"go.chromium.org/luci/resultdb/internal/invocations"
    30  	"go.chromium.org/luci/resultdb/internal/spanutil"
    31  	"go.chromium.org/luci/resultdb/internal/testutil"
    32  	"go.chromium.org/luci/resultdb/internal/testutil/insert"
    33  
    34  	"go.chromium.org/luci/resultdb/pbutil"
    35  	pb "go.chromium.org/luci/resultdb/proto/v1"
    36  	"go.chromium.org/luci/server/span"
    37  )
    38  
    39  func TestShouldMarkSubmitted(t *testing.T) {
    40  	Convey(`ShouldMarkSubmitted`, t, func() {
    41  		ctx := testutil.SpannerTestContext(t)
    42  
    43  		Convey(`FinalizedInvocation`, func() {
    44  			testutil.MustApply(ctx, testutil.CombineMutations(
    45  				insert.InvocationWithInclusions("a", pb.Invocation_FINALIZED, map[string]any{
    46  					"BaselineId": "try:linux-rel",
    47  				}),
    48  			)...)
    49  
    50  			inv, err := invocations.Read(span.Single(ctx), "a")
    51  			So(err, ShouldBeNil)
    52  
    53  			err = shouldMarkSubmitted(inv)
    54  			So(err, ShouldBeNil)
    55  		})
    56  
    57  		Convey(`Not Finalized Invocation`, func() {
    58  			testutil.MustApply(ctx, testutil.CombineMutations(
    59  				insert.InvocationWithInclusions("a", pb.Invocation_ACTIVE, map[string]any{
    60  					"BaselineId": "try:linux-rel",
    61  				}),
    62  			)...)
    63  
    64  			inv, err := invocations.Read(span.Single(ctx), "a")
    65  			So(err, ShouldBeNil)
    66  
    67  			err = shouldMarkSubmitted(inv)
    68  			So(err, ShouldErrLike, `the invocation is not yet finalized`)
    69  		})
    70  
    71  		Convey(`Non existent invocation`, func() {
    72  			_, err := invocations.Read(span.Single(ctx), "a")
    73  			So(err, ShouldErrLike, `invocations/a not found`)
    74  		})
    75  	})
    76  }
    77  
    78  func TestEnsureBaselineExists(t *testing.T) {
    79  	Convey(`EnsureBaselineExists`, t, func() {
    80  		ctx := testutil.SpannerTestContext(t)
    81  
    82  		Convey(`New Baseline`, func() {
    83  			inv := &pb.Invocation{
    84  				Name:       "invocations/a",
    85  				State:      pb.Invocation_FINALIZED,
    86  				Realm:      "testproject:testrealm",
    87  				BaselineId: "testrealm:linux-rel",
    88  			}
    89  
    90  			err := ensureBaselineExists(ctx, inv)
    91  			So(err, ShouldBeNil)
    92  
    93  			baseline, err := baselines.Read(span.Single(ctx), "testproject", "testrealm:linux-rel")
    94  			So(err, ShouldBeNil)
    95  			So(baseline.Project, ShouldEqual, "testproject")
    96  			So(baseline.BaselineID, ShouldEqual, "testrealm:linux-rel")
    97  		})
    98  
    99  		Convey(`Existing baseline updated`, func() {
   100  			var twoHoursAgo = time.Now().UTC().Add(-time.Hour * 2)
   101  			row := map[string]any{
   102  				"Project":         "testproject",
   103  				"BaselineId":      "try:linux-rel",
   104  				"LastUpdatedTime": twoHoursAgo,
   105  				"CreationTime":    twoHoursAgo,
   106  			}
   107  
   108  			testutil.MustApply(ctx,
   109  				spanutil.InsertMap("Baselines", row),
   110  			)
   111  
   112  			baseline, err := baselines.Read(span.Single(ctx), "testproject", "try:linux-rel")
   113  			So(err, ShouldBeNil)
   114  			So(baseline.LastUpdatedTime, ShouldEqual, twoHoursAgo)
   115  
   116  			inv := &pb.Invocation{
   117  				Name:       "invocations/a",
   118  				State:      pb.Invocation_FINALIZED,
   119  				Realm:      "testproject:testrealm",
   120  				BaselineId: "try:linux-rel",
   121  			}
   122  
   123  			// ensure baseline exists should update existing invocations'
   124  			// last updated time to now.
   125  			err = ensureBaselineExists(ctx, inv)
   126  			So(err, ShouldBeNil)
   127  
   128  			// re-read the baseline after ensuring it exists. the timestanp should
   129  			// be different from the original timestamp it was written into.
   130  			baseline, err = baselines.Read(span.Single(ctx), "testproject", "try:linux-rel")
   131  			So(err, ShouldBeNil)
   132  			So(baseline.LastUpdatedTime, ShouldNotEqual, twoHoursAgo)
   133  		})
   134  	})
   135  }
   136  
   137  func TestTryMarkInvocationSubmitted(t *testing.T) {
   138  
   139  	Convey(`e2e`, t, func() {
   140  		ctx := testutil.SpannerTestContext(t)
   141  
   142  		testutil.MustApply(ctx, testutil.CombineMutations(
   143  			insert.InvocationWithInclusions("inv1", pb.Invocation_FINALIZED, map[string]any{"BaselineId": "try:linux-rel"}, "inv2"),
   144  			insert.InvocationWithInclusions("inv2", pb.Invocation_FINALIZED, map[string]any{"BaselineId": "try:linux-rel"}),
   145  		)...)
   146  
   147  		// Split the test results between parent and included to ensure that the query
   148  		// fetches results for all included invs.
   149  		ms := make([]*spanner.Mutation, 0)
   150  		for i := 1; i <= 4000; i++ {
   151  			ms = append(ms, insert.TestResults("inv1", fmt.Sprintf("testId%d", i), pbutil.Variant("a", "b"), pb.TestStatus_PASS)...)
   152  		}
   153  		// TransactionLimit + 1 so we know there's overflow.
   154  		for i := 4001; i <= TransactionLimit+1; i++ {
   155  			ms = append(ms, insert.TestResults("inv2", fmt.Sprintf("testId%d", i), pbutil.Variant("a", "b"), pb.TestStatus_PASS)...)
   156  		}
   157  
   158  		testutil.MustApply(ctx, testutil.CombineMutations(ms)...)
   159  		err := tryMarkInvocationSubmitted(ctx, invocations.ID("inv1"))
   160  		So(err, ShouldBeNil)
   161  
   162  		// fetch the 8001 entry to ensure that test results from subinvocations
   163  		// that also exceed the 8000 limit are being processed
   164  		res, err := btv.Read(span.Single(ctx), "testproject", "try:linux-rel", "testId8001", pbutil.VariantHash(pbutil.Variant("a", "b")))
   165  		So(err, ShouldBeNil)
   166  		So(res.Project, ShouldEqual, "testproject")
   167  		So(res.BaselineID, ShouldEqual, "try:linux-rel")
   168  		So(res.TestID, ShouldEqual, "testId8001")
   169  
   170  		// find the baseline in the baselines table
   171  		baseline, err := baselines.Read(span.Single(ctx), "testproject", "try:linux-rel")
   172  		So(err, ShouldBeNil)
   173  		So(res.Project, ShouldEqual, baseline.Project)
   174  		So(res.BaselineID, ShouldEqual, baseline.BaselineID)
   175  
   176  		Convey(`Missing baseline`, func() {
   177  			testutil.MustApply(ctx, testutil.CombineMutations(
   178  				insert.InvocationWithInclusions("a", pb.Invocation_FINALIZED, nil),
   179  			)...)
   180  
   181  			_, err := invocations.Read(span.Single(ctx), "a")
   182  			So(err, ShouldBeNil)
   183  
   184  			err = tryMarkInvocationSubmitted(ctx, invocations.ID("a"))
   185  			// invocations without a baseline specified will terminate early with no error.
   186  			So(err, ShouldBeNil)
   187  		})
   188  
   189  		Convey(`Mark existing baseline test variant submitted`, func() {
   190  			testutil.MustApply(ctx, testutil.CombineMutations(
   191  				[]*spanner.Mutation{insert.Invocation("inv3", pb.Invocation_FINALIZED, map[string]any{"BaselineId": "try:linux-rel"})},
   192  				insert.TestResults("inv3", "testId8001", pbutil.Variant("a", "b"), pb.TestStatus_PASS),
   193  			)...)
   194  
   195  			err := tryMarkInvocationSubmitted(ctx, invocations.ID("inv3"))
   196  			So(err, ShouldBeNil)
   197  
   198  			exp := &btv.BaselineTestVariant{
   199  				Project:     "testproject",
   200  				BaselineID:  "try:linux-rel",
   201  				TestID:      "testId8001",
   202  				VariantHash: pbutil.VariantHash(pbutil.Variant("a", "b")),
   203  			}
   204  
   205  			res, err := btv.Read(span.Single(ctx), exp.Project, exp.BaselineID, exp.TestID, exp.VariantHash)
   206  			// zero out Timestamp
   207  			res.LastUpdated = time.Time{}
   208  			So(err, ShouldBeNil)
   209  			So(res, ShouldResemble, exp)
   210  		})
   211  
   212  		Convey(`Mark skipped test should be skipped`, func() {
   213  			testutil.MustApply(ctx, testutil.CombineMutations(
   214  				[]*spanner.Mutation{insert.Invocation("inv4", pb.Invocation_FINALIZED, map[string]any{"BaselineId": "try:linux-rel"})},
   215  				insert.TestResults("inv4", "testId8002", pbutil.Variant("a", "b"), pb.TestStatus_SKIP),
   216  			)...)
   217  
   218  			err := tryMarkInvocationSubmitted(ctx, invocations.ID("inv4"))
   219  			So(err, ShouldBeNil)
   220  
   221  			exp := &btv.BaselineTestVariant{
   222  				Project:     "testproject",
   223  				BaselineID:  "try:linux-rel",
   224  				TestID:      "testId8002",
   225  				VariantHash: pbutil.VariantHash(pbutil.Variant("a", "b")),
   226  			}
   227  
   228  			_, err = btv.Read(span.Single(ctx), exp.Project, exp.BaselineID, exp.TestID, exp.VariantHash)
   229  			So(err, ShouldErrLike, btv.NotFound)
   230  		})
   231  	})
   232  }