go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/finalizer/finalizer_test.go (about) 1 // Copyright 2020 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 finalizer 16 17 import ( 18 "sort" 19 "testing" 20 "time" 21 22 . "github.com/smartystreets/goconvey/convey" 23 "google.golang.org/protobuf/proto" 24 25 . "go.chromium.org/luci/common/testing/assertions" 26 "go.chromium.org/luci/resultdb/internal/invocations" 27 "go.chromium.org/luci/resultdb/internal/tasks/taskspb" 28 "go.chromium.org/luci/resultdb/internal/testutil" 29 "go.chromium.org/luci/resultdb/internal/testutil/insert" 30 pb "go.chromium.org/luci/resultdb/proto/v1" 31 "go.chromium.org/luci/server/tq" 32 ) 33 34 func TestShouldFinalize(t *testing.T) { 35 Convey(`ShouldFinalize`, t, func() { 36 ctx := testutil.SpannerTestContext(t) 37 38 assertReady := func(invID invocations.ID, expected bool) { 39 should, err := readyToFinalize(ctx, invID) 40 So(err, ShouldBeNil) 41 So(should, ShouldEqual, expected) 42 } 43 44 Convey(`Includes two ACTIVE`, func() { 45 testutil.MustApply(ctx, testutil.CombineMutations( 46 insert.InvocationWithInclusions("a", pb.Invocation_FINALIZING, nil, "b", "c"), 47 insert.InvocationWithInclusions("b", pb.Invocation_ACTIVE, nil), 48 insert.InvocationWithInclusions("c", pb.Invocation_ACTIVE, nil), 49 )...) 50 51 assertReady("a", false) 52 }) 53 54 Convey(`Includes ACTIVE and FINALIZED`, func() { 55 testutil.MustApply(ctx, testutil.CombineMutations( 56 insert.InvocationWithInclusions("a", pb.Invocation_FINALIZING, nil, "b", "c"), 57 insert.InvocationWithInclusions("b", pb.Invocation_ACTIVE, nil), 58 insert.InvocationWithInclusions("c", pb.Invocation_FINALIZED, nil), 59 )...) 60 61 assertReady("a", false) 62 }) 63 64 Convey(`INCLUDES ACTIVE and FINALIZING`, func() { 65 testutil.MustApply(ctx, testutil.CombineMutations( 66 insert.InvocationWithInclusions("a", pb.Invocation_FINALIZING, nil, "b", "c"), 67 insert.InvocationWithInclusions("b", pb.Invocation_ACTIVE, nil), 68 insert.InvocationWithInclusions("c", pb.Invocation_FINALIZING, nil), 69 )...) 70 71 assertReady("a", false) 72 }) 73 74 Convey(`INCLUDES FINALIZING which includes ACTIVE`, func() { 75 testutil.MustApply(ctx, testutil.CombineMutations( 76 insert.InvocationWithInclusions("a", pb.Invocation_FINALIZING, nil, "b"), 77 insert.InvocationWithInclusions("b", pb.Invocation_FINALIZING, nil, "c"), 78 insert.InvocationWithInclusions("c", pb.Invocation_ACTIVE, nil), 79 )...) 80 81 assertReady("a", false) 82 }) 83 84 Convey(`Cycle with one node`, func() { 85 testutil.MustApply(ctx, testutil.CombineMutations( 86 insert.InvocationWithInclusions("a", pb.Invocation_FINALIZING, nil, "a"), 87 )...) 88 89 assertReady("a", true) 90 }) 91 92 Convey(`Cycle with two nodes`, func() { 93 testutil.MustApply(ctx, testutil.CombineMutations( 94 insert.InvocationWithInclusions("a", pb.Invocation_FINALIZING, nil, "b"), 95 insert.InvocationWithInclusions("b", pb.Invocation_FINALIZING, nil, "a"), 96 )...) 97 98 assertReady("a", true) 99 }) 100 }) 101 } 102 103 func TestFinalizeInvocation(t *testing.T) { 104 Convey(`FinalizeInvocation`, t, func() { 105 ctx := testutil.SpannerTestContext(t) 106 ctx, sched := tq.TestingContext(ctx, nil) 107 108 Convey(`Changes the state and finalization time`, func() { 109 testutil.MustApply(ctx, testutil.CombineMutations( 110 insert.InvocationWithInclusions("x", pb.Invocation_FINALIZING, nil), 111 )...) 112 113 err := finalizeInvocation(ctx, "x") 114 So(err, ShouldBeNil) 115 116 var state pb.Invocation_State 117 var finalizeTime time.Time 118 testutil.MustReadRow(ctx, "Invocations", invocations.ID("x").Key(), map[string]any{ 119 "State": &state, 120 "FinalizeTime": &finalizeTime, 121 }) 122 So(state, ShouldEqual, pb.Invocation_FINALIZED) 123 So(finalizeTime, ShouldNotResemble, time.Time{}) 124 }) 125 126 Convey(`Enqueues more finalizing tasks`, func() { 127 testutil.MustApply(ctx, testutil.CombineMutations( 128 insert.InvocationWithInclusions("active", pb.Invocation_ACTIVE, nil, "x"), 129 insert.InvocationWithInclusions("finalizing1", pb.Invocation_FINALIZING, nil, "x"), 130 insert.InvocationWithInclusions("finalizing2", pb.Invocation_FINALIZING, nil, "x"), 131 insert.InvocationWithInclusions("x", pb.Invocation_FINALIZING, nil), 132 )...) 133 134 err := finalizeInvocation(ctx, "x") 135 So(err, ShouldBeNil) 136 137 // Enqueued TQ tasks. 138 invocations := []string{} 139 for _, t := range sched.Tasks().Payloads() { 140 payload, ok := t.(*taskspb.TryFinalizeInvocation) 141 if !ok { 142 continue 143 } 144 invocations = append(invocations, payload.InvocationId) 145 } 146 sort.Strings(invocations) 147 So(invocations, ShouldResemble, []string{"finalizing1", "finalizing2"}) 148 }) 149 150 Convey(`Enqueues a finalization notification and update test metadata tasks`, func() { 151 testutil.MustApply(ctx, 152 insert.Invocation("x", pb.Invocation_FINALIZING, map[string]any{ 153 "Realm": "myproject:myrealm", 154 }), 155 ) 156 157 err := finalizeInvocation(ctx, "x") 158 So(err, ShouldBeNil) 159 160 So(sched.Tasks().Payloads(), ShouldHaveLength, 2) 161 So(sched.Tasks().Payloads()[0], ShouldResembleProto, &taskspb.UpdateTestMetadata{ 162 InvocationId: "x", 163 }) 164 // Enqueued pub/sub notification. 165 So(sched.Tasks().Payloads()[1], ShouldResembleProto, &taskspb.NotifyInvocationFinalized{ 166 Message: &pb.InvocationFinalizedNotification{ 167 Invocation: "invocations/x", 168 Realm: "myproject:myrealm", 169 }, 170 }) 171 }) 172 173 Convey(`Enqueues more bq_export tasks`, func() { 174 bq1, _ := proto.Marshal(&pb.BigQueryExport{ 175 Dataset: "dataset", 176 Project: "project", 177 Table: "table", 178 ResultType: &pb.BigQueryExport_TestResults_{}, 179 }) 180 bq2, _ := proto.Marshal(&pb.BigQueryExport{ 181 Dataset: "dataset", 182 Project: "project2", 183 Table: "table1", 184 ResultType: &pb.BigQueryExport_TextArtifacts_{}, 185 }) 186 testutil.MustApply(ctx, 187 insert.Invocation("x", pb.Invocation_FINALIZING, map[string]any{ 188 "BigQueryExports": [][]byte{bq1, bq2}, 189 }), 190 ) 191 192 err := finalizeInvocation(ctx, "x") 193 So(err, ShouldBeNil) 194 // Enqueued TQ tasks. 195 So(sched.Tasks().Payloads()[0], ShouldResembleProto, 196 &taskspb.ExportInvocationArtifactsToBQ{ 197 InvocationId: "x", 198 BqExport: &pb.BigQueryExport{ 199 Dataset: "dataset", 200 Project: "project2", 201 Table: "table1", 202 ResultType: &pb.BigQueryExport_TextArtifacts_{}, 203 }, 204 }) 205 So(sched.Tasks().Payloads()[1], ShouldResembleProto, 206 &taskspb.ExportInvocationTestResultsToBQ{ 207 InvocationId: "x", 208 BqExport: &pb.BigQueryExport{ 209 Dataset: "dataset", 210 Project: "project", 211 Table: "table", 212 ResultType: &pb.BigQueryExport_TestResults_{}, 213 }, 214 }) 215 }) 216 217 Convey(`Enqueue mark submitted tasks`, func() { 218 testutil.MustApply(ctx, testutil.CombineMutations( 219 insert.InvocationWithInclusions("x", pb.Invocation_FINALIZING, map[string]any{ 220 "Submitted": true, 221 }), 222 )...) 223 224 err := finalizeInvocation(ctx, "x") 225 So(err, ShouldBeNil) 226 // there should be two tasks ahead, test metadata and notify finalized. 227 So(sched.Tasks().Payloads()[0], ShouldResembleProto, 228 &taskspb.MarkInvocationSubmitted{ 229 InvocationId: "x", 230 }) 231 }) 232 }) 233 }