go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/testmetadataupdator/update_test_metadata_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 testmetadataupdator
    16  
    17  import (
    18  	"context"
    19  	"math"
    20  	"testing"
    21  	"time"
    22  
    23  	"cloud.google.com/go/spanner"
    24  	. "github.com/smartystreets/goconvey/convey"
    25  	. "go.chromium.org/luci/common/testing/assertions"
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"go.chromium.org/luci/resultdb/internal/invocations"
    29  	"go.chromium.org/luci/resultdb/internal/spanutil"
    30  	"go.chromium.org/luci/resultdb/internal/testmetadata"
    31  	"go.chromium.org/luci/resultdb/internal/testutil"
    32  	"go.chromium.org/luci/resultdb/internal/testutil/insert"
    33  	"go.chromium.org/luci/resultdb/pbutil"
    34  	pb "go.chromium.org/luci/resultdb/proto/v1"
    35  )
    36  
    37  func TestFieldExistenceBitField(t *testing.T) {
    38  	Convey(`FieldExistenceBitField`, t, func() {
    39  		Convey("full test metadata", func() {
    40  			md := fakeFullTestMetadata("testname")
    41  
    42  			res := fieldExistenceBitField(md)
    43  			So(res, ShouldEqual, 0b11111)
    44  		})
    45  
    46  		Convey("partial test metadata", func() {
    47  			md := &pb.TestMetadata{
    48  				Location:     &pb.TestLocation{FileName: "testfilename"},
    49  				BugComponent: &pb.BugComponent{},
    50  			}
    51  
    52  			res := fieldExistenceBitField(md)
    53  			So(res, ShouldEqual, 0b10100)
    54  		})
    55  
    56  		Convey("empty test metadata", func() {
    57  			md := &pb.TestMetadata{}
    58  
    59  			res := fieldExistenceBitField(md)
    60  			So(res, ShouldEqual, 0b00000)
    61  		})
    62  	})
    63  }
    64  
    65  func TestUpdateTestMetadata(t *testing.T) {
    66  
    67  	Convey(`Error`, t, func() {
    68  		ctx := testutil.SpannerTestContext(t)
    69  		testInvocationID := invocations.ID("inv1")
    70  
    71  		Convey(`Invocation not finalized`, func() {
    72  			testutil.MustApply(ctx, insert.Invocation(testInvocationID, pb.Invocation_FINALIZING, nil))
    73  
    74  			err := updateTestMetadata(ctx, testInvocationID)
    75  			So(err, ShouldErrLike, "Invocation is not finalized")
    76  		})
    77  	})
    78  
    79  	Convey(`Skip invocation`, t, func() {
    80  		ctx := testutil.SpannerTestContext(t)
    81  		testInvocationID := invocations.ID("inv1")
    82  
    83  		Convey(`Invocation has no sourceSpec`, func() {
    84  			testutil.MustApply(ctx, insert.Invocation(testInvocationID, pb.Invocation_FINALIZED, nil))
    85  
    86  			err := updateTestMetadata(ctx, testInvocationID)
    87  			So(err, ShouldBeNil)
    88  		})
    89  
    90  		Convey(`Invocation has sources inherited`, func() {
    91  			testutil.MustApply(ctx, insert.Invocation(testInvocationID, pb.Invocation_FINALIZED, map[string]any{
    92  				"InheritSources": true,
    93  			}))
    94  
    95  			err := updateTestMetadata(ctx, testInvocationID)
    96  			So(err, ShouldBeNil)
    97  		})
    98  	})
    99  
   100  	Convey(`No error`, t, func() {
   101  		ctx := testutil.SpannerTestContext(t)
   102  
   103  		invSources := func(position int64) *pb.Sources {
   104  			return &pb.Sources{
   105  				GitilesCommit: &pb.GitilesCommit{
   106  					Host:       "testHost",
   107  					Project:    "testProject",
   108  					Ref:        "testRef",
   109  					CommitHash: "testCommitHash",
   110  					Position:   position,
   111  				},
   112  				IsDirty: false}
   113  		}
   114  		sourceRef := &pb.SourceRef{
   115  			System: &pb.SourceRef_Gitiles{
   116  				Gitiles: &pb.GitilesRef{
   117  					Host:    "testHost",
   118  					Project: "testProject",
   119  					Ref:     "testRef",
   120  				},
   121  			},
   122  		}
   123  		baseTestMetadata := testmetadata.TestMetadataRow{
   124  			Project:      "testproject",
   125  			TestID:       "testID",
   126  			RefHash:      pbutil.SourceRefHash(sourceRef),
   127  			SubRealm:     "testrealm",
   128  			TestMetadata: &pb.TestMetadata{},
   129  			SourceRef:    sourceRef,
   130  			Position:     0,
   131  		}
   132  
   133  		verifyDBTestMetadata := func(ctx context.Context, expected testmetadata.TestMetadataRow) {
   134  			// Read from spanner.
   135  			row := &testmetadata.TestMetadataRow{}
   136  			var compressedTestMetadata spanutil.Compressed
   137  			var compressedSourceRef spanutil.Compressed
   138  			key := spanner.Key{expected.Project, expected.TestID, expected.RefHash, expected.SubRealm}
   139  			testutil.MustReadRow(ctx, "TestMetadata", key, map[string]any{
   140  				"Project":      &row.Project,
   141  				"TestId":       &row.TestID,
   142  				"RefHash":      &row.RefHash,
   143  				"SubRealm":     &row.SubRealm,
   144  				"TestMetadata": &compressedTestMetadata,
   145  				"SourceRef":    &compressedSourceRef,
   146  				"Position":     &row.Position,
   147  			})
   148  			row.TestMetadata = &pb.TestMetadata{}
   149  			err := proto.Unmarshal(compressedTestMetadata, row.TestMetadata)
   150  			So(err, ShouldBeNil)
   151  			row.SourceRef = &pb.SourceRef{}
   152  			err = proto.Unmarshal(compressedSourceRef, row.SourceRef)
   153  			So(err, ShouldBeNil)
   154  			// Validate.
   155  			// ShouldResemble does not work on struct with nested proto buffer.
   156  			// So we compare each proto field separately.
   157  			So(row.TestMetadata, ShouldResembleProto, expected.TestMetadata)
   158  			So(row.SourceRef, ShouldResembleProto, expected.SourceRef)
   159  			row.TestMetadata = nil
   160  			row.SourceRef = nil
   161  			expected.TestMetadata = nil
   162  			expected.SourceRef = nil
   163  			expected.LastUpdated = time.Time{} // Remove lastupdated.
   164  			So(row, ShouldResemble, &expected)
   165  		}
   166  		Convey(`Save test metadata for new test`, func() {
   167  			invID := "inv1"
   168  			insertInvocationWithTestResults(ctx, invID, invSources(2), []*pb.TestResult{
   169  				{
   170  					Name:         pbutil.TestResultName(invID, "testID", "1"),
   171  					TestMetadata: &pb.TestMetadata{Name: "othertestname"},
   172  				},
   173  				{
   174  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   175  					TestMetadata: fakeFullTestMetadata("testname"),
   176  				}},
   177  			)
   178  
   179  			err := updateTestMetadata(ctx, invocations.ID(invID))
   180  			So(err, ShouldBeNil)
   181  			expected := baseTestMetadata
   182  			expected.TestMetadata = fakeFullTestMetadata("testname") // From test result.
   183  			expected.Position = 2                                    //From Invocation sources.
   184  			verifyDBTestMetadata(ctx, expected)
   185  		})
   186  
   187  		Convey(`Update test metadata - position advanced, less metadata fields, metadata row expired`, func() {
   188  			invID := "inv1"
   189  			insertInvocationWithTestResults(ctx, invID, invSources(3), []*pb.TestResult{
   190  				{
   191  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   192  					TestMetadata: &pb.TestMetadata{Name: "updatedtestname"},
   193  				}},
   194  			)
   195  			existingTestMetadata := baseTestMetadata
   196  			existingTestMetadata.TestMetadata = fakeFullTestMetadata("testname")
   197  			existingTestMetadata.LastUpdated = time.Now().Add(-49 * time.Hour)
   198  			existingTestMetadata.Position = 2
   199  			insertTestMetadata(ctx, &existingTestMetadata)
   200  
   201  			err := updateTestMetadata(ctx, invocations.ID(invID))
   202  			So(err, ShouldBeNil)
   203  			expected := baseTestMetadata
   204  			expected.TestMetadata = &pb.TestMetadata{Name: "updatedtestname"} // From the test result.
   205  			expected.Position = 3                                             // From the invocation sources.
   206  			verifyDBTestMetadata(ctx, expected)
   207  		})
   208  
   209  		Convey(`Update test metadata - commit position advanced, same metadata fields`, func() {
   210  			invID := "inv1"
   211  			insertInvocationWithTestResults(ctx, invID, invSources(3), []*pb.TestResult{
   212  				{
   213  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   214  					TestMetadata: fakeFullTestMetadata("updatedtestname"),
   215  				}},
   216  			)
   217  			existingTestMetadata := baseTestMetadata
   218  			existingTestMetadata.LastUpdated = time.Now().Add(-1 * time.Hour)
   219  			existingTestMetadata.TestMetadata = fakeFullTestMetadata("testname")
   220  			existingTestMetadata.Position = 2
   221  			insertTestMetadata(ctx, &existingTestMetadata)
   222  
   223  			err := updateTestMetadata(ctx, invocations.ID(invID))
   224  			So(err, ShouldBeNil)
   225  			expected := baseTestMetadata
   226  			expected.TestMetadata = fakeFullTestMetadata("updatedtestname") // From test result.
   227  			expected.Position = 3                                           // From Invocation sources.
   228  			verifyDBTestMetadata(ctx, expected)
   229  		})
   230  
   231  		Convey(`Update test metadata - same position more metadata fields`, func() {
   232  			invID := "inv1"
   233  			insertInvocationWithTestResults(ctx, invID, invSources(2), []*pb.TestResult{
   234  				{
   235  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   236  					TestMetadata: fakeFullTestMetadata("updatedtestname"),
   237  				}},
   238  			)
   239  			existingTestMetadata := baseTestMetadata
   240  			existingTestMetadata.LastUpdated = time.Now().Add(-1 * time.Hour)
   241  			existingTestMetadata.TestMetadata = &pb.TestMetadata{Name: "testname"}
   242  			existingTestMetadata.Position = 2
   243  			insertTestMetadata(ctx, &existingTestMetadata)
   244  
   245  			err := updateTestMetadata(ctx, invocations.ID(invID))
   246  			So(err, ShouldBeNil)
   247  			expected := baseTestMetadata
   248  			expected.TestMetadata = fakeFullTestMetadata("updatedtestname") // From test result.
   249  			expected.Position = 2                                           // From Invocation sources.
   250  			verifyDBTestMetadata(ctx, expected)
   251  		})
   252  
   253  		Convey(`No Update - lower position`, func() {
   254  			invID := "inv1"
   255  			insertInvocationWithTestResults(ctx, invID, invSources(2), []*pb.TestResult{
   256  				{
   257  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   258  					TestMetadata: fakeFullTestMetadata("updatedtestname"),
   259  				}},
   260  			)
   261  			existingTestMetadata := baseTestMetadata
   262  			existingTestMetadata.LastUpdated = time.Now().Add(-1 * time.Hour)
   263  			existingTestMetadata.TestMetadata = &pb.TestMetadata{Name: "testname"}
   264  			existingTestMetadata.Position = math.MaxInt64
   265  			insertTestMetadata(ctx, &existingTestMetadata)
   266  
   267  			err := updateTestMetadata(ctx, invocations.ID(invID))
   268  			So(err, ShouldBeNil)
   269  			verifyDBTestMetadata(ctx, existingTestMetadata)
   270  		})
   271  
   272  		Convey(`No Update - same position same metadata fields`, func() {
   273  			invID := "inv1"
   274  			insertInvocationWithTestResults(ctx, invID, invSources(2), []*pb.TestResult{
   275  				{
   276  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   277  					TestMetadata: &pb.TestMetadata{Name: "updatedTestname"},
   278  				}},
   279  			)
   280  			existingTestMetadata := baseTestMetadata
   281  			existingTestMetadata.LastUpdated = time.Now().Add(-1 * time.Hour)
   282  			existingTestMetadata.TestMetadata = &pb.TestMetadata{Name: "testname"}
   283  			existingTestMetadata.Position = 2
   284  			insertTestMetadata(ctx, &existingTestMetadata)
   285  
   286  			err := updateTestMetadata(ctx, invocations.ID(invID))
   287  			So(err, ShouldBeNil)
   288  			verifyDBTestMetadata(ctx, existingTestMetadata)
   289  		})
   290  
   291  		Convey(`No Update -  position advance and less metadata fields`, func() {
   292  			invID := "inv1"
   293  			insertInvocationWithTestResults(ctx, invID, invSources(3), []*pb.TestResult{
   294  				{
   295  					Name:         pbutil.TestResultName(invID, "testID", "0"),
   296  					TestMetadata: &pb.TestMetadata{Name: "updatedTestname"},
   297  				}},
   298  			)
   299  			existingTestMetadata := baseTestMetadata
   300  			existingTestMetadata.LastUpdated = time.Now().Add(-1 * time.Hour)
   301  			existingTestMetadata.TestMetadata = fakeFullTestMetadata("testname")
   302  			existingTestMetadata.Position = 2
   303  			insertTestMetadata(ctx, &existingTestMetadata)
   304  
   305  			err := updateTestMetadata(ctx, invocations.ID(invID))
   306  			So(err, ShouldBeNil)
   307  			verifyDBTestMetadata(ctx, existingTestMetadata)
   308  		})
   309  
   310  		Convey(`Save test metadata for new tests from different invocations`, func() {
   311  			testInvocationID := invocations.ID("inv1")
   312  			includedInvocationID := invocations.ID("includedinv")
   313  			testutil.MustApply(ctx,
   314  				testutil.CombineMutations(
   315  					insert.InvocationWithInclusions(testInvocationID, pb.Invocation_FINALIZED, map[string]any{
   316  						"Realm":   "testproject:testrealm",
   317  						"Sources": spanutil.Compressed(pbutil.MustMarshal(invSources(2)))}, includedInvocationID),
   318  					insert.InvocationWithInclusions(includedInvocationID, pb.Invocation_FINALIZED, map[string]any{
   319  						"Realm":          "testproject:otherRealm",
   320  						"InheritSources": true}),
   321  				)...)
   322  			testutil.MustApply(ctx, insert.TestResultMessages([]*pb.TestResult{
   323  				{
   324  					Name:         pbutil.TestResultName(string(testInvocationID), "testID", "1"),
   325  					TestMetadata: &pb.TestMetadata{Name: "testname"},
   326  				},
   327  				{
   328  					Name:         pbutil.TestResultName(string(includedInvocationID), "includedTestID", "1"),
   329  					TestMetadata: &pb.TestMetadata{Name: "includedtestname"},
   330  				}})...)
   331  
   332  			err := updateTestMetadata(ctx, testInvocationID)
   333  			So(err, ShouldBeNil)
   334  			expected1 := baseTestMetadata
   335  			expected1.TestMetadata = &pb.TestMetadata{Name: "testname"}
   336  			expected1.Position = 2
   337  			expected2 := baseTestMetadata
   338  			expected2.TestID = "includedTestID"
   339  			expected2.SubRealm = "otherRealm"
   340  			expected2.TestMetadata = &pb.TestMetadata{Name: "includedtestname"}
   341  			expected2.Position = 2
   342  			verifyDBTestMetadata(ctx, expected1)
   343  			verifyDBTestMetadata(ctx, expected2)
   344  		})
   345  	})
   346  }
   347  
   348  func insertInvocationWithTestResults(ctx context.Context, invID string, sources *pb.Sources, testResults []*pb.TestResult) {
   349  	testutil.MustApply(ctx,
   350  		insert.Invocation(invocations.ID(invID), pb.Invocation_FINALIZED, map[string]any{
   351  			"Realm":   "testproject:testrealm",
   352  			"Sources": spanutil.Compressed(pbutil.MustMarshal(sources))}))
   353  	testutil.MustApply(ctx, insert.TestResultMessages(testResults)...)
   354  }
   355  
   356  func insertTestMetadata(ctx context.Context, tm *testmetadata.TestMetadataRow) {
   357  	testutil.MustApply(ctx, insert.TestMetadataRows([]*testmetadata.TestMetadataRow{tm})...)
   358  }
   359  
   360  func fakeFullTestMetadata(testname string) *pb.TestMetadata {
   361  	return &pb.TestMetadata{
   362  		Name: testname,
   363  		Location: &pb.TestLocation{
   364  			Repo:     "testrepo",
   365  			FileName: "testFileName",
   366  			Line:     10,
   367  		},
   368  		BugComponent: &pb.BugComponent{
   369  			System: &pb.BugComponent_IssueTracker{
   370  				IssueTracker: &pb.IssueTrackerComponent{
   371  					ComponentId: 100,
   372  				},
   373  			},
   374  		},
   375  	}
   376  }