go.uber.org/cadence@v1.2.9/internal/internal_workers_test.go (about)

     1  // Copyright (c) 2017-2020 Uber Technologies Inc.
     2  // Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc.
     3  //
     4  // Permission is hereby granted, free of charge, to any person obtaining a copy
     5  // of this software and associated documentation files (the "Software"), to deal
     6  // in the Software without restriction, including without limitation the rights
     7  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  // copies of the Software, and to permit persons to whom the Software is
     9  // furnished to do so, subject to the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be included in
    12  // all copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20  // THE SOFTWARE.
    21  
    22  package internal
    23  
    24  import (
    25  	"context"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/pborman/uuid"
    31  	"github.com/stretchr/testify/suite"
    32  	"go.uber.org/atomic"
    33  	"go.uber.org/yarpc"
    34  	"go.uber.org/zap"
    35  	"go.uber.org/zap/zaptest"
    36  
    37  	"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
    38  	m "go.uber.org/cadence/.gen/go/shared"
    39  	"go.uber.org/cadence/internal/common"
    40  )
    41  
    42  // ActivityTaskHandler never returns response
    43  type noResponseActivityTaskHandler struct {
    44  	isExecuteCalled chan struct{}
    45  }
    46  
    47  func newNoResponseActivityTaskHandler() *noResponseActivityTaskHandler {
    48  	return &noResponseActivityTaskHandler{isExecuteCalled: make(chan struct{})}
    49  }
    50  
    51  func (ath noResponseActivityTaskHandler) Execute(taskList string, task *m.PollForActivityTaskResponse) (interface{}, error) {
    52  	close(ath.isExecuteCalled)
    53  	c := make(chan struct{})
    54  	<-c
    55  	return nil, nil
    56  }
    57  
    58  func (ath noResponseActivityTaskHandler) BlockedOnExecuteCalled() error {
    59  	<-ath.isExecuteCalled
    60  	return nil
    61  }
    62  
    63  type (
    64  	WorkersTestSuite struct {
    65  		suite.Suite
    66  		mockCtrl *gomock.Controller
    67  		service  *workflowservicetest.MockClient
    68  	}
    69  )
    70  
    71  // Test suite.
    72  func (s *WorkersTestSuite) SetupTest() {
    73  	s.mockCtrl = gomock.NewController(s.T())
    74  	s.service = workflowservicetest.NewMockClient(s.mockCtrl)
    75  }
    76  
    77  func (s *WorkersTestSuite) TearDownTest() {
    78  	s.mockCtrl.Finish() // assert mock’s expectations
    79  }
    80  
    81  func TestWorkersTestSuite(t *testing.T) {
    82  	suite.Run(t, new(WorkersTestSuite))
    83  }
    84  
    85  func (s *WorkersTestSuite) TestWorkflowWorker() {
    86  	domain := "testDomain"
    87  	logger, _ := zap.NewDevelopment()
    88  
    89  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil)
    90  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, nil).AnyTimes()
    91  	s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
    92  
    93  	ctx, cancel := context.WithCancel(context.Background())
    94  	executionParameters := workerExecutionParameters{
    95  		TaskList: "testTaskList",
    96  		WorkerOptions: WorkerOptions{
    97  			MaxConcurrentDecisionTaskPollers: 5,
    98  			Logger:                           logger},
    99  		UserContext:       ctx,
   100  		UserContextCancel: cancel,
   101  	}
   102  	overrides := &workerOverrides{workflowTaskHandler: newSampleWorkflowTaskHandler()}
   103  	workflowWorker := newWorkflowWorkerInternal(
   104  		s.service, domain, executionParameters, nil, overrides, newRegistry(), nil,
   105  	)
   106  	workflowWorker.Start()
   107  	workflowWorker.Stop()
   108  
   109  	s.Nil(ctx.Err())
   110  }
   111  
   112  func (s *WorkersTestSuite) TestActivityWorker() {
   113  	s.testActivityWorker(false)
   114  }
   115  
   116  func (s *WorkersTestSuite) TestActivityWorkerWithLocalActivityDispatch() {
   117  	s.testActivityWorker(true)
   118  }
   119  
   120  func (s *WorkersTestSuite) testActivityWorker(useLocallyDispatched bool) {
   121  	domain := "testDomain"
   122  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil)
   123  	s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForActivityTaskResponse{}, nil).AnyTimes()
   124  	s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).Return(nil).AnyTimes()
   125  
   126  	executionParameters := workerExecutionParameters{
   127  		TaskList: "testTaskList",
   128  		WorkerOptions: WorkerOptions{
   129  			MaxConcurrentActivityTaskPollers: 5,
   130  			Logger:                           zaptest.NewLogger(s.T())},
   131  	}
   132  	overrides := &workerOverrides{activityTaskHandler: newSampleActivityTaskHandler(), useLocallyDispatchedActivityPoller: useLocallyDispatched}
   133  	a := &greeterActivity{}
   134  	registry := newRegistry()
   135  	registry.addActivityWithLock(a.ActivityType().Name, a)
   136  	activityWorker := newActivityWorker(
   137  		s.service, domain, executionParameters, overrides, registry, nil,
   138  	)
   139  	activityWorker.Start()
   140  	activityWorker.Stop()
   141  }
   142  
   143  func (s *WorkersTestSuite) TestActivityWorkerStop() {
   144  	domain := "testDomain"
   145  
   146  	pats := &m.PollForActivityTaskResponse{
   147  		TaskToken: []byte("token"),
   148  		WorkflowExecution: &m.WorkflowExecution{
   149  			WorkflowId: common.StringPtr("wID"),
   150  			RunId:      common.StringPtr("rID")},
   151  		ActivityType:                    &m.ActivityType{Name: common.StringPtr("test")},
   152  		ActivityId:                      common.StringPtr(uuid.New()),
   153  		ScheduledTimestamp:              common.Int64Ptr(time.Now().UnixNano()),
   154  		ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()),
   155  		ScheduleToCloseTimeoutSeconds:   common.Int32Ptr(1),
   156  		StartedTimestamp:                common.Int64Ptr(time.Now().UnixNano()),
   157  		StartToCloseTimeoutSeconds:      common.Int32Ptr(1),
   158  		WorkflowType: &m.WorkflowType{
   159  			Name: common.StringPtr("wType"),
   160  		},
   161  		WorkflowDomain: common.StringPtr("domain"),
   162  	}
   163  
   164  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil)
   165  	s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(pats, nil).AnyTimes()
   166  	s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).Return(nil).AnyTimes()
   167  
   168  	stopC := make(chan struct{})
   169  	ctx, cancel := context.WithCancel(context.Background())
   170  	executionParameters := workerExecutionParameters{
   171  		TaskList: "testTaskList",
   172  		WorkerOptions: AugmentWorkerOptions(
   173  			WorkerOptions{
   174  				MaxConcurrentActivityTaskPollers:   5,
   175  				MaxConcurrentActivityExecutionSize: 2,
   176  				Logger:                             zaptest.NewLogger(s.T()),
   177  			},
   178  		),
   179  		UserContext:       ctx,
   180  		UserContextCancel: cancel,
   181  		WorkerStopTimeout: time.Second * 2,
   182  		WorkerStopChannel: stopC,
   183  	}
   184  	activityTaskHandler := newNoResponseActivityTaskHandler()
   185  	overrides := &workerOverrides{activityTaskHandler: activityTaskHandler}
   186  	a := &greeterActivity{}
   187  	registry := newRegistry()
   188  	registry.addActivityWithLock(a.ActivityType().Name, a)
   189  	worker := newActivityWorker(
   190  		s.service, domain, executionParameters, overrides, registry, nil,
   191  	)
   192  	worker.Start()
   193  	activityTaskHandler.BlockedOnExecuteCalled()
   194  	go worker.Stop()
   195  
   196  	<-worker.worker.shutdownCh
   197  	err := ctx.Err()
   198  	s.NoError(err)
   199  
   200  	<-ctx.Done()
   201  	err = ctx.Err()
   202  	s.Error(err)
   203  }
   204  
   205  func (s *WorkersTestSuite) TestPollForDecisionTask_InternalServiceError() {
   206  	domain := "testDomain"
   207  
   208  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil)
   209  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes()
   210  
   211  	executionParameters := workerExecutionParameters{
   212  		TaskList: "testDecisionTaskList",
   213  		WorkerOptions: WorkerOptions{
   214  			MaxConcurrentDecisionTaskPollers: 5,
   215  			Logger:                           zaptest.NewLogger(s.T())},
   216  	}
   217  	overrides := &workerOverrides{workflowTaskHandler: newSampleWorkflowTaskHandler()}
   218  	workflowWorker := newWorkflowWorkerInternal(
   219  		s.service, domain, executionParameters, nil, overrides, newRegistry(), nil,
   220  	)
   221  	workflowWorker.Start()
   222  	workflowWorker.Stop()
   223  }
   224  
   225  func (s *WorkersTestSuite) TestLongRunningDecisionTask() {
   226  	localActivityCalledCount := 0
   227  	localActivitySleep := func(duration time.Duration) error {
   228  		time.Sleep(duration)
   229  		localActivityCalledCount++
   230  		return nil
   231  	}
   232  
   233  	doneCh := make(chan struct{})
   234  
   235  	isWorkflowCompleted := false
   236  	longDecisionWorkflowFn := func(ctx Context, input []byte) error {
   237  		lao := LocalActivityOptions{
   238  			ScheduleToCloseTimeout: time.Second * 2,
   239  		}
   240  		ctx = WithLocalActivityOptions(ctx, lao)
   241  		err := ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil)
   242  
   243  		if err != nil {
   244  			return err
   245  		}
   246  
   247  		err = ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil)
   248  		isWorkflowCompleted = true
   249  		return err
   250  	}
   251  
   252  	domain := "testDomain"
   253  	taskList := "long-running-decision-tl"
   254  	testEvents := []*m.HistoryEvent{
   255  		{
   256  			EventId:   common.Int64Ptr(1),
   257  			EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted),
   258  			WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{
   259  				TaskList:                            &m.TaskList{Name: &taskList},
   260  				ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10),
   261  				TaskStartToCloseTimeoutSeconds:      common.Int32Ptr(2),
   262  				WorkflowType:                        &m.WorkflowType{Name: common.StringPtr("long-running-decision-workflow-type")},
   263  			},
   264  		},
   265  		createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   266  		createTestEventDecisionTaskStarted(3),
   267  		createTestEventDecisionTaskCompleted(4, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}),
   268  		{
   269  			EventId:   common.Int64Ptr(5),
   270  			EventType: common.EventTypePtr(m.EventTypeMarkerRecorded),
   271  			MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{
   272  				MarkerName:                   common.StringPtr(localActivityMarkerName),
   273  				Details:                      s.createLocalActivityMarkerDataForTest("0"),
   274  				DecisionTaskCompletedEventId: common.Int64Ptr(4),
   275  			},
   276  		},
   277  		createTestEventDecisionTaskScheduled(6, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   278  		createTestEventDecisionTaskStarted(7),
   279  		createTestEventDecisionTaskCompleted(8, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}),
   280  		{
   281  			EventId:   common.Int64Ptr(9),
   282  			EventType: common.EventTypePtr(m.EventTypeMarkerRecorded),
   283  			MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{
   284  				MarkerName:                   common.StringPtr(localActivityMarkerName),
   285  				Details:                      s.createLocalActivityMarkerDataForTest("1"),
   286  				DecisionTaskCompletedEventId: common.Int64Ptr(8),
   287  			},
   288  		},
   289  		createTestEventDecisionTaskScheduled(10, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   290  		createTestEventDecisionTaskStarted(11),
   291  	}
   292  
   293  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   294  	task := &m.PollForDecisionTaskResponse{
   295  		TaskToken: []byte("test-token"),
   296  		WorkflowExecution: &m.WorkflowExecution{
   297  			WorkflowId: common.StringPtr("long-running-decision-workflow-id"),
   298  			RunId:      common.StringPtr("long-running-decision-workflow-run-id"),
   299  		},
   300  		WorkflowType: &m.WorkflowType{
   301  			Name: common.StringPtr("long-running-decision-workflow-type"),
   302  		},
   303  		PreviousStartedEventId: common.Int64Ptr(0),
   304  		StartedEventId:         common.Int64Ptr(3),
   305  		History:                &m.History{Events: testEvents[0:3]},
   306  		NextPageToken:          nil,
   307  		NextEventId:            common.Int64Ptr(4),
   308  	}
   309  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1)
   310  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes()
   311  
   312  	respondCounter := 0
   313  	s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption,
   314  	) (success *m.RespondDecisionTaskCompletedResponse, err error) {
   315  		respondCounter++
   316  		switch respondCounter {
   317  		case 1:
   318  			s.Equal(1, len(request.Decisions))
   319  			s.Equal(m.DecisionTypeRecordMarker, request.Decisions[0].GetDecisionType())
   320  			*task.PreviousStartedEventId = 3
   321  			*task.StartedEventId = 7
   322  			task.History.Events = testEvents[3:7]
   323  			return &m.RespondDecisionTaskCompletedResponse{DecisionTask: task}, nil
   324  		case 2:
   325  			s.Equal(2, len(request.Decisions))
   326  			s.Equal(m.DecisionTypeRecordMarker, request.Decisions[0].GetDecisionType())
   327  			s.Equal(m.DecisionTypeCompleteWorkflowExecution, request.Decisions[1].GetDecisionType())
   328  			*task.PreviousStartedEventId = 7
   329  			*task.StartedEventId = 11
   330  			task.History.Events = testEvents[7:11]
   331  			close(doneCh)
   332  			return nil, nil
   333  		default:
   334  			panic("unexpected RespondDecisionTaskCompleted")
   335  		}
   336  	}).Times(2)
   337  
   338  	options := WorkerOptions{
   339  		Logger:                zaptest.NewLogger(s.T()),
   340  		DisableActivityWorker: true,
   341  		Identity:              "test-worker-identity",
   342  	}
   343  	worker := newAggregatedWorker(s.service, domain, taskList, options)
   344  	worker.RegisterWorkflowWithOptions(
   345  		longDecisionWorkflowFn,
   346  		RegisterWorkflowOptions{Name: "long-running-decision-workflow-type"},
   347  	)
   348  	worker.RegisterActivity(localActivitySleep)
   349  
   350  	startWorkerAndWait(s, worker, &doneCh)
   351  
   352  	s.True(isWorkflowCompleted)
   353  	s.Equal(2, localActivityCalledCount)
   354  }
   355  
   356  func (s *WorkersTestSuite) TestQueryTask_WorkflowCacheEvicted() {
   357  	domain := "testDomain"
   358  	taskList := "query-task-cache-evicted-tl"
   359  	workflowType := "query-task-cache-evicted-workflow"
   360  	workflowID := "query-task-cache-evicted-workflow-id"
   361  	runID := "query-task-cache-evicted-workflow-run-id"
   362  	activityType := "query-task-cache-evicted-activity"
   363  	queryType := "state"
   364  	doneCh := make(chan struct{})
   365  
   366  	activityFn := func(ctx context.Context) error {
   367  		return nil
   368  	}
   369  
   370  	queryWorkflowFn := func(ctx Context) error {
   371  		queryResult := "started"
   372  		// setup query handler for query type "state"
   373  		if err := SetQueryHandler(ctx, queryType, func(input []byte) (string, error) {
   374  			return queryResult, nil
   375  		}); err != nil {
   376  			return err
   377  		}
   378  
   379  		queryResult = "waiting on timer"
   380  		NewTimer(ctx, time.Minute*2).Get(ctx, nil)
   381  
   382  		queryResult = "waiting on activity"
   383  		ctx = WithActivityOptions(ctx, ActivityOptions{
   384  			ScheduleToStartTimeout: 10 * time.Second,
   385  			StartToCloseTimeout:    10 * time.Second,
   386  		})
   387  		if err := ExecuteActivity(ctx, activityFn).Get(ctx, nil); err != nil {
   388  			return err
   389  		}
   390  		queryResult = "done"
   391  		return nil
   392  	}
   393  
   394  	testEvents := []*m.HistoryEvent{
   395  		{
   396  			EventId:   common.Int64Ptr(1),
   397  			EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted),
   398  			WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{
   399  				TaskList:                            &m.TaskList{Name: &taskList},
   400  				ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(180),
   401  				TaskStartToCloseTimeoutSeconds:      common.Int32Ptr(2),
   402  				WorkflowType:                        &m.WorkflowType{Name: common.StringPtr(workflowType)},
   403  			},
   404  		},
   405  		createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   406  		createTestEventDecisionTaskStarted(3),
   407  		createTestEventDecisionTaskCompleted(4, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}),
   408  		{
   409  			EventId:   common.Int64Ptr(5),
   410  			EventType: common.EventTypePtr(m.EventTypeTimerStarted),
   411  			TimerStartedEventAttributes: &m.TimerStartedEventAttributes{
   412  				TimerId:                      common.StringPtr("0"),
   413  				StartToFireTimeoutSeconds:    common.Int64Ptr(120),
   414  				DecisionTaskCompletedEventId: common.Int64Ptr(4),
   415  			},
   416  		},
   417  		{
   418  			EventId:   common.Int64Ptr(6),
   419  			EventType: common.EventTypePtr(m.EventTypeTimerFired),
   420  			TimerFiredEventAttributes: &m.TimerFiredEventAttributes{
   421  				TimerId:        common.StringPtr("0"),
   422  				StartedEventId: common.Int64Ptr(5),
   423  			},
   424  		},
   425  		createTestEventDecisionTaskScheduled(7, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   426  		createTestEventDecisionTaskStarted(8),
   427  		createTestEventDecisionTaskCompleted(9, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}),
   428  		createTestEventActivityTaskScheduled(10, &m.ActivityTaskScheduledEventAttributes{
   429  			ActivityId: common.StringPtr("1"),
   430  			ActivityType: &m.ActivityType{
   431  				Name: common.StringPtr(activityType),
   432  			},
   433  			Domain: common.StringPtr(domain),
   434  			TaskList: &m.TaskList{
   435  				Name: common.StringPtr(taskList),
   436  			},
   437  			ScheduleToStartTimeoutSeconds: common.Int32Ptr(10),
   438  			StartToCloseTimeoutSeconds:    common.Int32Ptr(10),
   439  			DecisionTaskCompletedEventId:  common.Int64Ptr(9),
   440  		}),
   441  	}
   442  
   443  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   444  	task := &m.PollForDecisionTaskResponse{
   445  		TaskToken: []byte("test-token"),
   446  		WorkflowExecution: &m.WorkflowExecution{
   447  			WorkflowId: common.StringPtr(workflowID),
   448  			RunId:      common.StringPtr(runID),
   449  		},
   450  		WorkflowType: &m.WorkflowType{
   451  			Name: common.StringPtr(workflowType),
   452  		},
   453  		PreviousStartedEventId: common.Int64Ptr(0),
   454  		StartedEventId:         common.Int64Ptr(3),
   455  		History:                &m.History{Events: testEvents[0:3]},
   456  		NextPageToken:          nil,
   457  		NextEventId:            common.Int64Ptr(4),
   458  	}
   459  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1)
   460  	s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption,
   461  	) (success *m.RespondDecisionTaskCompletedResponse, err error) {
   462  		s.Equal(1, len(request.Decisions))
   463  		s.Equal(m.DecisionTypeStartTimer, request.Decisions[0].GetDecisionType())
   464  		return &m.RespondDecisionTaskCompletedResponse{}, nil
   465  	}).Times(1)
   466  	queryTask := &m.PollForDecisionTaskResponse{
   467  		TaskToken: []byte("test-token"),
   468  		WorkflowExecution: &m.WorkflowExecution{
   469  			WorkflowId: common.StringPtr(workflowID),
   470  			RunId:      common.StringPtr(runID),
   471  		},
   472  		WorkflowType: &m.WorkflowType{
   473  			Name: common.StringPtr(workflowType),
   474  		},
   475  		PreviousStartedEventId: common.Int64Ptr(3),
   476  		History:                &m.History{}, // sticky query, so there's no history
   477  		NextPageToken:          nil,
   478  		NextEventId:            common.Int64Ptr(5),
   479  		Query: &m.WorkflowQuery{
   480  			QueryType: common.StringPtr(queryType),
   481  		},
   482  	}
   483  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.PollForDecisionTaskRequest, opts ...yarpc.CallOption,
   484  	) (success *m.PollForDecisionTaskResponse, err error) {
   485  		getWorkflowCache().Delete(runID) // force remove the workflow state
   486  		return queryTask, nil
   487  	}).Times(1)
   488  	s.service.EXPECT().ResetStickyTaskList(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.ResetStickyTaskListResponse{}, nil).AnyTimes()
   489  	s.service.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.GetWorkflowExecutionHistoryResponse{
   490  		History: &m.History{Events: testEvents}, // workflow has made progress, return all available events
   491  	}, nil).Times(1)
   492  	dc := getDefaultDataConverter()
   493  	expectedResult, err := dc.ToData("waiting on timer")
   494  	s.NoError(err)
   495  	s.service.EXPECT().RespondQueryTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondQueryTaskCompletedRequest, opts ...yarpc.CallOption) error {
   496  		s.Equal(m.QueryTaskCompletedTypeCompleted, request.GetCompletedType())
   497  		s.Equal(expectedResult, request.GetQueryResult())
   498  		close(doneCh)
   499  		return nil
   500  	}).Times(1)
   501  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes()
   502  
   503  	options := WorkerOptions{
   504  		Logger:                zaptest.NewLogger(s.T()),
   505  		DisableActivityWorker: true,
   506  		Identity:              "test-worker-identity",
   507  		DataConverter:         dc,
   508  		// set concurrent decision task execution to 1,
   509  		// otherwise query task may be polled and start execution
   510  		// before decision task put created workflowContext into the cache,
   511  		// resulting in a cache hit for query
   512  		// by setting concurrent execution size to 1, we ensure when polling
   513  		// query task, cache already contains the workflowContext for this workflow,
   514  		// and we can force clear the cache when polling the query task.
   515  		// See the mock function for the second PollForDecisionTask call above.
   516  		MaxConcurrentDecisionTaskExecutionSize: 1,
   517  	}
   518  	worker := newAggregatedWorker(s.service, domain, taskList, options)
   519  	worker.RegisterWorkflowWithOptions(
   520  		queryWorkflowFn,
   521  		RegisterWorkflowOptions{Name: workflowType},
   522  	)
   523  	worker.RegisterActivityWithOptions(
   524  		activityFn,
   525  		RegisterActivityOptions{Name: activityType},
   526  	)
   527  
   528  	startWorkerAndWait(s, worker, &doneCh)
   529  }
   530  
   531  func (s *WorkersTestSuite) TestMultipleLocalActivities() {
   532  	localActivityCalledCount := 0
   533  	localActivitySleep := func(duration time.Duration) error {
   534  		time.Sleep(duration)
   535  		localActivityCalledCount++
   536  		return nil
   537  	}
   538  
   539  	doneCh := make(chan struct{})
   540  
   541  	isWorkflowCompleted := false
   542  	longDecisionWorkflowFn := func(ctx Context, input []byte) error {
   543  		lao := LocalActivityOptions{
   544  			ScheduleToCloseTimeout: time.Second * 2,
   545  		}
   546  		ctx = WithLocalActivityOptions(ctx, lao)
   547  		err := ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil)
   548  
   549  		if err != nil {
   550  			return err
   551  		}
   552  
   553  		err = ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil)
   554  		isWorkflowCompleted = true
   555  		return err
   556  	}
   557  
   558  	domain := "testDomain"
   559  	taskList := "multiple-local-activities-tl"
   560  	testEvents := []*m.HistoryEvent{
   561  		{
   562  			EventId:   common.Int64Ptr(1),
   563  			EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted),
   564  			WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{
   565  				TaskList:                            &m.TaskList{Name: &taskList},
   566  				ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10),
   567  				TaskStartToCloseTimeoutSeconds:      common.Int32Ptr(3),
   568  				WorkflowType:                        &m.WorkflowType{Name: common.StringPtr("multiple-local-activities-workflow-type")},
   569  			},
   570  		},
   571  		createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   572  		createTestEventDecisionTaskStarted(3),
   573  		createTestEventDecisionTaskCompleted(4, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}),
   574  		{
   575  			EventId:   common.Int64Ptr(5),
   576  			EventType: common.EventTypePtr(m.EventTypeMarkerRecorded),
   577  			MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{
   578  				MarkerName:                   common.StringPtr(localActivityMarkerName),
   579  				Details:                      s.createLocalActivityMarkerDataForTest("0"),
   580  				DecisionTaskCompletedEventId: common.Int64Ptr(4),
   581  			},
   582  		},
   583  		createTestEventDecisionTaskScheduled(6, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   584  		createTestEventDecisionTaskStarted(7),
   585  		createTestEventDecisionTaskCompleted(8, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}),
   586  		{
   587  			EventId:   common.Int64Ptr(9),
   588  			EventType: common.EventTypePtr(m.EventTypeMarkerRecorded),
   589  			MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{
   590  				MarkerName:                   common.StringPtr(localActivityMarkerName),
   591  				Details:                      s.createLocalActivityMarkerDataForTest("1"),
   592  				DecisionTaskCompletedEventId: common.Int64Ptr(8),
   593  			},
   594  		},
   595  		createTestEventDecisionTaskScheduled(10, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   596  		createTestEventDecisionTaskStarted(11),
   597  	}
   598  
   599  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   600  	task := &m.PollForDecisionTaskResponse{
   601  		TaskToken: []byte("test-token"),
   602  		WorkflowExecution: &m.WorkflowExecution{
   603  			WorkflowId: common.StringPtr("multiple-local-activities-workflow-id"),
   604  			RunId:      common.StringPtr("multiple-local-activities-workflow-run-id"),
   605  		},
   606  		WorkflowType: &m.WorkflowType{
   607  			Name: common.StringPtr("multiple-local-activities-workflow-type"),
   608  		},
   609  		PreviousStartedEventId: common.Int64Ptr(0),
   610  		StartedEventId:         common.Int64Ptr(3),
   611  		History:                &m.History{Events: testEvents[0:3]},
   612  		NextPageToken:          nil,
   613  		NextEventId:            common.Int64Ptr(4),
   614  	}
   615  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1)
   616  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes()
   617  
   618  	respondCounter := 0
   619  	s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption,
   620  	) (success *m.RespondDecisionTaskCompletedResponse, err error) {
   621  		respondCounter++
   622  		switch respondCounter {
   623  		case 1:
   624  			s.Equal(3, len(request.Decisions))
   625  			s.Equal(m.DecisionTypeRecordMarker, request.Decisions[0].GetDecisionType())
   626  			*task.PreviousStartedEventId = 3
   627  			*task.StartedEventId = 7
   628  			task.History.Events = testEvents[3:11]
   629  			close(doneCh)
   630  			return nil, nil
   631  		default:
   632  			panic("unexpected RespondDecisionTaskCompleted")
   633  		}
   634  	}).Times(1)
   635  
   636  	options := WorkerOptions{
   637  		Logger:                zaptest.NewLogger(s.T()),
   638  		DisableActivityWorker: true,
   639  		Identity:              "test-worker-identity",
   640  	}
   641  	worker := newAggregatedWorker(s.service, domain, taskList, options)
   642  	worker.RegisterWorkflowWithOptions(
   643  		longDecisionWorkflowFn,
   644  		RegisterWorkflowOptions{Name: "multiple-local-activities-workflow-type"},
   645  	)
   646  	worker.RegisterActivity(localActivitySleep)
   647  
   648  	startWorkerAndWait(s, worker, &doneCh)
   649  
   650  	s.True(isWorkflowCompleted)
   651  	s.Equal(2, localActivityCalledCount)
   652  }
   653  
   654  func (s *WorkersTestSuite) createLocalActivityMarkerDataForTest(activityID string) []byte {
   655  	lamd := localActivityMarkerData{
   656  		ActivityID: activityID,
   657  		ReplayTime: time.Now(),
   658  	}
   659  
   660  	// encode marker data
   661  	markerData, err := encodeArg(nil, lamd)
   662  	s.NoError(err)
   663  	return markerData
   664  }
   665  
   666  func (s *WorkersTestSuite) TestLocallyDispatchedActivity() {
   667  	activityCalledCount := atomic.NewInt32(0) // must be accessed with atomics, worker uses goroutines to run activities
   668  	activitySleep := func(duration time.Duration) error {
   669  		time.Sleep(duration)
   670  		activityCalledCount.Add(1)
   671  		return nil
   672  	}
   673  
   674  	doneCh := make(chan struct{})
   675  
   676  	workflowFn := func(ctx Context, input []byte) error {
   677  		ao := ActivityOptions{
   678  			ScheduleToCloseTimeout: 1 * time.Second,
   679  			ScheduleToStartTimeout: 1 * time.Second,
   680  			StartToCloseTimeout:    1 * time.Second,
   681  		}
   682  		ctx = WithActivityOptions(ctx, ao)
   683  		err := ExecuteActivity(ctx, activitySleep, 500*time.Millisecond).Get(ctx, nil)
   684  		return err
   685  	}
   686  
   687  	domain := "testDomain"
   688  	workflowType := "locally-dispatched-activity-workflow-type"
   689  	taskList := "locally-dispatched-activity-tl"
   690  	testEvents := []*m.HistoryEvent{
   691  		{
   692  			EventId:   common.Int64Ptr(1),
   693  			EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted),
   694  			WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{
   695  				TaskList:                            &m.TaskList{Name: &taskList},
   696  				ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10),
   697  				TaskStartToCloseTimeoutSeconds:      common.Int32Ptr(2),
   698  				WorkflowType:                        &m.WorkflowType{Name: common.StringPtr(workflowType)},
   699  			},
   700  		},
   701  		createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   702  		createTestEventDecisionTaskStarted(3),
   703  	}
   704  
   705  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   706  	task := &m.PollForDecisionTaskResponse{
   707  		TaskToken: []byte("test-token"),
   708  		WorkflowExecution: &m.WorkflowExecution{
   709  			WorkflowId: common.StringPtr("locally-dispatched-activity-workflow-id"),
   710  			RunId:      common.StringPtr("locally-dispatched-activity-workflow-run-id"),
   711  		},
   712  		WorkflowType: &m.WorkflowType{
   713  			Name: common.StringPtr(workflowType),
   714  		},
   715  		PreviousStartedEventId: common.Int64Ptr(0),
   716  		StartedEventId:         common.Int64Ptr(3),
   717  		History:                &m.History{Events: testEvents[0:3]},
   718  		NextPageToken:          nil,
   719  		NextEventId:            common.Int64Ptr(4),
   720  	}
   721  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1)
   722  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes()
   723  	s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   724  	s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption,
   725  	) (success *m.RespondDecisionTaskCompletedResponse, err error) {
   726  		s.Equal(1, len(request.Decisions))
   727  		activitiesToDispatchLocally := make(map[string]*m.ActivityLocalDispatchInfo)
   728  		d := request.Decisions[0]
   729  		s.Equal(m.DecisionTypeScheduleActivityTask, d.GetDecisionType())
   730  		activitiesToDispatchLocally[*d.ScheduleActivityTaskDecisionAttributes.ActivityId] =
   731  			&m.ActivityLocalDispatchInfo{
   732  				ActivityId:                      d.ScheduleActivityTaskDecisionAttributes.ActivityId,
   733  				ScheduledTimestamp:              common.Int64Ptr(time.Now().UnixNano()),
   734  				ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()),
   735  				StartedTimestamp:                common.Int64Ptr(time.Now().UnixNano()),
   736  				TaskToken:                       []byte("test-token")}
   737  		return &m.RespondDecisionTaskCompletedResponse{ActivitiesToDispatchLocally: activitiesToDispatchLocally}, nil
   738  	}).Times(1)
   739  	isActivityResponseCompleted := atomic.NewBool(false)
   740  	s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondActivityTaskCompletedRequest, opts ...yarpc.CallOption,
   741  	) error {
   742  		defer close(doneCh)
   743  		isActivityResponseCompleted.Swap(true)
   744  		return nil
   745  	}).Times(1)
   746  
   747  	options := WorkerOptions{
   748  		Logger:   zaptest.NewLogger(s.T()),
   749  		Identity: "test-worker-identity",
   750  	}
   751  	worker := newAggregatedWorker(s.service, domain, taskList, options)
   752  	worker.RegisterWorkflowWithOptions(
   753  		workflowFn,
   754  		RegisterWorkflowOptions{Name: workflowType},
   755  	)
   756  	worker.RegisterActivityWithOptions(activitySleep, RegisterActivityOptions{Name: "activitySleep"})
   757  
   758  	startWorkerAndWait(s, worker, &doneCh)
   759  
   760  	s.True(isActivityResponseCompleted.Load())
   761  	s.Equal(int32(1), activityCalledCount.Load())
   762  }
   763  
   764  func (s *WorkersTestSuite) TestMultipleLocallyDispatchedActivity() {
   765  	activityCalledCount := atomic.NewInt32(0)
   766  	activitySleep := func(duration time.Duration) error {
   767  		time.Sleep(duration)
   768  		activityCalledCount.Add(1)
   769  		return nil
   770  	}
   771  
   772  	doneCh := make(chan struct{})
   773  
   774  	var activityCount int32 = 5
   775  	workflowFn := func(ctx Context, input []byte) error {
   776  		ao := ActivityOptions{
   777  			ScheduleToCloseTimeout: 1 * time.Second,
   778  			ScheduleToStartTimeout: 1 * time.Second,
   779  			StartToCloseTimeout:    1 * time.Second,
   780  		}
   781  		ctx = WithActivityOptions(ctx, ao)
   782  
   783  		// start all activities in parallel, and wait for them all to complete.
   784  		var all []Future
   785  		for i := 0; i < int(activityCount); i++ {
   786  			all = append(all, ExecuteActivity(ctx, activitySleep, 500*time.Millisecond))
   787  		}
   788  		for i, f := range all {
   789  			s.NoError(f.Get(ctx, nil), "activity %v should not have failed", i)
   790  		}
   791  		return nil
   792  	}
   793  
   794  	domain := "testDomain"
   795  	workflowType := "locally-dispatched-multiple-activity-workflow-type"
   796  	taskList := "locally-dispatched-multiple-activity-tl"
   797  	testEvents := []*m.HistoryEvent{
   798  		{
   799  			EventId:   common.Int64Ptr(1),
   800  			EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted),
   801  			WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{
   802  				TaskList:                            &m.TaskList{Name: &taskList},
   803  				ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10),
   804  				TaskStartToCloseTimeoutSeconds:      common.Int32Ptr(2),
   805  				WorkflowType:                        &m.WorkflowType{Name: common.StringPtr(workflowType)},
   806  			},
   807  		},
   808  		createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}),
   809  		createTestEventDecisionTaskStarted(3),
   810  	}
   811  
   812  	options := WorkerOptions{
   813  		Logger:   zaptest.NewLogger(s.T()),
   814  		Identity: "test-worker-identity",
   815  	}
   816  
   817  	s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   818  	task := &m.PollForDecisionTaskResponse{
   819  		TaskToken: []byte("test-token"),
   820  		WorkflowExecution: &m.WorkflowExecution{
   821  			WorkflowId: common.StringPtr("locally-dispatched-multiple-activity-workflow-id"),
   822  			RunId:      common.StringPtr("locally-dispatched-multiple-activity-workflow-run-id"),
   823  		},
   824  		WorkflowType: &m.WorkflowType{
   825  			Name: common.StringPtr(workflowType),
   826  		},
   827  		PreviousStartedEventId: common.Int64Ptr(0),
   828  		StartedEventId:         common.Int64Ptr(3),
   829  		History:                &m.History{Events: testEvents[0:3]},
   830  		NextPageToken:          nil,
   831  		NextEventId:            common.Int64Ptr(4),
   832  	}
   833  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1)
   834  	s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes()
   835  	s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes()
   836  	s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption,
   837  	) (success *m.RespondDecisionTaskCompletedResponse, err error) {
   838  		s.Equal(int(activityCount), len(request.Decisions))
   839  		activitiesToDispatchLocally := make(map[string]*m.ActivityLocalDispatchInfo)
   840  		for _, d := range request.Decisions {
   841  			s.Equal(m.DecisionTypeScheduleActivityTask, d.GetDecisionType())
   842  			activitiesToDispatchLocally[*d.ScheduleActivityTaskDecisionAttributes.ActivityId] =
   843  				&m.ActivityLocalDispatchInfo{
   844  					ActivityId:                      d.ScheduleActivityTaskDecisionAttributes.ActivityId,
   845  					ScheduledTimestamp:              common.Int64Ptr(time.Now().UnixNano()),
   846  					ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()),
   847  					StartedTimestamp:                common.Int64Ptr(time.Now().UnixNano()),
   848  					TaskToken:                       []byte("test-token")}
   849  		}
   850  		return &m.RespondDecisionTaskCompletedResponse{ActivitiesToDispatchLocally: activitiesToDispatchLocally}, nil
   851  	}).Times(1)
   852  	activityResponseCompletedCount := atomic.NewInt32(0)
   853  	s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondActivityTaskCompletedRequest, opts ...yarpc.CallOption,
   854  	) error {
   855  		counted := activityResponseCompletedCount.Add(1)
   856  		if counted == activityCount {
   857  			close(doneCh)
   858  		}
   859  		return nil
   860  	}).MinTimes(1)
   861  
   862  	worker := newAggregatedWorker(s.service, domain, taskList, options)
   863  	worker.RegisterWorkflowWithOptions(
   864  		workflowFn,
   865  		RegisterWorkflowOptions{Name: workflowType},
   866  	)
   867  	worker.RegisterActivityWithOptions(activitySleep, RegisterActivityOptions{Name: "activitySleep"})
   868  	s.NotNil(worker.locallyDispatchedActivityWorker)
   869  	err := worker.Start()
   870  	s.NoError(err, "worker failed to start")
   871  
   872  	// wait for test to complete
   873  	// This test currently never completes, however after the timeout the asserts are true
   874  	// so the test passes, I believe this is an error.
   875  	select {
   876  	case <-doneCh:
   877  		s.T().Log("completed")
   878  	case <-time.After(1 * time.Second):
   879  		s.T().Log("timed out")
   880  	}
   881  	worker.Stop()
   882  
   883  	// for currently unbuffered channel at least one activity should be sent
   884  	s.True(activityResponseCompletedCount.Load() > 0)
   885  	s.True(activityCalledCount.Load() > 0)
   886  }
   887  
   888  // wait for test to complete - timeout and fail after 10 seconds to not block execution of other tests
   889  func startWorkerAndWait(s *WorkersTestSuite, worker *aggregatedWorker, doneCh *chan struct{}) {
   890  	s.T().Helper()
   891  	err := worker.Start()
   892  	s.NoError(err, "worker failed to start")
   893  	// wait for test to complete
   894  	select {
   895  	case <-*doneCh:
   896  	case <-time.After(10 * time.Second):
   897  		s.Fail("Test timed out")
   898  	}
   899  	worker.Stop()
   900  }