go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/recorder/create_test_result_test.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 recorder 16 17 import ( 18 "fmt" 19 "testing" 20 "time" 21 22 "github.com/golang/protobuf/proto" 23 . "github.com/smartystreets/goconvey/convey" 24 "google.golang.org/grpc/codes" 25 "google.golang.org/grpc/metadata" 26 "google.golang.org/protobuf/types/known/structpb" 27 28 "go.chromium.org/luci/common/clock" 29 "go.chromium.org/luci/common/clock/testclock" 30 . "go.chromium.org/luci/common/testing/assertions" 31 "go.chromium.org/luci/resultdb/internal/invocations" 32 "go.chromium.org/luci/resultdb/internal/resultcount" 33 "go.chromium.org/luci/resultdb/internal/testresults" 34 "go.chromium.org/luci/resultdb/internal/testutil" 35 "go.chromium.org/luci/resultdb/internal/testutil/insert" 36 "go.chromium.org/luci/resultdb/pbutil" 37 pb "go.chromium.org/luci/resultdb/proto/v1" 38 "go.chromium.org/luci/server/span" 39 ) 40 41 // validCreateTestResultRequest returns a valid CreateTestResultRequest message. 42 func validCreateTestResultRequest(now time.Time, invName, testID string) *pb.CreateTestResultRequest { 43 trName := fmt.Sprintf("invocations/%s/tests/%s/results/result-id-0", invName, testID) 44 properties, err := structpb.NewStruct(map[string]any{ 45 "key_1": "value_1", 46 "key_2": map[string]any{ 47 "child_key": 1, 48 }, 49 }) 50 if err != nil { 51 // Should never fail. 52 panic(err) 53 } 54 55 return &pb.CreateTestResultRequest{ 56 Invocation: invName, 57 RequestId: "request-id-123", 58 59 TestResult: &pb.TestResult{ 60 Name: trName, 61 TestId: testID, 62 ResultId: "result-id-0", 63 Expected: true, 64 Status: pb.TestStatus_PASS, 65 Variant: pbutil.Variant( 66 "a/b", "1", 67 "c", "2", 68 ), 69 TestMetadata: &pb.TestMetadata{ 70 Name: "original_name", 71 Location: &pb.TestLocation{ 72 Repo: "https://chromium.googlesource.com/chromium/src", 73 FileName: "//a_test.go", 74 Line: 54, 75 }, 76 BugComponent: &pb.BugComponent{ 77 System: &pb.BugComponent_Monorail{ 78 Monorail: &pb.MonorailComponent{ 79 Project: "chromium", 80 Value: "Component>Value", 81 }, 82 }, 83 }, 84 }, 85 FailureReason: &pb.FailureReason{ 86 PrimaryErrorMessage: "got 1, want 0", 87 Errors: []*pb.FailureReason_Error{ 88 {Message: "got 1, want 0"}, 89 {Message: "got 2, want 1"}, 90 }, 91 TruncatedErrorsCount: 0, 92 }, 93 Properties: properties, 94 }, 95 } 96 } 97 98 func TestValidateCreateTestResultRequest(t *testing.T) { 99 t.Parallel() 100 101 now := testclock.TestRecentTimeUTC 102 Convey("ValidateCreateTestResultRequest", t, func() { 103 req := validCreateTestResultRequest(now, "invocations/u-build-1", "test-id") 104 105 Convey("suceeeds", func() { 106 So(validateCreateTestResultRequest(req, now), ShouldBeNil) 107 108 Convey("with empty request_id", func() { 109 req.RequestId = "" 110 So(validateCreateTestResultRequest(req, now), ShouldBeNil) 111 }) 112 }) 113 114 Convey("fails with ", func() { 115 Convey(`empty invocation`, func() { 116 req.Invocation = "" 117 err := validateCreateTestResultRequest(req, now) 118 So(err, ShouldErrLike, "invocation: unspecified") 119 }) 120 Convey(`invalid invocation`, func() { 121 req.Invocation = " invalid " 122 err := validateCreateTestResultRequest(req, now) 123 So(err, ShouldErrLike, "invocation: does not match") 124 }) 125 126 Convey(`empty test_result`, func() { 127 req.TestResult = nil 128 err := validateCreateTestResultRequest(req, now) 129 So(err, ShouldErrLike, "test_result: unspecified") 130 }) 131 Convey(`invalid test_result`, func() { 132 req.TestResult.TestId = "" 133 err := validateCreateTestResultRequest(req, now) 134 So(err, ShouldErrLike, "test_result: test_id: unspecified") 135 }) 136 137 Convey("invalid request_id", func() { 138 // non-ascii character 139 req.RequestId = string(rune(244)) 140 err := validateCreateTestResultRequest(req, now) 141 So(err, ShouldErrLike, "request_id: does not match") 142 }) 143 }) 144 }) 145 } 146 147 func TestCreateTestResult(t *testing.T) { 148 Convey(`CreateTestResult`, t, func() { 149 ctx := testutil.SpannerTestContext(t) 150 recorder := newTestRecorderServer() 151 req := validCreateTestResultRequest( 152 clock.Now(ctx).UTC(), "invocations/u-build-1", "test-id", 153 ) 154 155 createTestResult := func(req *pb.CreateTestResultRequest, expectedCommonPrefix string) { 156 expected := proto.Clone(req.TestResult).(*pb.TestResult) 157 expected.Name = "invocations/u-build-1/tests/test-id/results/result-id-0" 158 expected.VariantHash = "c8643f74854d84b4" 159 res, err := recorder.CreateTestResult(ctx, req) 160 So(err, ShouldBeNil) 161 So(res, ShouldResembleProto, expected) 162 163 // double-check it with the database 164 expected.VariantHash = "c8643f74854d84b4" 165 row, err := testresults.Read(span.Single(ctx), res.Name) 166 So(err, ShouldBeNil) 167 So(row, ShouldResembleProto, expected) 168 169 var invCommonTestIdPrefix string 170 err = invocations.ReadColumns(span.Single(ctx), invocations.ID("u-build-1"), map[string]any{"CommonTestIDPrefix": &invCommonTestIdPrefix}) 171 So(err, ShouldBeNil) 172 So(invCommonTestIdPrefix, ShouldEqual, expectedCommonPrefix) 173 } 174 175 // Insert a sample invocation 176 tok, err := generateInvocationToken(ctx, "u-build-1") 177 So(err, ShouldBeNil) 178 ctx = metadata.NewIncomingContext(ctx, metadata.Pairs(pb.UpdateTokenMetadataKey, tok)) 179 invID := invocations.ID("u-build-1") 180 testutil.MustApply(ctx, insert.Invocation(invID, pb.Invocation_ACTIVE, nil)) 181 182 Convey("succeeds", func() { 183 Convey("with a request ID", func() { 184 createTestResult(req, "test-id") 185 186 ctx, cancel := span.ReadOnlyTransaction(ctx) 187 defer cancel() 188 trNum, err := resultcount.ReadTestResultCount(ctx, invocations.NewIDSet("u-build-1")) 189 So(err, ShouldBeNil) 190 So(trNum, ShouldEqual, 1) 191 }) 192 193 Convey("without a request ID", func() { 194 req.RequestId = "" 195 createTestResult(req, "test-id") 196 }) 197 }) 198 199 Convey("fails", func() { 200 Convey("with an invalid request", func() { 201 req.Invocation = "this is an invalid invocation name" 202 _, err := recorder.CreateTestResult(ctx, req) 203 So(err, ShouldHaveAppStatus, codes.InvalidArgument, "bad request: invocation: does not match") 204 }) 205 206 Convey("with an non-existing invocation", func() { 207 tok, err = generateInvocationToken(ctx, "inv") 208 So(err, ShouldBeNil) 209 ctx = metadata.NewIncomingContext(ctx, metadata.Pairs(pb.UpdateTokenMetadataKey, tok)) 210 req.Invocation = "invocations/inv" 211 _, err := recorder.CreateTestResult(ctx, req) 212 So(err, ShouldHaveAppStatus, codes.NotFound, "invocations/inv not found") 213 }) 214 }) 215 }) 216 }