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 }