go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/testutil/insert/insert.go (about)

     1  // Copyright 2019 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 insert implements functions to insert rows for testing purposes.
    16  package insert
    17  
    18  import (
    19  	"fmt"
    20  	"strconv"
    21  	"time"
    22  
    23  	"cloud.google.com/go/spanner"
    24  	. "github.com/smartystreets/goconvey/convey"
    25  	"google.golang.org/protobuf/proto"
    26  	durpb "google.golang.org/protobuf/types/known/durationpb"
    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/pbutil"
    32  	pb "go.chromium.org/luci/resultdb/proto/v1"
    33  )
    34  
    35  // TestRealm is the default realm used for invocation mutations returned by Invocation().
    36  const TestRealm = "testproject:testrealm"
    37  
    38  func updateDict(dest, source map[string]any) {
    39  	for k, v := range source {
    40  		dest[k] = v
    41  	}
    42  }
    43  
    44  // Invocation returns a spanner mutation that inserts an invocation.
    45  func Invocation(id invocations.ID, state pb.Invocation_State, extraValues map[string]any) *spanner.Mutation {
    46  	future := time.Date(2050, 1, 1, 0, 0, 0, 0, time.UTC)
    47  	values := map[string]any{
    48  		"InvocationId":                      id,
    49  		"ShardId":                           0,
    50  		"State":                             state,
    51  		"Realm":                             TestRealm,
    52  		"InvocationExpirationTime":          future,
    53  		"ExpectedTestResultsExpirationTime": future,
    54  		"CreateTime":                        spanner.CommitTimestamp,
    55  		"Deadline":                          future,
    56  	}
    57  
    58  	if state == pb.Invocation_FINALIZED {
    59  		values["FinalizeTime"] = spanner.CommitTimestamp
    60  	}
    61  	updateDict(values, extraValues)
    62  	return spanutil.InsertMap("Invocations", values)
    63  }
    64  
    65  // FinalizedInvocationWithInclusions returns mutations to insert a finalized invocation with inclusions.
    66  func FinalizedInvocationWithInclusions(id invocations.ID, extraValues map[string]any, included ...invocations.ID) []*spanner.Mutation {
    67  	return InvocationWithInclusions(id, pb.Invocation_FINALIZED, extraValues, included...)
    68  }
    69  
    70  // InvocationWithInclusions returns mutations to insert an invocation with inclusions.
    71  func InvocationWithInclusions(id invocations.ID, state pb.Invocation_State, extraValues map[string]any, included ...invocations.ID) []*spanner.Mutation {
    72  	ms := []*spanner.Mutation{Invocation(id, state, extraValues)}
    73  	for _, incl := range included {
    74  		ms = append(ms, Inclusion(id, incl))
    75  	}
    76  	return ms
    77  }
    78  
    79  // Inclusion returns a spanner mutation that inserts an inclusion.
    80  func Inclusion(including, included invocations.ID) *spanner.Mutation {
    81  	return spanutil.InsertMap("IncludedInvocations", map[string]any{
    82  		"InvocationId":         including,
    83  		"IncludedInvocationId": included,
    84  	})
    85  }
    86  
    87  // TestResults returns spanner mutations to insert test results
    88  func TestResults(invID, testID string, v *pb.Variant, statuses ...pb.TestStatus) []*spanner.Mutation {
    89  	return TestResultMessages(MakeTestResults(invID, testID, v, statuses...))
    90  }
    91  
    92  // TestResultMessages returns spanner mutations to insert test results
    93  func TestResultMessages(trs []*pb.TestResult) []*spanner.Mutation {
    94  	ms := make([]*spanner.Mutation, len(trs))
    95  	for i, tr := range trs {
    96  		invID, testID, resultID, err := pbutil.ParseTestResultName(tr.Name)
    97  		So(err, ShouldBeNil)
    98  		mutMap := map[string]any{
    99  			"InvocationId":    invocations.ID(invID),
   100  			"TestId":          testID,
   101  			"ResultId":        resultID,
   102  			"Variant":         trs[i].Variant,
   103  			"VariantHash":     pbutil.VariantHash(trs[i].Variant),
   104  			"CommitTimestamp": spanner.CommitTimestamp,
   105  			"Status":          tr.Status,
   106  			"RunDurationUsec": 1e6*i + 234567,
   107  			"SummaryHtml":     spanutil.Compressed("SummaryHtml"),
   108  		}
   109  		if tr.SkipReason != pb.SkipReason_SKIP_REASON_UNSPECIFIED {
   110  			mutMap["SkipReason"] = tr.SkipReason
   111  		}
   112  		if !trs[i].Expected {
   113  			mutMap["IsUnexpected"] = true
   114  		}
   115  		if tr.TestMetadata != nil {
   116  			tmdBytes, err := proto.Marshal(tr.TestMetadata)
   117  			So(err, ShouldBeNil)
   118  			mutMap["TestMetadata"] = spanutil.Compressed(tmdBytes)
   119  		}
   120  		if tr.FailureReason != nil {
   121  			frBytes, err := proto.Marshal(tr.FailureReason)
   122  			So(err, ShouldBeNil)
   123  			mutMap["FailureReason"] = spanutil.Compressed(frBytes)
   124  		}
   125  		if tr.Properties != nil {
   126  			propertiesBytes, err := proto.Marshal(tr.Properties)
   127  			So(err, ShouldBeNil)
   128  			mutMap["Properties"] = spanutil.Compressed(propertiesBytes)
   129  		}
   130  
   131  		ms[i] = spanutil.InsertMap("TestResults", mutMap)
   132  	}
   133  	return ms
   134  }
   135  
   136  // TestExonerations returns Spanner mutations to insert test exonerations.
   137  func TestExonerations(invID invocations.ID, testID string, variant *pb.Variant, reasons ...pb.ExonerationReason) []*spanner.Mutation {
   138  	ms := make([]*spanner.Mutation, len(reasons))
   139  	for i := 0; i < len(reasons); i++ {
   140  		ms[i] = spanutil.InsertMap("TestExonerations", map[string]any{
   141  			"InvocationId":    invID,
   142  			"TestId":          testID,
   143  			"ExonerationId":   strconv.Itoa(i),
   144  			"Variant":         variant,
   145  			"VariantHash":     pbutil.VariantHash(variant),
   146  			"ExplanationHTML": spanutil.Compressed(fmt.Sprintf("explanation %d", i)),
   147  			"Reason":          reasons[i],
   148  		})
   149  	}
   150  	return ms
   151  }
   152  
   153  // Artifact returns a Spanner mutation to insert an artifact.
   154  func Artifact(invID invocations.ID, parentID, artID string, extraValues map[string]any) *spanner.Mutation {
   155  	values := map[string]any{
   156  		"InvocationId": invID,
   157  		"ParentID":     parentID,
   158  		"ArtifactId":   artID,
   159  	}
   160  	updateDict(values, extraValues)
   161  	return spanutil.InsertMap("Artifacts", values)
   162  }
   163  
   164  func TestMetadataRows(rows []*testmetadata.TestMetadataRow) []*spanner.Mutation {
   165  	ms := make([]*spanner.Mutation, len(rows))
   166  	for i, row := range rows {
   167  		mutMap := map[string]any{
   168  			"Project":      row.Project,
   169  			"TestId":       row.TestID,
   170  			"SubRealm":     row.SubRealm,
   171  			"RefHash":      row.RefHash,
   172  			"LastUpdated":  row.LastUpdated,
   173  			"TestMetadata": spanutil.Compressed(pbutil.MustMarshal(row.TestMetadata)),
   174  			"SourceRef":    spanutil.Compressed(pbutil.MustMarshal(row.SourceRef)),
   175  			"Position":     int64(row.Position),
   176  		}
   177  		ms[i] = spanutil.InsertMap("TestMetadata", mutMap)
   178  	}
   179  	return ms
   180  }
   181  
   182  func MakeTestMetadataRow(project, testID, subRealm string, refHash []byte) *testmetadata.TestMetadataRow {
   183  	return &testmetadata.TestMetadataRow{
   184  		Project:     project,
   185  		TestID:      testID,
   186  		RefHash:     refHash,
   187  		SubRealm:    subRealm,
   188  		LastUpdated: time.Time{},
   189  		TestMetadata: &pb.TestMetadata{
   190  			Name: project + "\n" + testID + "\n" + subRealm + "\n" + string(refHash),
   191  			Location: &pb.TestLocation{
   192  				Repo:     "testRepo",
   193  				FileName: "testFile",
   194  				Line:     0,
   195  			},
   196  			BugComponent: &pb.BugComponent{},
   197  		},
   198  		SourceRef: &pb.SourceRef{
   199  			System: &pb.SourceRef_Gitiles{
   200  				Gitiles: &pb.GitilesRef{Host: "testHost"},
   201  			},
   202  		},
   203  		Position: 0,
   204  	}
   205  }
   206  
   207  // MakeTestResults creates test results.
   208  func MakeTestResults(invID, testID string, v *pb.Variant, statuses ...pb.TestStatus) []*pb.TestResult {
   209  	trs := make([]*pb.TestResult, len(statuses))
   210  	for i, status := range statuses {
   211  		resultID := fmt.Sprintf("%d", i)
   212  		trs[i] = &pb.TestResult{
   213  			Name:        pbutil.TestResultName(invID, testID, resultID),
   214  			TestId:      testID,
   215  			ResultId:    resultID,
   216  			Variant:     v,
   217  			VariantHash: pbutil.VariantHash(v),
   218  			Expected:    status == pb.TestStatus_PASS,
   219  			Status:      status,
   220  			Duration:    &durpb.Duration{Seconds: int64(i), Nanos: 234567000},
   221  			SummaryHtml: "SummaryHtml",
   222  		}
   223  	}
   224  	return trs
   225  }