go.uber.org/cadence@v1.2.9/test/integration_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 test 23 24 import ( 25 "context" 26 "errors" 27 "fmt" 28 "net" 29 "strings" 30 "sync" 31 "testing" 32 "time" 33 34 "github.com/pborman/uuid" 35 "github.com/stretchr/testify/require" 36 "github.com/stretchr/testify/suite" 37 "github.com/uber-go/tally" 38 "go.uber.org/goleak" 39 "go.uber.org/zap/zaptest" 40 41 "go.uber.org/cadence" 42 "go.uber.org/cadence/.gen/go/shared" 43 "go.uber.org/cadence/client" 44 "go.uber.org/cadence/interceptors" 45 "go.uber.org/cadence/internal" 46 "go.uber.org/cadence/worker" 47 "go.uber.org/cadence/workflow" 48 ) 49 50 type IntegrationTestSuite struct { 51 *require.Assertions 52 suite.Suite 53 config Config 54 rpcClient *rpcClient 55 libClient client.Client 56 domainClient client.DomainClient 57 activities *Activities 58 workflows *Workflows 59 worker worker.Worker 60 seq int64 61 taskListName string 62 tracer *tracingInterceptorFactory 63 } 64 65 const ( 66 ctxTimeout = 15 * time.Second 67 domainName = "integration-test-domain" 68 domainCacheRefreshInterval = 20 * time.Second 69 testContextKey = "test-context-key" 70 ) 71 72 func TestIntegrationSuite(t *testing.T) { 73 suite.Run(t, new(IntegrationTestSuite)) 74 } 75 76 // waitForTCP waits until target tcp address is available. 77 func waitForTCP(timeout time.Duration, addr string) error { 78 var d net.Dialer 79 ctx, cancel := context.WithTimeout(context.Background(), timeout) 80 defer cancel() 81 82 for { 83 select { 84 case <-ctx.Done(): 85 return fmt.Errorf("failed to wait until %s: %v", addr, ctx.Err()) 86 default: 87 conn, err := d.DialContext(ctx, "tcp", addr) 88 if err != nil { 89 continue 90 } 91 _ = conn.Close() 92 return nil 93 } 94 } 95 } 96 97 func (ts *IntegrationTestSuite) SetupSuite() { 98 ts.Assertions = require.New(ts.T()) 99 ts.config = newConfig() 100 ts.activities = newActivities() 101 ts.workflows = &Workflows{} 102 ts.Nil(waitForTCP(time.Minute, ts.config.ServiceAddr)) 103 var err error 104 if ts.config.EnableGrpcAdapter { 105 ts.rpcClient, err = newGRPCAdapterClient(ts.config.ServiceName, ts.config.ServiceAddr) 106 } else { 107 ts.rpcClient, err = newRPCClient(ts.config.ServiceName, ts.config.ServiceAddr) 108 } 109 ts.NoError(err) 110 ts.libClient = client.NewClient(ts.rpcClient.Interface, domainName, 111 &client.Options{ 112 ContextPropagators: []workflow.ContextPropagator{NewStringMapPropagator([]string{testContextKey})}, 113 }) 114 ts.domainClient = client.NewDomainClient(ts.rpcClient.Interface, &client.Options{}) 115 ts.registerDomain() 116 internal.StartVersionMetrics(tally.NoopScope) 117 } 118 119 func (ts *IntegrationTestSuite) TearDownSuite() { 120 ts.Assertions = require.New(ts.T()) 121 ts.rpcClient.Close() 122 close(internal.StopMetrics) 123 // allow the pollers to shut down, and ensure there are no goroutine leaks. 124 // this will wait for up to 1 minute for leaks to subside, but exit relatively quickly if possible. 125 max := time.After(time.Minute) 126 var last error 127 for { 128 select { 129 case <-max: 130 if last != nil { 131 ts.NoError(last) 132 return 133 } 134 ts.FailNow("leaks timed out but no error, should be impossible") 135 case <-time.After(time.Second): 136 // https://github.com/uber-go/cadence-client/issues/739 137 last = goleak.Find(goleak.IgnoreTopFunction("go.uber.org/cadence/internal.(*coroutineState).initialYield")) 138 if last == nil { 139 // no leak, done waiting 140 return 141 } 142 // else wait for another check or the timeout (which will record the latest error) 143 } 144 } 145 } 146 147 func (ts *IntegrationTestSuite) SetupTest() { 148 ts.Assertions = require.New(ts.T()) 149 ts.seq++ 150 ts.activities.clearInvoked() 151 ts.taskListName = fmt.Sprintf("tl-%v", ts.seq) 152 ts.tracer = newtracingInterceptorFactory() 153 } 154 155 func (ts *IntegrationTestSuite) BeforeTest(suiteName, testName string) { 156 options := worker.Options{ 157 DisableStickyExecution: ts.config.IsStickyOff, 158 Logger: zaptest.NewLogger(ts.T()), 159 WorkflowInterceptorChainFactories: []interceptors.WorkflowInterceptorFactory{ts.tracer}, 160 ContextPropagators: []workflow.ContextPropagator{NewStringMapPropagator([]string{testContextKey})}, 161 } 162 163 if testName == "TestNonDeterministicWorkflowQuery" || testName == "TestNonDeterministicWorkflowFailPolicy" { 164 options.NonDeterministicWorkflowPolicy = worker.NonDeterministicWorkflowPolicyFailWorkflow 165 166 // disable sticky executon so each workflow yield will require rerunning it from beginning 167 options.DisableStickyExecution = true 168 } 169 170 ts.worker = worker.New(ts.rpcClient.Interface, domainName, ts.taskListName, options) 171 ts.registerWorkflowsAndActivities(ts.worker) 172 ts.Nil(ts.worker.Start()) 173 } 174 175 func (ts *IntegrationTestSuite) TearDownTest() { 176 ts.worker.Stop() 177 } 178 179 func (ts *IntegrationTestSuite) TestBasic() { 180 var expected []string 181 err := ts.executeWorkflow("test-basic", ts.workflows.Basic, &expected) 182 ts.NoError(err) 183 ts.EqualValues(expected, ts.activities.invoked()) 184 ts.Equal([]string{"ExecuteWorkflow begin", "ExecuteActivity", "ExecuteActivity", "ExecuteWorkflow end"}, 185 ts.tracer.GetTrace("go.uber.org/cadence/test.(*Workflows).Basic")) 186 } 187 188 func (ts *IntegrationTestSuite) TestActivityRetryOnError() { 189 var expected []string 190 err := ts.executeWorkflow("test-activity-retry-on-error", ts.workflows.ActivityRetryOnError, &expected) 191 ts.NoError(err) 192 ts.EqualValues(expected, ts.activities.invoked()) 193 } 194 195 func (ts *IntegrationTestSuite) TestActivityRetryOnTimeoutStableError() { 196 var expected []string 197 err := ts.executeWorkflow("test-activity-retry-on-timeout-stable-error", ts.workflows.RetryTimeoutStableErrorWorkflow, &expected) 198 ts.Nil(err) 199 } 200 201 func (ts *IntegrationTestSuite) TestActivityRetryOptionsChange() { 202 var expected []string 203 err := ts.executeWorkflow("test-activity-retry-options-change", ts.workflows.ActivityRetryOptionsChange, &expected) 204 ts.NoError(err) 205 ts.EqualValues(expected, ts.activities.invoked()) 206 } 207 208 func (ts *IntegrationTestSuite) TestActivityRetryOnStartToCloseTimeout() { 209 var expected []string 210 err := ts.executeWorkflow( 211 "test-activity-retry-on-start2close-timeout", 212 ts.workflows.ActivityRetryOnTimeout, 213 &expected, 214 shared.TimeoutTypeStartToClose) 215 216 ts.NoError(err) 217 ts.EqualValues(expected, ts.activities.invoked()) 218 } 219 220 func (ts *IntegrationTestSuite) TestActivityRetryOnHBTimeout() { 221 var expected []string 222 err := ts.executeWorkflow("test-activity-retry-on-hbtimeout", ts.workflows.ActivityRetryOnHBTimeout, &expected) 223 ts.NoError(err) 224 ts.EqualValues(expected, ts.activities.invoked()) 225 } 226 227 func (ts *IntegrationTestSuite) TestActivityAutoHeartbeat() { 228 var expected []string 229 err := ts.executeWorkflow("test-activity-auto-heartbeat", ts.workflows.ActivityAutoHeartbeat, &expected) 230 ts.NoError(err) 231 ts.EqualValues(expected, ts.activities.invoked()) 232 } 233 234 func (ts *IntegrationTestSuite) TestContinueAsNew() { 235 var result int 236 err := ts.executeWorkflow("test-continueasnew", ts.workflows.ContinueAsNew, &result, 4, ts.taskListName) 237 ts.NoError(err) 238 ts.Equal(999, result) 239 } 240 241 func (ts *IntegrationTestSuite) TestContinueAsNewCarryOver() { 242 var result string 243 startOptions := ts.startWorkflowOptions("test-continueasnew-carryover") 244 startOptions.Memo = map[string]interface{}{ 245 "memoKey": "memoVal", 246 } 247 startOptions.SearchAttributes = map[string]interface{}{ 248 "CustomKeywordField": "searchAttr", 249 } 250 err := ts.executeWorkflowWithOption(startOptions, ts.workflows.ContinueAsNewWithOptions, &result, 4, ts.taskListName) 251 ts.NoError(err) 252 ts.Equal("memoVal,searchAttr", result) 253 } 254 255 func (ts *IntegrationTestSuite) TestCancellation() { 256 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 257 defer cancel() 258 run, err := ts.libClient.ExecuteWorkflow(ctx, 259 ts.startWorkflowOptions("test-cancellation"), ts.workflows.Basic) 260 ts.NoError(err) 261 ts.NotNil(run) 262 ts.Nil(ts.libClient.CancelWorkflow(ctx, "test-cancellation", run.GetRunID())) 263 err = run.Get(ctx, nil) 264 ts.Error(err) 265 _, ok := err.(*cadence.CanceledError) 266 ts.True(ok) 267 ts.Truef(client.IsWorkflowError(err), "err from canceled workflows should be a workflow error: %#v", err) 268 } 269 270 func (ts *IntegrationTestSuite) TestStackTraceQuery() { 271 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 272 defer cancel() 273 run, err := ts.libClient.ExecuteWorkflow(ctx, 274 ts.startWorkflowOptions("test-stack-trace-query"), ts.workflows.Basic) 275 ts.NoError(err) 276 value, err := ts.libClient.QueryWorkflow(ctx, "test-stack-trace-query", run.GetRunID(), "__stack_trace") 277 ts.NoError(err) 278 ts.NotNil(value) 279 var trace string 280 ts.NoError(value.Get(&trace)) 281 ts.True(strings.Contains(trace, "go.uber.org/cadence/test.(*Workflows).Basic")) 282 } 283 284 func (ts *IntegrationTestSuite) TestConsistentQuery() { 285 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 286 defer cancel() 287 // this workflow will start a local activity which blocks for long enough 288 // to ensure that consistent query must wait in order to satisfy consistency 289 wfOpts := ts.startWorkflowOptions("test-consistent-query") 290 wfOpts.DecisionTaskStartToCloseTimeout = 5 * time.Second 291 run, err := ts.libClient.ExecuteWorkflow(ctx, wfOpts, ts.workflows.ConsistentQueryWorkflow, 3*time.Second) 292 ts.Nil(err) 293 // Wait for a second to ensure that first decision task gets started and completed before we send signal. 294 // Query cannot be run until first decision task has been completed. 295 // If signal occurs right after workflow start then WorkflowStarted and Signal events will both be part of the same 296 // decision task. So query will be blocked waiting for signal to complete, this is not what we want because it 297 // will not exercise the consistent query code path. 298 <-time.After(time.Second) 299 err = ts.libClient.SignalWorkflow(ctx, "test-consistent-query", run.GetRunID(), consistentQuerySignalCh, "signal-input") 300 ts.NoError(err) 301 302 value, err := ts.libClient.QueryWorkflowWithOptions(ctx, &client.QueryWorkflowWithOptionsRequest{ 303 WorkflowID: "test-consistent-query", 304 RunID: run.GetRunID(), 305 QueryType: "consistent_query", 306 QueryConsistencyLevel: shared.QueryConsistencyLevelStrong.Ptr(), 307 }) 308 ts.Nil(err) 309 ts.NotNil(value) 310 ts.NotNil(value.QueryResult) 311 ts.Nil(value.QueryRejected) 312 var queryResult string 313 ts.NoError(value.QueryResult.Get(&queryResult)) 314 ts.Equal("signal-input", queryResult) 315 } 316 317 func (ts *IntegrationTestSuite) TestWorkflowIDReuseRejectDuplicate() { 318 var result string 319 err := ts.executeWorkflow( 320 "test-workflowidreuse-reject-duplicate", 321 ts.workflows.IDReusePolicy, 322 &result, 323 uuid.New(), 324 client.WorkflowIDReusePolicyRejectDuplicate, 325 false, 326 false, 327 ) 328 ts.Error(err) 329 gerr, ok := err.(*workflow.GenericError) 330 ts.True(ok) 331 ts.True(strings.Contains(gerr.Error(), "WorkflowExecutionAlreadyStartedError")) 332 ts.Truef(client.IsWorkflowError(err), "already-started child error should be a workflow error: %#v", err) 333 } 334 335 func (ts *IntegrationTestSuite) TestWorkflowIDReuseAllowDuplicateFailedOnly1() { 336 var result string 337 err := ts.executeWorkflow( 338 "test-workflowidreuse-reject-duplicate-failed-only1", 339 ts.workflows.IDReusePolicy, 340 &result, 341 uuid.New(), 342 client.WorkflowIDReusePolicyAllowDuplicateFailedOnly, 343 false, 344 false, 345 ) 346 ts.Error(err) 347 gerr, ok := err.(*workflow.GenericError) 348 ts.True(ok) 349 ts.True(strings.Contains(gerr.Error(), "WorkflowExecutionAlreadyStartedError")) 350 ts.Truef(client.IsWorkflowError(err), "already-started child error should be a workflow error: %#v", err) 351 } 352 353 func (ts *IntegrationTestSuite) TestWorkflowIDReuseAllowDuplicateFailedOnly2() { 354 var result string 355 err := ts.executeWorkflow( 356 "test-workflowidreuse-reject-duplicate-failed-only2", 357 ts.workflows.IDReusePolicy, 358 &result, 359 uuid.New(), 360 client.WorkflowIDReusePolicyAllowDuplicateFailedOnly, 361 false, 362 true, 363 ) 364 ts.NoError(err) 365 ts.Equal("WORLD", result) 366 } 367 368 func (ts *IntegrationTestSuite) TestWorkflowIDReuseAllowDuplicate() { 369 var result string 370 err := ts.executeWorkflow( 371 "test-workflowidreuse-allow-duplicate", 372 ts.workflows.IDReusePolicy, 373 &result, 374 uuid.New(), 375 client.WorkflowIDReusePolicyAllowDuplicate, 376 false, 377 false, 378 ) 379 ts.NoError(err) 380 ts.Equal("HELLOWORLD", result) 381 } 382 383 func (ts *IntegrationTestSuite) TestWorkflowIDReuseErrorViaStartWorkflow() { 384 duplicatedWID := "test-workflowidreuse-duplicate-start-error" 385 // setup: run any workflow once to consume the ID 386 err := ts.executeWorkflow( 387 duplicatedWID, 388 ts.workflows.SimplestWorkflow, 389 nil, 390 ) 391 ts.NoError(err, "basic workflow should succeed") 392 393 // a second attempt should fail 394 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 395 defer cancel() 396 opts := ts.startWorkflowOptions(duplicatedWID) 397 opts.WorkflowIDReusePolicy = client.WorkflowIDReusePolicyRejectDuplicate 398 exec, err := ts.libClient.StartWorkflow(ctx, opts, ts.workflows.SimplestWorkflow) 399 ts.Nil(exec) 400 ts.Error(err) 401 ts.IsType(&shared.WorkflowExecutionAlreadyStartedError{}, err, "should be the known already-started error type") 402 ts.False(client.IsWorkflowError(err), "start-workflow rejected errors should not be workflow errors") 403 } 404 405 func (ts *IntegrationTestSuite) TestChildWFRetryOnError() { 406 err := ts.executeWorkflow("test-childwf-retry-on-error", ts.workflows.ChildWorkflowRetryOnError, nil) 407 ts.Error(err) 408 ts.Truef(client.IsWorkflowError(err), "child error should be a workflow error: %#v", err) 409 ts.EqualValues([]string{"toUpper", "toUpper", "toUpper"}, ts.activities.invoked()) 410 } 411 412 func (ts *IntegrationTestSuite) TestChildWFRetryOnTimeout() { 413 err := ts.executeWorkflow("test-childwf-retry-on-timeout", ts.workflows.ChildWorkflowRetryOnTimeout, nil) 414 ts.Error(err) 415 ts.Truef(client.IsWorkflowError(err), "child-timeout error should be a workflow error: %#v", err) 416 ts.EqualValues([]string{"sleep", "sleep", "sleep"}, ts.activities.invoked()) 417 } 418 419 func (ts *IntegrationTestSuite) TestChildWFWithMemoAndSearchAttributes() { 420 var result string 421 err := ts.executeWorkflow("test-childwf-success-memo-searchAttr", ts.workflows.ChildWorkflowSuccess, &result) 422 ts.NoError(err) 423 ts.EqualValues([]string{"getMemoAndSearchAttr"}, ts.activities.invoked()) 424 ts.Equal("memoVal, searchAttrVal", result) 425 ts.Equal([]string{"ExecuteWorkflow begin", "ExecuteChildWorkflow", "ExecuteWorkflow end"}, 426 ts.tracer.GetTrace("go.uber.org/cadence/test.(*Workflows).ChildWorkflowSuccess")) 427 } 428 429 func (ts *IntegrationTestSuite) TestChildWFWithParentClosePolicyTerminate() { 430 var childWorkflowID string 431 err := ts.executeWorkflow("test-childwf-parent-close-policy", ts.workflows.ChildWorkflowSuccessWithParentClosePolicyTerminate, &childWorkflowID) 432 ts.NoError(err) 433 // Need to wait for child workflow to finish as well otherwise test becomes flaky 434 ts.waitForWorkflowFinish(childWorkflowID, "") 435 resp, err := ts.libClient.DescribeWorkflowExecution(context.Background(), childWorkflowID, "") 436 ts.NoError(err) 437 ts.True(resp.WorkflowExecutionInfo.GetCloseTime() > 0) 438 } 439 440 func (ts *IntegrationTestSuite) TestChildWFWithParentClosePolicyAbandon() { 441 var childWorkflowID string 442 err := ts.executeWorkflow("test-childwf-parent-close-policy", ts.workflows.ChildWorkflowSuccessWithParentClosePolicyAbandon, &childWorkflowID) 443 ts.NoError(err) 444 resp, err := ts.libClient.DescribeWorkflowExecution(context.Background(), childWorkflowID, "") 445 ts.NoError(err) 446 ts.Zerof(resp.WorkflowExecutionInfo.GetCloseTime(), "Expected close time to be zero, got %d. Describe response: %#v", resp.WorkflowExecutionInfo.GetCloseTime(), resp) 447 } 448 449 func (ts *IntegrationTestSuite) TestChildWFCancel() { 450 var childWorkflowID string 451 err := ts.executeWorkflow("test-childwf-cancel", ts.workflows.ChildWorkflowCancel, &childWorkflowID) 452 ts.NoError(err) 453 resp, err := ts.libClient.DescribeWorkflowExecution(context.Background(), childWorkflowID, "") 454 ts.NoError(err) 455 ts.Equal(shared.WorkflowExecutionCloseStatusCanceled, resp.WorkflowExecutionInfo.GetCloseStatus()) 456 } 457 458 func (ts *IntegrationTestSuite) TestActivityCancelUsingReplay() { 459 replayer := worker.NewWorkflowReplayer() 460 replayer.RegisterWorkflowWithOptions(ts.workflows.ActivityCancelRepro, workflow.RegisterOptions{DisableAlreadyRegisteredCheck: true}) 461 err := replayer.ReplayPartialWorkflowHistoryFromJSONFile(zaptest.NewLogger(ts.T()), "fixtures/activity.cancel.sm.repro.json", 12) 462 ts.NoError(err) 463 } 464 465 func (ts *IntegrationTestSuite) TestActivityCancelRepro() { 466 var expected []string 467 err := ts.executeWorkflow("test-activity-cancel-sm", ts.workflows.ActivityCancelRepro, &expected) 468 ts.NoError(err) 469 ts.EqualValues(expected, ts.activities.invoked()) 470 } 471 472 func (ts *IntegrationTestSuite) TestWorkflowWithLocalActivityCtxPropagation() { 473 var expected string 474 err := ts.executeWorkflow("test-wf-local-activity-ctx-prop", ts.workflows.WorkflowWithLocalActivityCtxPropagation, &expected) 475 ts.NoError(err) 476 ts.EqualValues(expected, "test-data-in-contexttest-data-in-context") 477 } 478 479 func (ts *IntegrationTestSuite) TestLargeQueryResultError() { 480 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 481 defer cancel() 482 run, err := ts.libClient.ExecuteWorkflow(ctx, 483 ts.startWorkflowOptions("test-large-query-error"), ts.workflows.LargeQueryResultWorkflow) 484 ts.Nil(err) 485 value, err := ts.libClient.QueryWorkflow(ctx, "test-large-query-error", run.GetRunID(), "large_query") 486 ts.Error(err) 487 ts.False(client.IsWorkflowError(err), "query errors should not be workflow errors, as they are request-related") 488 489 queryErr, ok := err.(*shared.QueryFailedError) 490 ts.True(ok) 491 ts.Equal("query result size (3000000) exceeds limit (2000000)", queryErr.Message) 492 ts.Nil(value) 493 } 494 495 func (ts *IntegrationTestSuite) TestInspectActivityInfo() { 496 err := ts.executeWorkflow("test-activity-info", ts.workflows.InspectActivityInfo, nil) 497 ts.Nil(err) 498 } 499 500 func (ts *IntegrationTestSuite) TestInspectLocalActivityInfo() { 501 err := ts.executeWorkflow("test-local-activity-info", ts.workflows.InspectLocalActivityInfo, nil) 502 ts.Nil(err) 503 } 504 505 func (ts *IntegrationTestSuite) TestDomainUpdate() { 506 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 507 defer cancel() 508 name := domainName 509 description := "test-description" 510 err := ts.domainClient.Update(ctx, &shared.UpdateDomainRequest{ 511 Name: &name, 512 UpdatedInfo: &shared.UpdateDomainInfo{Description: &description}, 513 }) 514 ts.NoError(err) 515 516 domain, err := ts.domainClient.Describe(ctx, name) 517 ts.NoError(err) 518 ts.Equal(description, *domain.DomainInfo.Description) 519 } 520 521 func (ts *IntegrationTestSuite) TestNonDeterministicWorkflowFailPolicy() { 522 err := ts.executeWorkflow("test-nondeterminism-failpolicy", ts.workflows.NonDeterminismSimulatorWorkflow, nil) 523 var customErr *internal.CustomError 524 ok := errors.As(err, &customErr) 525 ts.Truef(ok, "expected CustomError but got %T", err) 526 ts.Equal("NonDeterministicWorkflowPolicyFailWorkflow", customErr.Reason()) 527 } 528 529 func (ts *IntegrationTestSuite) TestNonDeterministicWorkflowQuery() { 530 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 531 defer cancel() 532 run, err := ts.libClient.ExecuteWorkflow(ctx, ts.startWorkflowOptions("test-nondeterministic-query"), ts.workflows.NonDeterminismSimulatorWorkflow) 533 ts.Nil(err) 534 err = run.Get(ctx, nil) 535 var customErr *internal.CustomError 536 ok := errors.As(err, &customErr) 537 ts.Truef(ok, "expected CustomError but got %T", err) 538 ts.Equal("NonDeterministicWorkflowPolicyFailWorkflow", customErr.Reason()) 539 540 // query failed workflow should still work 541 value, err := ts.libClient.QueryWorkflow(ctx, "test-nondeterministic-query", run.GetRunID(), "__stack_trace") 542 ts.NoError(err) 543 ts.NotNil(value) 544 var trace string 545 ts.NoError(value.Get(&trace)) 546 } 547 548 func (ts *IntegrationTestSuite) registerDomain() { 549 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 550 defer cancel() 551 name := domainName 552 retention := int32(1) 553 err := ts.domainClient.Register(ctx, &shared.RegisterDomainRequest{ 554 Name: &name, 555 WorkflowExecutionRetentionPeriodInDays: &retention, 556 }) 557 if err != nil { 558 if _, ok := err.(*shared.DomainAlreadyExistsError); ok { 559 return 560 } 561 } 562 ts.NoError(err) 563 time.Sleep(domainCacheRefreshInterval) // wait for domain cache refresh on cadence-server 564 // bellow is used to guarantee domain is ready 565 var dummyReturn string 566 err = ts.executeWorkflow("test-domain-exist", ts.workflows.SimplestWorkflow, &dummyReturn) 567 numOfRetry := 20 568 for err != nil && numOfRetry >= 0 { 569 if _, ok := err.(*shared.EntityNotExistsError); ok { 570 time.Sleep(domainCacheRefreshInterval) 571 err = ts.executeWorkflow("test-domain-exist", ts.workflows.SimplestWorkflow, &dummyReturn) 572 } else { 573 break 574 } 575 numOfRetry-- 576 } 577 } 578 579 // executeWorkflow executes a given workflow and waits for the result 580 func (ts *IntegrationTestSuite) executeWorkflow( 581 wfID string, wfFunc interface{}, retValPtr interface{}, args ...interface{}) error { 582 options := ts.startWorkflowOptions(wfID) 583 return ts.executeWorkflowWithOption(options, wfFunc, retValPtr, args...) 584 } 585 586 func (ts *IntegrationTestSuite) executeWorkflowWithOption( 587 options client.StartWorkflowOptions, wfFunc interface{}, retValPtr interface{}, args ...interface{}) error { 588 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 589 defer cancel() 590 run, err := ts.libClient.ExecuteWorkflow(ctx, options, wfFunc, args...) 591 if err != nil { 592 return err 593 } 594 err = run.Get(ctx, retValPtr) 595 logger := zaptest.NewLogger(ts.T()) 596 if ts.config.Debug { 597 iter := ts.libClient.GetWorkflowHistory(ctx, options.ID, run.GetRunID(), false, shared.HistoryEventFilterTypeAllEvent) 598 for iter.HasNext() { 599 event, err1 := iter.Next() 600 if err1 != nil { 601 break 602 } 603 logger.Info(event.String()) 604 } 605 } 606 return err 607 } 608 609 func (ts *IntegrationTestSuite) startWorkflowOptions(wfID string) client.StartWorkflowOptions { 610 return client.StartWorkflowOptions{ 611 ID: wfID, 612 TaskList: ts.taskListName, 613 ExecutionStartToCloseTimeout: 15 * time.Second, 614 DecisionTaskStartToCloseTimeout: time.Second, 615 WorkflowIDReusePolicy: client.WorkflowIDReusePolicyAllowDuplicate, 616 } 617 } 618 619 func (ts *IntegrationTestSuite) registerWorkflowsAndActivities(w worker.Worker) { 620 ts.workflows.register(w) 621 ts.activities.register(w) 622 } 623 624 func (ts *IntegrationTestSuite) waitForWorkflowFinish(wid string, runId string) error { 625 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 626 defer cancel() 627 wfRun := ts.libClient.GetWorkflow(ctx, wid, runId) 628 return wfRun.Get(ctx, nil) 629 } 630 631 var _ interceptors.WorkflowInterceptorFactory = (*tracingInterceptorFactory)(nil) 632 633 type tracingInterceptorFactory struct { 634 sync.Mutex 635 // key is workflow id 636 instances map[string]*tracingInterceptor 637 } 638 639 func newtracingInterceptorFactory() *tracingInterceptorFactory { 640 return &tracingInterceptorFactory{instances: make(map[string]*tracingInterceptor)} 641 } 642 643 func (t *tracingInterceptorFactory) GetTrace(workflowType string) []string { 644 t.Mutex.Lock() 645 defer t.Mutex.Unlock() 646 if i, ok := t.instances[workflowType]; ok { 647 return i.trace 648 } 649 panic(fmt.Sprintf("Unknown workflowType %v, known types: %v", workflowType, t.instances)) 650 } 651 func (t *tracingInterceptorFactory) NewInterceptor(info *workflow.Info, next interceptors.WorkflowInterceptor) interceptors.WorkflowInterceptor { 652 t.Mutex.Lock() 653 defer t.Mutex.Unlock() 654 result := &tracingInterceptor{ 655 WorkflowInterceptorBase: interceptors.WorkflowInterceptorBase{Next: next}, 656 } 657 t.instances[info.WorkflowType.Name] = result 658 return result 659 } 660 661 var _ interceptors.WorkflowInterceptor = (*tracingInterceptor)(nil) 662 663 type tracingInterceptor struct { 664 interceptors.WorkflowInterceptorBase 665 trace []string 666 } 667 668 func (t *tracingInterceptor) ExecuteActivity(ctx workflow.Context, activityType string, args ...interface{}) workflow.Future { 669 t.trace = append(t.trace, "ExecuteActivity") 670 return t.Next.ExecuteActivity(ctx, activityType, args...) 671 } 672 673 func (t *tracingInterceptor) ExecuteChildWorkflow(ctx workflow.Context, childWorkflowType string, args ...interface{}) workflow.ChildWorkflowFuture { 674 t.trace = append(t.trace, "ExecuteChildWorkflow") 675 return t.Next.ExecuteChildWorkflow(ctx, childWorkflowType, args...) 676 } 677 678 func (t *tracingInterceptor) ExecuteWorkflow(ctx workflow.Context, workflowType string, args ...interface{}) []interface{} { 679 t.trace = append(t.trace, "ExecuteWorkflow begin") 680 result := t.Next.ExecuteWorkflow(ctx, workflowType, args...) 681 t.trace = append(t.trace, "ExecuteWorkflow end") 682 return result 683 }