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 }