go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/run/runtest/runtest.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 runtest
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"time"
    22  
    23  	"google.golang.org/protobuf/proto"
    24  	"google.golang.org/protobuf/types/known/timestamppb"
    25  
    26  	"go.chromium.org/luci/common/clock"
    27  	"go.chromium.org/luci/server/tq/tqtesting"
    28  
    29  	"go.chromium.org/luci/cv/internal/common"
    30  	"go.chromium.org/luci/cv/internal/common/eventbox"
    31  	"go.chromium.org/luci/cv/internal/cvtesting"
    32  	"go.chromium.org/luci/cv/internal/run"
    33  	"go.chromium.org/luci/cv/internal/run/eventpb"
    34  
    35  	. "github.com/smartystreets/goconvey/convey"
    36  )
    37  
    38  // Runs returns list of runs from tasks for Run Manager.
    39  func Runs(in tqtesting.TaskList) (runs common.RunIDs) {
    40  	for _, t := range in.SortByETA() {
    41  		switch v := t.Payload.(type) {
    42  		case *eventpb.ManageRunTask:
    43  			runs = append(runs, common.RunID(v.GetRunId()))
    44  		case *eventpb.KickManageRunTask:
    45  			runs = append(runs, common.RunID(v.GetRunId()))
    46  		}
    47  	}
    48  	return runs
    49  }
    50  
    51  // SortedRuns returns sorted list of runs from tasks for Run Manager.
    52  func SortedRuns(in tqtesting.TaskList) common.RunIDs {
    53  	runs := Runs(in)
    54  	sort.Sort(runs)
    55  	return runs
    56  }
    57  
    58  // Tasks returns all Run tasks sorted by ETA.
    59  func Tasks(in tqtesting.TaskList) tqtesting.TaskList {
    60  	ret := make(tqtesting.TaskList, 0, len(in))
    61  	for _, t := range in.SortByETA() {
    62  		switch t.Payload.(type) {
    63  		case *eventpb.ManageRunTask, *eventpb.KickManageRunTask:
    64  			ret = append(ret, t)
    65  		}
    66  	}
    67  	return ret
    68  }
    69  
    70  func iterEventBox(ctx context.Context, runID common.RunID, cb func(*eventpb.Event)) {
    71  	events, err := eventbox.List(ctx, run.EventboxRecipient(ctx, runID))
    72  	So(err, ShouldBeNil)
    73  	for _, item := range events {
    74  		evt := &eventpb.Event{}
    75  		So(proto.Unmarshal(item.Value, evt), ShouldBeNil)
    76  		cb(evt)
    77  	}
    78  }
    79  
    80  func matchEventBox(ctx context.Context, runID common.RunID, targets []*eventpb.Event) (matched, remaining []*eventpb.Event) {
    81  	remaining = make([]*eventpb.Event, len(targets))
    82  	copy(remaining, targets)
    83  	iterEventBox(ctx, runID, func(evt *eventpb.Event) {
    84  		for i, r := range remaining {
    85  			if proto.Equal(evt, r) {
    86  				matched = append(matched, r)
    87  				remaining[i] = remaining[len(remaining)-1]
    88  				remaining[len(remaining)-1] = nil
    89  				remaining = remaining[:len(remaining)-1]
    90  				return
    91  			}
    92  		}
    93  	})
    94  	return
    95  }
    96  
    97  // AssertEventboxEmpty asserts the eventbox of the provided Run is empty.
    98  func AssertEventboxEmpty(ctx context.Context, runID common.RunID) {
    99  	iterEventBox(ctx, runID, func(evt *eventpb.Event) {
   100  		So(fmt.Sprintf("%s eventbox received event %s", runID, evt), ShouldBeEmpty)
   101  	})
   102  }
   103  
   104  // AssertNotInEventbox asserts none of the target events exists in the Eventbox.
   105  func AssertNotInEventbox(ctx context.Context, runID common.RunID, targets ...*eventpb.Event) {
   106  	matched, _ := matchEventBox(ctx, runID, targets)
   107  	So(matched, ShouldBeEmpty)
   108  }
   109  
   110  // AssertInEventbox asserts all target events exist in the Eventbox.
   111  func AssertInEventbox(ctx context.Context, runID common.RunID, targets ...*eventpb.Event) {
   112  	_, remaining := matchEventBox(ctx, runID, targets)
   113  	So(remaining, ShouldBeEmpty)
   114  }
   115  
   116  // AssertReceivedStart asserts Run has received Start event.
   117  func AssertReceivedStart(ctx context.Context, runID common.RunID) {
   118  	target := &eventpb.Event{
   119  		Event: &eventpb.Event_Start{
   120  			Start: &eventpb.Start{},
   121  		},
   122  	}
   123  	AssertInEventbox(ctx, runID, target)
   124  }
   125  
   126  // AssertReceivedPoke asserts Run has received Poke event that should be
   127  // processed at `eta`.
   128  //
   129  // If `eta` is not later than now, assert that Poke event should be processed
   130  // immediately.
   131  func AssertReceivedPoke(ctx context.Context, runID common.RunID, eta time.Time) {
   132  	expect := &eventpb.Event{
   133  		Event: &eventpb.Event_Poke{
   134  			Poke: &eventpb.Poke{},
   135  		},
   136  	}
   137  	if eta.After(clock.Now(ctx)) {
   138  		expect.ProcessAfter = timestamppb.New(eta)
   139  	}
   140  	AssertInEventbox(ctx, runID, expect)
   141  }
   142  
   143  // AssertReceivedReadyForSubmission asserts Run has received ReadyForSubmission
   144  // event.
   145  func AssertReceivedReadyForSubmission(ctx context.Context, runID common.RunID, eta time.Time) {
   146  	target := &eventpb.Event{
   147  		Event: &eventpb.Event_ReadyForSubmission{
   148  			ReadyForSubmission: &eventpb.ReadyForSubmission{},
   149  		},
   150  	}
   151  	if !eta.IsZero() {
   152  		target.ProcessAfter = timestamppb.New(eta)
   153  	}
   154  	AssertInEventbox(ctx, runID, target)
   155  }
   156  
   157  // AssertReceivedCLsSubmitted asserts Run has received CLsSubmitted event for
   158  // the provided CL.
   159  func AssertReceivedCLsSubmitted(ctx context.Context, runID common.RunID, clids ...common.CLID) {
   160  	target := &eventpb.Event{
   161  		Event: &eventpb.Event_ClsSubmitted{
   162  			ClsSubmitted: &eventpb.CLsSubmitted{
   163  				Clids: common.CLIDsAsInt64s(common.CLIDs(clids)),
   164  			},
   165  		},
   166  	}
   167  	AssertInEventbox(ctx, runID, target)
   168  }
   169  
   170  // AssertNotReceivedCLsSubmitted asserts Run has NOT received CLsSubmitted event
   171  // for the provided CL.
   172  func AssertNotReceivedCLsSubmitted(ctx context.Context, runID common.RunID, clids ...common.CLID) {
   173  	target := &eventpb.Event{
   174  		Event: &eventpb.Event_ClsSubmitted{
   175  			ClsSubmitted: &eventpb.CLsSubmitted{
   176  				Clids: common.CLIDsAsInt64s(common.CLIDs(clids)),
   177  			},
   178  		},
   179  	}
   180  	AssertNotInEventbox(ctx, runID, target)
   181  }
   182  
   183  // AssertReceivedSubmissionCompleted asserts Run has received the provided
   184  // SubmissionCompleted event.
   185  func AssertReceivedSubmissionCompleted(ctx context.Context, runID common.RunID, sc *eventpb.SubmissionCompleted) {
   186  	target := &eventpb.Event{
   187  		Event: &eventpb.Event_SubmissionCompleted{
   188  			SubmissionCompleted: sc,
   189  		},
   190  	}
   191  	AssertInEventbox(ctx, runID, target)
   192  }
   193  
   194  // AssertReceivedLongOpCompleted asserts Run has received LongOpCompleted event.
   195  func AssertReceivedLongOpCompleted(ctx context.Context, runID common.RunID, result *eventpb.LongOpCompleted) {
   196  	target := &eventpb.Event{
   197  		Event: &eventpb.Event_LongOpCompleted{
   198  			LongOpCompleted: result,
   199  		},
   200  	}
   201  	AssertInEventbox(ctx, runID, target)
   202  }
   203  
   204  // AssertReceivedParentRunCompleted asserts Run has received ParentRunCompleted event.
   205  func AssertReceivedParentRunCompleted(ctx context.Context, runID common.RunID) {
   206  	target := &eventpb.Event{
   207  		Event: &eventpb.Event_ParentRunCompleted{
   208  			ParentRunCompleted: &eventpb.ParentRunCompleted{},
   209  		},
   210  	}
   211  	AssertInEventbox(ctx, runID, target)
   212  }
   213  
   214  // AssertNotReceivedParentRunCompleted asserts Run has not received ParentRunCompleted event.
   215  func AssertNotReceivedParentRunCompleted(ctx context.Context, runID common.RunID) {
   216  	target := &eventpb.Event{
   217  		Event: &eventpb.Event_ParentRunCompleted{
   218  			ParentRunCompleted: &eventpb.ParentRunCompleted{},
   219  		},
   220  	}
   221  	AssertNotInEventbox(ctx, runID, target)
   222  }
   223  
   224  // MockDispatch installs and returns MockDispatcher for Run Manager.
   225  func MockDispatch(ctx context.Context) (context.Context, MockDispatcher) {
   226  	m := MockDispatcher{&cvtesting.DispatchRecorder{}}
   227  	ctx = eventpb.InstallMockDispatcher(ctx, m.Dispatch)
   228  	return ctx, m
   229  }
   230  
   231  // MockDispatcher records in memory what would have resulted in task enqueues
   232  // for a Run Manager.
   233  type MockDispatcher struct {
   234  	*cvtesting.DispatchRecorder
   235  }
   236  
   237  // Runs returns sorted list of Run IDs.
   238  func (m *MockDispatcher) Runs() common.RunIDs {
   239  	return common.MakeRunIDs(m.Targets()...)
   240  }
   241  
   242  // PopRuns returns sorted list of Run IDs and resets the state.
   243  func (m *MockDispatcher) PopRuns() common.RunIDs {
   244  	return common.MakeRunIDs(m.PopTargets()...)
   245  }