go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/purger/purger_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 purger
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strconv"
    21  	"testing"
    22  	"time"
    23  
    24  	"cloud.google.com/go/spanner"
    25  	durpb "google.golang.org/protobuf/types/known/durationpb"
    26  
    27  	"go.chromium.org/luci/common/clock"
    28  	"go.chromium.org/luci/server/span"
    29  
    30  	"go.chromium.org/luci/resultdb/internal/artifacts"
    31  	"go.chromium.org/luci/resultdb/internal/invocations"
    32  	"go.chromium.org/luci/resultdb/internal/spanutil"
    33  	"go.chromium.org/luci/resultdb/internal/testutil"
    34  	"go.chromium.org/luci/resultdb/internal/testutil/insert"
    35  	"go.chromium.org/luci/resultdb/pbutil"
    36  	pb "go.chromium.org/luci/resultdb/proto/v1"
    37  
    38  	. "github.com/smartystreets/goconvey/convey"
    39  )
    40  
    41  // makeTestResultsWithVariants creates test results with a number of passing/failing variants.
    42  //
    43  // There'll be two rows for each variant. If the variant is a passing variant, both results
    44  // will have a passing status, otherwise the first will be passing, and the second faled.
    45  func makeTestResultsWithVariants(invID, testID string, nPassingVariants, nFailedVariants int) []*pb.TestResult {
    46  	nVariants := nPassingVariants + nFailedVariants
    47  	// For every variant we'll generate two rows.
    48  	statuses := []pb.TestStatus{pb.TestStatus_PASS, pb.TestStatus_PASS}
    49  	trs := make([]*pb.TestResult, nVariants*len(statuses))
    50  	for v := 0; v < nVariants; v++ {
    51  		// We'll generate all passing variants first, so we only have to change the
    52  		// list of statuses once.
    53  		if v == nPassingVariants {
    54  			statuses[len(statuses)-1] = pb.TestStatus_FAIL
    55  		}
    56  		for s, status := range statuses {
    57  			rIndex := v*len(statuses) + s
    58  			resultID := fmt.Sprintf("%d", rIndex)
    59  			trs[rIndex] = &pb.TestResult{
    60  				Name:     pbutil.TestResultName(invID, testID, resultID),
    61  				TestId:   testID,
    62  				ResultId: resultID,
    63  				Variant:  pbutil.Variant("k1", "v1", "k2", fmt.Sprintf("v%d", v)),
    64  				Expected: status == pb.TestStatus_PASS,
    65  				Status:   status,
    66  				Duration: &durpb.Duration{Seconds: int64(rIndex), Nanos: 234567000},
    67  			}
    68  		}
    69  	}
    70  	return trs
    71  }
    72  
    73  func insertInvocation(ctx context.Context, invID invocations.ID, nTests, nPassingVariants, nFailedVariants, nArtifactsPerResult int) invocations.ID {
    74  	now := clock.Now(ctx).UTC()
    75  
    76  	// Insert an invocation,
    77  	testutil.MustApply(ctx, insert.Invocation(invID, pb.Invocation_FINALIZED, map[string]any{
    78  		"ExpectedTestResultsExpirationTime": now.Add(-time.Minute),
    79  		"CreateTime":                        now.Add(-time.Hour),
    80  		"FinalizeTime":                      now.Add(-time.Hour),
    81  	}))
    82  
    83  	// Insert test results and artifacts.
    84  	inserts := []*spanner.Mutation{}
    85  	for i := 0; i < nTests; i++ {
    86  		results := makeTestResultsWithVariants(string(invID), fmt.Sprintf("Test%d", i), nPassingVariants, nFailedVariants)
    87  		inserts = append(inserts, insert.TestResultMessages(results)...)
    88  		for _, res := range results {
    89  			for j := 0; j < nArtifactsPerResult; j++ {
    90  				inserts = append(inserts, spanutil.InsertMap("Artifacts", map[string]any{
    91  					"InvocationId": invID,
    92  					"ParentId":     artifacts.ParentID(res.TestId, res.ResultId),
    93  					"ArtifactId":   strconv.Itoa(j),
    94  				}))
    95  			}
    96  		}
    97  	}
    98  	testutil.MustApply(ctx, testutil.CombineMutations(inserts)...)
    99  	return invID
   100  }
   101  
   102  func countRows(ctx context.Context, invID invocations.ID) (testResults, artifacts int64) {
   103  	st := spanner.NewStatement(`
   104  		SELECT
   105  			(SELECT COUNT(*) FROM TestResults WHERE InvocationId = @invocationId),
   106  			(SELECT COUNT(*) FROM Artifacts WHERE InvocationId = @invocationId),
   107  		`)
   108  	st.Params["invocationId"] = spanutil.ToSpanner(invID)
   109  	So(spanutil.QueryFirstRow(span.Single(ctx), st, &testResults, &artifacts), ShouldBeNil)
   110  	return
   111  }
   112  
   113  func TestPurgeExpiredResults(t *testing.T) {
   114  	Convey(`TestPurgeExpiredResults`, t, func() {
   115  		ctx := testutil.SpannerTestContext(t)
   116  
   117  		Convey(`Some results are unexpected`, func() {
   118  			inv := insertInvocation(ctx, "inv-some-unexpected", 10, 9, 1, 0)
   119  
   120  			err := purgeOneShard(ctx, 0)
   121  			So(err, ShouldBeNil)
   122  
   123  			// 10 tests * 1 variant with unexpected results * 2 results per test variant.
   124  			testResults, _ := countRows(ctx, inv)
   125  			So(testResults, ShouldEqual, 20)
   126  		})
   127  
   128  		Convey(`No unexpected results`, func() {
   129  			inv := insertInvocation(ctx, "inv-no-unexpected", 10, 10, 0, 0)
   130  
   131  			err := purgeOneShard(ctx, 0)
   132  			So(err, ShouldBeNil)
   133  
   134  			// 10 tests * 0 variants with unexpected results * 2 results per test variant.
   135  			testResults, _ := countRows(ctx, inv)
   136  			So(testResults, ShouldEqual, 0)
   137  		})
   138  
   139  		Convey(`Purge artifacts`, func() {
   140  			inv := insertInvocation(ctx, "inv", 10, 9, 1, 10)
   141  
   142  			err := purgeOneShard(ctx, 0)
   143  			So(err, ShouldBeNil)
   144  
   145  			// 10 tests * 1 variant with unexpected results * 2 results per test variant * 10 artifacts..
   146  			_, artifacts := countRows(ctx, inv)
   147  			So(artifacts, ShouldEqual, 200)
   148  		})
   149  	})
   150  }