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

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package internal
    22  
    23  import (
    24  	"context"
    25  	"testing"
    26  
    27  	"github.com/golang/mock/gomock"
    28  	"github.com/stretchr/testify/require"
    29  	"github.com/stretchr/testify/suite"
    30  	"go.uber.org/yarpc"
    31  	"go.uber.org/zap"
    32  	"go.uber.org/zap/zaptest"
    33  
    34  	"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
    35  	"go.uber.org/cadence/.gen/go/shared"
    36  	"go.uber.org/cadence/internal/common"
    37  )
    38  
    39  const (
    40  	testWorkflowType = "test-workflow-type"
    41  	testActivityType = "test-activity-type"
    42  )
    43  
    44  type activityTestSuite struct {
    45  	suite.Suite
    46  	mockCtrl *gomock.Controller
    47  	service  *workflowservicetest.MockClient
    48  	logger   *zap.Logger
    49  }
    50  
    51  func TestActivityTestSuite(t *testing.T) {
    52  	s := new(activityTestSuite)
    53  	suite.Run(t, s)
    54  }
    55  
    56  func (s *activityTestSuite) SetupTest() {
    57  	s.mockCtrl = gomock.NewController(s.T())
    58  	s.service = workflowservicetest.NewMockClient(s.mockCtrl)
    59  	s.logger = zaptest.NewLogger(s.T())
    60  }
    61  
    62  func (s *activityTestSuite) TearDownTest() {
    63  	s.mockCtrl.Finish() // assert mock’s expectations
    64  }
    65  
    66  func (s *activityTestSuite) TestActivityHeartbeat() {
    67  	ctx, cancel := context.WithCancel(context.Background())
    68  	invoker := newServiceInvoker([]byte("task-token"), "identity", s.service, cancel, 1, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
    69  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{serviceInvoker: invoker})
    70  
    71  	s.service.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
    72  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).Times(1)
    73  
    74  	RecordActivityHeartbeat(ctx, "testDetails")
    75  }
    76  
    77  func (s *activityTestSuite) TestActivityHeartbeat_InternalError() {
    78  	ctx, cancel := context.WithCancel(context.Background())
    79  	invoker := newServiceInvoker([]byte("task-token"), "identity", s.service, cancel, 1, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
    80  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
    81  		serviceInvoker: invoker,
    82  		logger:         getTestLogger(s.T())})
    83  
    84  	s.service.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
    85  		Return(nil, &shared.InternalServiceError{}).
    86  		Do(func(ctx context.Context, request *shared.RecordActivityTaskHeartbeatRequest, opts ...yarpc.CallOption) {
    87  			s.T().Log("MOCK RecordActivityTaskHeartbeat executed")
    88  		}).AnyTimes()
    89  
    90  	RecordActivityHeartbeat(ctx, "testDetails")
    91  }
    92  
    93  func (s *activityTestSuite) TestActivityHeartbeat_CancelRequested() {
    94  	ctx, cancel := context.WithCancel(context.Background())
    95  	invoker := newServiceInvoker([]byte("task-token"), "identity", s.service, cancel, 1, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
    96  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
    97  		serviceInvoker: invoker,
    98  		logger:         getTestLogger(s.T())})
    99  
   100  	s.service.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   101  		Return(&shared.RecordActivityTaskHeartbeatResponse{CancelRequested: common.BoolPtr(true)}, nil).Times(1)
   102  
   103  	RecordActivityHeartbeat(ctx, "testDetails")
   104  	<-ctx.Done()
   105  	require.Equal(s.T(), ctx.Err(), context.Canceled)
   106  }
   107  
   108  func (s *activityTestSuite) TestActivityHeartbeat_EntityNotExist() {
   109  	ctx, cancel := context.WithCancel(context.Background())
   110  	invoker := newServiceInvoker([]byte("task-token"), "identity", s.service, cancel, 1, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
   111  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
   112  		serviceInvoker: invoker,
   113  		logger:         getTestLogger(s.T())})
   114  
   115  	s.service.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   116  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, &shared.EntityNotExistsError{}).Times(1)
   117  
   118  	RecordActivityHeartbeat(ctx, "testDetails")
   119  	<-ctx.Done()
   120  	require.Equal(s.T(), ctx.Err(), context.Canceled)
   121  }
   122  
   123  func (s *activityTestSuite) TestActivityHeartbeat_SuppressContinousInvokes() {
   124  	ctx, cancel := context.WithCancel(context.Background())
   125  	invoker := newServiceInvoker([]byte("task-token"), "identity", s.service, cancel, 2, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
   126  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
   127  		serviceInvoker: invoker,
   128  		logger:         getTestLogger(s.T())})
   129  
   130  	// Multiple calls but only one call is made.
   131  	s.service.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   132  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).Times(1)
   133  	RecordActivityHeartbeat(ctx, "testDetails")
   134  	RecordActivityHeartbeat(ctx, "testDetails")
   135  	RecordActivityHeartbeat(ctx, "testDetails")
   136  	invoker.Close(false)
   137  
   138  	// No HB timeout configured.
   139  	service2 := workflowservicetest.NewMockClient(s.mockCtrl)
   140  	invoker2 := newServiceInvoker([]byte("task-token"), "identity", service2, cancel, 0, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
   141  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
   142  		serviceInvoker: invoker2,
   143  		logger:         getTestLogger(s.T())})
   144  	service2.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   145  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).Times(1)
   146  	RecordActivityHeartbeat(ctx, "testDetails")
   147  	RecordActivityHeartbeat(ctx, "testDetails")
   148  	invoker2.Close(false)
   149  
   150  	// simulate batch picks before expiry.
   151  	waitCh := make(chan struct{})
   152  	service3 := workflowservicetest.NewMockClient(s.mockCtrl)
   153  	invoker3 := newServiceInvoker([]byte("task-token"), "identity", service3, cancel, 2, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
   154  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
   155  		serviceInvoker: invoker3,
   156  		logger:         getTestLogger(s.T())})
   157  	service3.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   158  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).Times(1)
   159  
   160  	service3.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   161  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).
   162  		Do(func(ctx context.Context, request *shared.RecordActivityTaskHeartbeatRequest, opts ...yarpc.CallOption) {
   163  			ev := newEncodedValues(request.Details, nil)
   164  			var progress string
   165  			err := ev.Get(&progress)
   166  			if err != nil {
   167  				panic(err)
   168  			}
   169  			require.Equal(s.T(), "testDetails-expected", progress)
   170  			waitCh <- struct{}{}
   171  		}).Times(1)
   172  
   173  	RecordActivityHeartbeat(ctx, "testDetails")
   174  	RecordActivityHeartbeat(ctx, "testDetails2")
   175  	RecordActivityHeartbeat(ctx, "testDetails3")
   176  	RecordActivityHeartbeat(ctx, "testDetails-expected")
   177  	<-waitCh
   178  	invoker3.Close(false)
   179  
   180  	// simulate batch picks before expiry, with out any progress specified.
   181  	waitCh2 := make(chan struct{})
   182  	service4 := workflowservicetest.NewMockClient(s.mockCtrl)
   183  	invoker4 := newServiceInvoker([]byte("task-token"), "identity", service4, cancel, 2, make(chan struct{}), FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
   184  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
   185  		serviceInvoker: invoker4,
   186  		logger:         getTestLogger(s.T())})
   187  	service4.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   188  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).Times(1)
   189  	service4.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   190  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).
   191  		Do(func(ctx context.Context, request *shared.RecordActivityTaskHeartbeatRequest, opts ...yarpc.CallOption) {
   192  			require.Nil(s.T(), request.Details)
   193  			waitCh2 <- struct{}{}
   194  		}).Times(1)
   195  
   196  	RecordActivityHeartbeat(ctx, nil)
   197  	RecordActivityHeartbeat(ctx, nil)
   198  	RecordActivityHeartbeat(ctx, nil)
   199  	RecordActivityHeartbeat(ctx, nil)
   200  	<-waitCh2
   201  	invoker4.Close(false)
   202  }
   203  
   204  func (s *activityTestSuite) TestActivityHeartbeat_WorkerStop() {
   205  	ctx, cancel := context.WithCancel(context.Background())
   206  	workerStopChannel := make(chan struct{})
   207  	invoker := newServiceInvoker([]byte("task-token"), "identity", s.service, cancel, 5, workerStopChannel, FeatureFlags{}, s.logger, testWorkflowType, testActivityType)
   208  	ctx = context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{serviceInvoker: invoker})
   209  
   210  	heartBeatDetail := "testDetails"
   211  	waitCh := make(chan struct{}, 1)
   212  	waitCh <- struct{}{}
   213  	waitC2 := make(chan struct{}, 1)
   214  	s.service.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).
   215  		Return(&shared.RecordActivityTaskHeartbeatResponse{}, nil).
   216  		Do(func(ctx context.Context, request *shared.RecordActivityTaskHeartbeatRequest, opts ...yarpc.CallOption) {
   217  			if _, ok := <-waitCh; ok {
   218  				close(waitCh)
   219  				return
   220  			}
   221  			close(waitC2)
   222  		}).Times(2)
   223  	RecordActivityHeartbeat(ctx, heartBeatDetail)
   224  	RecordActivityHeartbeat(ctx, "testDetails")
   225  	close(workerStopChannel)
   226  	<-waitC2
   227  }
   228  
   229  func (s *activityTestSuite) TestGetWorkerStopChannel() {
   230  	ch := make(chan struct{}, 1)
   231  	ctx := context.WithValue(context.Background(), activityEnvContextKey, &activityEnvironment{workerStopChannel: ch})
   232  	channel := GetWorkerStopChannel(ctx)
   233  	s.NotNil(channel)
   234  }