| // Copyright 2019 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package insert implements functions to insert rows for testing purposes. |
| package insert |
| |
| import ( |
| "fmt" |
| "strconv" |
| "time" |
| |
| "cloud.google.com/go/spanner" |
| . "github.com/smartystreets/goconvey/convey" |
| "google.golang.org/protobuf/proto" |
| durpb "google.golang.org/protobuf/types/known/durationpb" |
| |
| "go.chromium.org/luci/resultdb/internal/invocations" |
| "go.chromium.org/luci/resultdb/internal/spanutil" |
| "go.chromium.org/luci/resultdb/internal/testmetadata" |
| "go.chromium.org/luci/resultdb/pbutil" |
| pb "go.chromium.org/luci/resultdb/proto/v1" |
| ) |
| |
| // TestRealm is the default realm used for invocation mutations returned by Invocation(). |
| const TestRealm = "testproject:testrealm" |
| |
| func updateDict(dest, source map[string]any) { |
| for k, v := range source { |
| dest[k] = v |
| } |
| } |
| |
| // Invocation returns a spanner mutation that inserts an invocation. |
| func Invocation(id invocations.ID, state pb.Invocation_State, extraValues map[string]any) *spanner.Mutation { |
| future := time.Date(2050, 1, 1, 0, 0, 0, 0, time.UTC) |
| values := map[string]any{ |
| "InvocationId": id, |
| "ShardId": 0, |
| "State": state, |
| "Realm": TestRealm, |
| "InvocationExpirationTime": future, |
| "ExpectedTestResultsExpirationTime": future, |
| "CreateTime": spanner.CommitTimestamp, |
| "Deadline": future, |
| } |
| |
| if state == pb.Invocation_FINALIZED { |
| values["FinalizeTime"] = spanner.CommitTimestamp |
| } |
| updateDict(values, extraValues) |
| return spanutil.InsertMap("Invocations", values) |
| } |
| |
| // FinalizedInvocationWithInclusions returns mutations to insert a finalized invocation with inclusions. |
| func FinalizedInvocationWithInclusions(id invocations.ID, extraValues map[string]any, included ...invocations.ID) []*spanner.Mutation { |
| return InvocationWithInclusions(id, pb.Invocation_FINALIZED, extraValues, included...) |
| } |
| |
| // InvocationWithInclusions returns mutations to insert an invocation with inclusions. |
| func InvocationWithInclusions(id invocations.ID, state pb.Invocation_State, extraValues map[string]any, included ...invocations.ID) []*spanner.Mutation { |
| ms := []*spanner.Mutation{Invocation(id, state, extraValues)} |
| for _, incl := range included { |
| ms = append(ms, Inclusion(id, incl)) |
| } |
| return ms |
| } |
| |
| // Inclusion returns a spanner mutation that inserts an inclusion. |
| func Inclusion(including, included invocations.ID) *spanner.Mutation { |
| return spanutil.InsertMap("IncludedInvocations", map[string]any{ |
| "InvocationId": including, |
| "IncludedInvocationId": included, |
| }) |
| } |
| |
| // TestResults returns spanner mutations to insert test results |
| func TestResults(invID, testID string, v *pb.Variant, statuses ...pb.TestStatus) []*spanner.Mutation { |
| return TestResultMessages(MakeTestResults(invID, testID, v, statuses...)) |
| } |
| |
| // TestResultMessages returns spanner mutations to insert test results |
| func TestResultMessages(trs []*pb.TestResult) []*spanner.Mutation { |
| ms := make([]*spanner.Mutation, len(trs)) |
| for i, tr := range trs { |
| invID, testID, resultID, err := pbutil.ParseTestResultName(tr.Name) |
| So(err, ShouldBeNil) |
| mutMap := map[string]any{ |
| "InvocationId": invocations.ID(invID), |
| "TestId": testID, |
| "ResultId": resultID, |
| "Variant": trs[i].Variant, |
| "VariantHash": pbutil.VariantHash(trs[i].Variant), |
| "CommitTimestamp": spanner.CommitTimestamp, |
| "Status": tr.Status, |
| "RunDurationUsec": 1e6*i + 234567, |
| "SummaryHtml": spanutil.Compressed("SummaryHtml"), |
| } |
| if tr.SkipReason != pb.SkipReason_SKIP_REASON_UNSPECIFIED { |
| mutMap["SkipReason"] = tr.SkipReason |
| } |
| if !trs[i].Expected { |
| mutMap["IsUnexpected"] = true |
| } |
| if tr.TestMetadata != nil { |
| tmdBytes, err := proto.Marshal(tr.TestMetadata) |
| So(err, ShouldBeNil) |
| mutMap["TestMetadata"] = spanutil.Compressed(tmdBytes) |
| } |
| if tr.FailureReason != nil { |
| frBytes, err := proto.Marshal(tr.FailureReason) |
| So(err, ShouldBeNil) |
| mutMap["FailureReason"] = spanutil.Compressed(frBytes) |
| } |
| if tr.Properties != nil { |
| propertiesBytes, err := proto.Marshal(tr.Properties) |
| So(err, ShouldBeNil) |
| mutMap["Properties"] = spanutil.Compressed(propertiesBytes) |
| } |
| |
| ms[i] = spanutil.InsertMap("TestResults", mutMap) |
| } |
| return ms |
| } |
| |
| // TestExonerations returns Spanner mutations to insert test exonerations. |
| func TestExonerations(invID invocations.ID, testID string, variant *pb.Variant, reasons ...pb.ExonerationReason) []*spanner.Mutation { |
| ms := make([]*spanner.Mutation, len(reasons)) |
| for i := 0; i < len(reasons); i++ { |
| ms[i] = spanutil.InsertMap("TestExonerations", map[string]any{ |
| "InvocationId": invID, |
| "TestId": testID, |
| "ExonerationId": strconv.Itoa(i), |
| "Variant": variant, |
| "VariantHash": pbutil.VariantHash(variant), |
| "ExplanationHTML": spanutil.Compressed(fmt.Sprintf("explanation %d", i)), |
| "Reason": reasons[i], |
| }) |
| } |
| return ms |
| } |
| |
| // Artifact returns a Spanner mutation to insert an artifact. |
| func Artifact(invID invocations.ID, parentID, artID string, extraValues map[string]any) *spanner.Mutation { |
| values := map[string]any{ |
| "InvocationId": invID, |
| "ParentID": parentID, |
| "ArtifactId": artID, |
| } |
| updateDict(values, extraValues) |
| return spanutil.InsertMap("Artifacts", values) |
| } |
| |
| func TestMetadataRows(rows []*testmetadata.TestMetadataRow) []*spanner.Mutation { |
| ms := make([]*spanner.Mutation, len(rows)) |
| for i, row := range rows { |
| mutMap := map[string]any{ |
| "Project": row.Project, |
| "TestId": row.TestID, |
| "SubRealm": row.SubRealm, |
| "RefHash": row.RefHash, |
| "LastUpdated": row.LastUpdated, |
| "TestMetadata": spanutil.Compressed(pbutil.MustMarshal(row.TestMetadata)), |
| "SourceRef": spanutil.Compressed(pbutil.MustMarshal(row.SourceRef)), |
| "Position": int64(row.Position), |
| } |
| ms[i] = spanutil.InsertMap("TestMetadata", mutMap) |
| } |
| return ms |
| } |
| |
| func MakeTestMetadataRow(project, testID, subRealm string, refHash []byte) *testmetadata.TestMetadataRow { |
| return &testmetadata.TestMetadataRow{ |
| Project: project, |
| TestID: testID, |
| RefHash: refHash, |
| SubRealm: subRealm, |
| LastUpdated: time.Time{}, |
| TestMetadata: &pb.TestMetadata{ |
| Name: project + "\n" + testID + "\n" + subRealm + "\n" + string(refHash), |
| Location: &pb.TestLocation{ |
| Repo: "testRepo", |
| FileName: "testFile", |
| Line: 0, |
| }, |
| BugComponent: &pb.BugComponent{}, |
| }, |
| SourceRef: &pb.SourceRef{ |
| System: &pb.SourceRef_Gitiles{ |
| Gitiles: &pb.GitilesRef{Host: "testHost"}, |
| }, |
| }, |
| Position: 0, |
| } |
| } |
| |
| // MakeTestResults creates test results. |
| func MakeTestResults(invID, testID string, v *pb.Variant, statuses ...pb.TestStatus) []*pb.TestResult { |
| trs := make([]*pb.TestResult, len(statuses)) |
| for i, status := range statuses { |
| resultID := fmt.Sprintf("%d", i) |
| trs[i] = &pb.TestResult{ |
| Name: pbutil.TestResultName(invID, testID, resultID), |
| TestId: testID, |
| ResultId: resultID, |
| Variant: v, |
| VariantHash: pbutil.VariantHash(v), |
| Expected: status == pb.TestStatus_PASS, |
| Status: status, |
| Duration: &durpb.Duration{Seconds: int64(i), Nanos: 234567000}, |
| SummaryHtml: "SummaryHtml", |
| } |
| } |
| return trs |
| } |