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  }