go.uber.org/cadence@v1.2.9/internal/internal_decision_state_machine_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 "testing" 25 26 "github.com/stretchr/testify/require" 27 28 s "go.uber.org/cadence/.gen/go/shared" 29 "go.uber.org/cadence/internal/common" 30 ) 31 32 func Test_TimerStateMachine_CancelBeforeSent(t *testing.T) { 33 t.Parallel() 34 timerID := "test-timer-1" 35 attributes := &s.StartTimerDecisionAttributes{ 36 TimerId: common.StringPtr(timerID), 37 } 38 h := newDecisionsHelper() 39 d := h.startTimer(attributes) 40 require.Equal(t, decisionStateCreated, d.getState()) 41 h.cancelTimer(timerID) 42 require.Equal(t, decisionStateCompleted, d.getState()) 43 decisions := h.getDecisions(true) 44 require.Equal(t, 0, len(decisions)) 45 } 46 47 func Test_TimerStateMachine_CancelAfterInitiated(t *testing.T) { 48 t.Parallel() 49 timerID := "test-timer-1" 50 attributes := &s.StartTimerDecisionAttributes{ 51 TimerId: common.StringPtr(timerID), 52 } 53 h := newDecisionsHelper() 54 d := h.startTimer(attributes) 55 require.Equal(t, decisionStateCreated, d.getState()) 56 decisions := h.getDecisions(true) 57 require.Equal(t, decisionStateDecisionSent, d.getState()) 58 require.Equal(t, 1, len(decisions)) 59 require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType()) 60 require.Equal(t, attributes, decisions[0].StartTimerDecisionAttributes) 61 h.handleTimerStarted(timerID) 62 require.Equal(t, decisionStateInitiated, d.getState()) 63 h.cancelTimer(timerID) 64 require.Equal(t, decisionStateCanceledAfterInitiated, d.getState()) 65 decisions = h.getDecisions(true) 66 require.Equal(t, 1, len(decisions)) 67 require.Equal(t, s.DecisionTypeCancelTimer, decisions[0].GetDecisionType()) 68 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 69 h.handleTimerCanceled(timerID) 70 require.Equal(t, decisionStateCompleted, d.getState()) 71 } 72 73 func Test_TimerStateMachine_CompletedAfterCancel(t *testing.T) { 74 t.Parallel() 75 timerID := "test-timer-1" 76 attributes := &s.StartTimerDecisionAttributes{ 77 TimerId: common.StringPtr(timerID), 78 } 79 h := newDecisionsHelper() 80 d := h.startTimer(attributes) 81 require.Equal(t, decisionStateCreated, d.getState()) 82 decisions := h.getDecisions(true) 83 require.Equal(t, decisionStateDecisionSent, d.getState()) 84 require.Equal(t, 1, len(decisions)) 85 require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType()) 86 h.cancelTimer(timerID) 87 require.Equal(t, decisionStateCanceledBeforeInitiated, d.getState()) 88 require.Equal(t, 0, len(h.getDecisions(true))) 89 h.handleTimerStarted(timerID) 90 require.Equal(t, decisionStateCanceledAfterInitiated, d.getState()) 91 decisions = h.getDecisions(true) 92 require.Equal(t, 1, len(decisions)) 93 require.Equal(t, s.DecisionTypeCancelTimer, decisions[0].GetDecisionType()) 94 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 95 h.handleTimerClosed(timerID) 96 require.Equal(t, decisionStateCompletedAfterCancellationDecisionSent, d.getState()) 97 } 98 99 func Test_TimerStateMachine_CompleteWithoutCancel(t *testing.T) { 100 t.Parallel() 101 timerID := "test-timer-1" 102 attributes := &s.StartTimerDecisionAttributes{ 103 TimerId: common.StringPtr(timerID), 104 } 105 h := newDecisionsHelper() 106 d := h.startTimer(attributes) 107 require.Equal(t, decisionStateCreated, d.getState()) 108 decisions := h.getDecisions(true) 109 require.Equal(t, decisionStateDecisionSent, d.getState()) 110 require.Equal(t, 1, len(decisions)) 111 require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType()) 112 h.handleTimerStarted(timerID) 113 require.Equal(t, decisionStateInitiated, d.getState()) 114 require.Equal(t, 0, len(h.getDecisions(false))) 115 h.handleTimerClosed(timerID) 116 require.Equal(t, decisionStateCompleted, d.getState()) 117 } 118 119 func Test_TimerStateMachine_PanicInvalidStateTransition(t *testing.T) { 120 t.Parallel() 121 timerID := "test-timer-1" 122 attributes := &s.StartTimerDecisionAttributes{ 123 TimerId: common.StringPtr(timerID), 124 } 125 h := newDecisionsHelper() 126 h.startTimer(attributes) 127 h.getDecisions(true) 128 h.handleTimerStarted(timerID) 129 h.handleTimerClosed(timerID) 130 131 panicErr := runAndCatchPanic(func() { 132 h.handleCancelTimerFailed(timerID) 133 }) 134 135 require.NotNil(t, panicErr) 136 } 137 138 func Test_TimerCancelEventOrdering(t *testing.T) { 139 t.Parallel() 140 timerID := "test-timer-1" 141 localActivityID := "test-activity-1" 142 attributes := &s.StartTimerDecisionAttributes{ 143 TimerId: common.StringPtr(timerID), 144 } 145 h := newDecisionsHelper() 146 d := h.startTimer(attributes) 147 require.Equal(t, decisionStateCreated, d.getState()) 148 decisions := h.getDecisions(true) 149 require.Equal(t, decisionStateDecisionSent, d.getState()) 150 require.Equal(t, 1, len(decisions)) 151 require.Equal(t, s.DecisionTypeStartTimer, decisions[0].GetDecisionType()) 152 require.Equal(t, attributes, decisions[0].StartTimerDecisionAttributes) 153 h.handleTimerStarted(timerID) 154 require.Equal(t, decisionStateInitiated, d.getState()) 155 m := h.recordLocalActivityMarker(localActivityID, []byte{}) 156 require.Equal(t, decisionStateCreated, m.getState()) 157 h.cancelTimer(timerID) 158 require.Equal(t, decisionStateCanceledAfterInitiated, d.getState()) 159 decisions = h.getDecisions(true) 160 require.Equal(t, 2, len(decisions)) 161 require.Equal(t, s.DecisionTypeRecordMarker, decisions[0].GetDecisionType()) 162 require.Equal(t, s.DecisionTypeCancelTimer, decisions[1].GetDecisionType()) 163 } 164 165 func Test_ActivityStateMachine_CompleteWithoutCancel(t *testing.T) { 166 t.Parallel() 167 activityID := "test-activity-1" 168 attributes := &s.ScheduleActivityTaskDecisionAttributes{ 169 ActivityId: common.StringPtr(activityID), 170 } 171 h := newDecisionsHelper() 172 173 // schedule activity 174 d := h.scheduleActivityTask(attributes) 175 require.Equal(t, decisionStateCreated, d.getState()) 176 decisions := h.getDecisions(true) 177 require.Equal(t, decisionStateDecisionSent, d.getState()) 178 require.Equal(t, 1, len(decisions)) 179 require.Equal(t, s.DecisionTypeScheduleActivityTask, decisions[0].GetDecisionType()) 180 181 // activity scheduled 182 h.handleActivityTaskScheduled(1, activityID) 183 require.Equal(t, decisionStateInitiated, d.getState()) 184 185 // activity completed 186 h.handleActivityTaskClosed(activityID) 187 require.Equal(t, decisionStateCompleted, d.getState()) 188 } 189 190 func Test_ActivityStateMachine_CancelBeforeSent(t *testing.T) { 191 t.Parallel() 192 activityID := "test-activity-1" 193 attributes := &s.ScheduleActivityTaskDecisionAttributes{ 194 ActivityId: common.StringPtr(activityID), 195 } 196 h := newDecisionsHelper() 197 198 // schedule activity 199 d := h.scheduleActivityTask(attributes) 200 require.Equal(t, decisionStateCreated, d.getState()) 201 202 // cancel before decision sent, this will put decision state machine directly into completed state 203 h.requestCancelActivityTask(activityID) 204 require.Equal(t, decisionStateCompleted, d.getState()) 205 206 // there should be no decisions needed to be send 207 decisions := h.getDecisions(true) 208 require.Equal(t, 0, len(decisions)) 209 } 210 211 func Test_ActivityStateMachine_CancelAfterSent(t *testing.T) { 212 t.Parallel() 213 activityID := "test-activity-1" 214 attributes := &s.ScheduleActivityTaskDecisionAttributes{ 215 ActivityId: common.StringPtr(activityID), 216 } 217 h := newDecisionsHelper() 218 219 // schedule activity 220 d := h.scheduleActivityTask(attributes) 221 require.Equal(t, decisionStateCreated, d.getState()) 222 decisions := h.getDecisions(true) 223 require.Equal(t, 1, len(decisions)) 224 require.Equal(t, s.DecisionTypeScheduleActivityTask, decisions[0].GetDecisionType()) 225 226 // cancel activity 227 h.requestCancelActivityTask(activityID) 228 require.Equal(t, decisionStateCanceledBeforeInitiated, d.getState()) 229 require.Equal(t, 0, len(h.getDecisions(true))) 230 231 // activity scheduled 232 h.handleActivityTaskScheduled(1, activityID) 233 require.Equal(t, decisionStateCanceledAfterInitiated, d.getState()) 234 decisions = h.getDecisions(true) 235 require.Equal(t, 1, len(decisions)) 236 require.Equal(t, s.DecisionTypeRequestCancelActivityTask, decisions[0].GetDecisionType()) 237 238 // activity canceled 239 h.handleActivityTaskCanceled(activityID) 240 require.Equal(t, decisionStateCompleted, d.getState()) 241 require.Equal(t, 0, len(h.getDecisions(false))) 242 } 243 244 func Test_ActivityStateMachine_CompletedAfterCancel(t *testing.T) { 245 t.Parallel() 246 activityID := "test-activity-1" 247 attributes := &s.ScheduleActivityTaskDecisionAttributes{ 248 ActivityId: common.StringPtr(activityID), 249 } 250 h := newDecisionsHelper() 251 252 // schedule activity 253 d := h.scheduleActivityTask(attributes) 254 require.Equal(t, decisionStateCreated, d.getState()) 255 decisions := h.getDecisions(true) 256 require.Equal(t, 1, len(decisions)) 257 require.Equal(t, s.DecisionTypeScheduleActivityTask, decisions[0].GetDecisionType()) 258 259 // cancel activity 260 h.requestCancelActivityTask(activityID) 261 require.Equal(t, decisionStateCanceledBeforeInitiated, d.getState()) 262 require.Equal(t, 0, len(h.getDecisions(true))) 263 264 // activity scheduled 265 h.handleActivityTaskScheduled(1, activityID) 266 require.Equal(t, decisionStateCanceledAfterInitiated, d.getState()) 267 decisions = h.getDecisions(true) 268 require.Equal(t, 1, len(decisions)) 269 require.Equal(t, s.DecisionTypeRequestCancelActivityTask, decisions[0].GetDecisionType()) 270 271 // activity completed after cancel 272 h.handleActivityTaskClosed(activityID) 273 require.Equal(t, decisionStateCompletedAfterCancellationDecisionSent, d.getState()) 274 require.Equal(t, 0, len(h.getDecisions(false))) 275 } 276 277 func Test_ActivityStateMachine_PanicInvalidStateTransition(t *testing.T) { 278 t.Parallel() 279 activityID := "test-activity-1" 280 attributes := &s.ScheduleActivityTaskDecisionAttributes{ 281 ActivityId: common.StringPtr(activityID), 282 } 283 h := newDecisionsHelper() 284 285 // schedule activity 286 h.scheduleActivityTask(attributes) 287 288 // verify that using invalid activity id will panic 289 err := runAndCatchPanic(func() { 290 h.handleActivityTaskClosed("invalid-activity-id") 291 }) 292 require.NotNil(t, err) 293 294 // send schedule decision 295 h.getDecisions(true) 296 // activity scheduled 297 h.handleActivityTaskScheduled(1, activityID) 298 299 // now simulate activity canceled, which is invalid transition 300 err = runAndCatchPanic(func() { 301 h.handleActivityTaskCanceled(activityID) 302 }) 303 require.NotNil(t, err) 304 } 305 306 func Test_ChildWorkflowStateMachine_Basic(t *testing.T) { 307 t.Parallel() 308 workflowID := "test-child-workflow-1" 309 attributes := &s.StartChildWorkflowExecutionDecisionAttributes{ 310 WorkflowId: common.StringPtr(workflowID), 311 } 312 h := newDecisionsHelper() 313 314 // start child workflow 315 d := h.startChildWorkflowExecution(attributes) 316 require.Equal(t, decisionStateCreated, d.getState()) 317 318 // send decision 319 decisions := h.getDecisions(true) 320 require.Equal(t, decisionStateDecisionSent, d.getState()) 321 require.Equal(t, 1, len(decisions)) 322 require.Equal(t, s.DecisionTypeStartChildWorkflowExecution, decisions[0].GetDecisionType()) 323 324 // child workflow initiated 325 h.handleStartChildWorkflowExecutionInitiated(workflowID) 326 require.Equal(t, decisionStateInitiated, d.getState()) 327 require.Equal(t, 0, len(h.getDecisions(true))) 328 329 // child workflow started 330 h.handleChildWorkflowExecutionStarted(workflowID) 331 require.Equal(t, decisionStateStarted, d.getState()) 332 require.Equal(t, 0, len(h.getDecisions(true))) 333 334 // child workflow completed 335 h.handleChildWorkflowExecutionClosed(workflowID) 336 require.Equal(t, decisionStateCompleted, d.getState()) 337 require.Equal(t, 0, len(h.getDecisions(true))) 338 } 339 340 func Test_ChildWorkflowStateMachine_CancelSucceed(t *testing.T) { 341 t.Parallel() 342 domain := "test-domain" 343 workflowID := "test-child-workflow" 344 runID := "" 345 cancellationID := "" 346 initiatedEventID := int64(28) 347 isChildWorkflowOnly := true 348 attributes := &s.StartChildWorkflowExecutionDecisionAttributes{ 349 WorkflowId: common.StringPtr(workflowID), 350 } 351 h := newDecisionsHelper() 352 353 // start child workflow 354 d := h.startChildWorkflowExecution(attributes) 355 // send decision 356 decisions := h.getDecisions(true) 357 // child workflow initiated 358 h.handleStartChildWorkflowExecutionInitiated(workflowID) 359 // child workflow started 360 h.handleChildWorkflowExecutionStarted(workflowID) 361 362 // cancel child workflow 363 h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, isChildWorkflowOnly) 364 require.Equal(t, decisionStateCanceledAfterStarted, d.getState()) 365 366 // send cancel request 367 decisions = h.getDecisions(true) 368 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 369 require.Equal(t, 1, len(decisions)) 370 require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType()) 371 372 // cancel request initiated 373 h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID) 374 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 375 376 // cancel request accepted 377 h.handleExternalWorkflowExecutionCancelRequested(initiatedEventID, workflowID) 378 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 379 380 // child workflow canceled 381 h.handleChildWorkflowExecutionCanceled(workflowID) 382 require.Equal(t, decisionStateCompleted, d.getState()) 383 } 384 385 func Test_ChildWorkflowStateMachine_InvalidStates(t *testing.T) { 386 t.Parallel() 387 domain := "test-domain" 388 workflowID := "test-workflow-id" 389 runID := "" 390 attributes := &s.StartChildWorkflowExecutionDecisionAttributes{ 391 WorkflowId: common.StringPtr(workflowID), 392 } 393 cancellationID := "" 394 initiatedEventID := int64(28) 395 isChildWorkflowOnly := true 396 h := newDecisionsHelper() 397 398 // start child workflow 399 d := h.startChildWorkflowExecution(attributes) 400 require.Equal(t, decisionStateCreated, d.getState()) 401 402 // invalid: start child workflow failed before decision was sent 403 err := runAndCatchPanic(func() { 404 h.handleStartChildWorkflowExecutionFailed(workflowID) 405 }) 406 require.NotNil(t, err) 407 408 // send decision 409 decisions := h.getDecisions(true) 410 require.Equal(t, decisionStateDecisionSent, d.getState()) 411 require.Equal(t, 1, len(decisions)) 412 413 // invalid: child workflow completed before it was initiated 414 err = runAndCatchPanic(func() { 415 h.handleChildWorkflowExecutionClosed(workflowID) 416 }) 417 require.NotNil(t, err) 418 419 // child workflow initiated 420 h.handleStartChildWorkflowExecutionInitiated(workflowID) 421 require.Equal(t, decisionStateInitiated, d.getState()) 422 423 h.handleChildWorkflowExecutionStarted(workflowID) 424 require.Equal(t, decisionStateStarted, d.getState()) 425 // invalid: cancel child workflow failed before cancel request 426 err = runAndCatchPanic(func() { 427 h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID) 428 }) 429 require.NotNil(t, err) 430 431 // cancel child workflow after child workflow is started 432 h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, isChildWorkflowOnly) 433 require.Equal(t, decisionStateCanceledAfterStarted, d.getState()) 434 435 // send cancel request 436 decisions = h.getDecisions(true) 437 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 438 require.Equal(t, 1, len(decisions)) 439 require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType()) 440 441 // invalid: start child workflow failed after it was already started 442 err = runAndCatchPanic(func() { 443 h.handleStartChildWorkflowExecutionFailed(workflowID) 444 }) 445 require.NotNil(t, err) 446 447 // invalid: child workflow initiated again 448 err = runAndCatchPanic(func() { 449 h.handleStartChildWorkflowExecutionInitiated(workflowID) 450 }) 451 require.NotNil(t, err) 452 453 // cancel request initiated 454 h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID) 455 require.Equal(t, decisionStateCancellationDecisionSent, d.getState()) 456 457 // child workflow completed 458 h.handleChildWorkflowExecutionClosed(workflowID) 459 require.Equal(t, decisionStateCompletedAfterCancellationDecisionSent, d.getState()) 460 461 // invalid: child workflow canceled after it was completed 462 err = runAndCatchPanic(func() { 463 h.handleChildWorkflowExecutionCanceled(workflowID) 464 }) 465 require.NotNil(t, err) 466 } 467 468 func Test_ChildWorkflowStateMachine_CancelFailed(t *testing.T) { 469 t.Parallel() 470 domain := "test-domain" 471 workflowID := "test-workflow-id" 472 runID := "" 473 attributes := &s.StartChildWorkflowExecutionDecisionAttributes{ 474 WorkflowId: common.StringPtr(workflowID), 475 } 476 cancellationID := "" 477 initiatedEventID := int64(28) 478 isChildWorkflowOnly := true 479 h := newDecisionsHelper() 480 481 // start child workflow 482 d := h.startChildWorkflowExecution(attributes) 483 // send decision 484 h.getDecisions(true) 485 // child workflow initiated 486 h.handleStartChildWorkflowExecutionInitiated(workflowID) 487 // child workflow started 488 h.handleChildWorkflowExecutionStarted(workflowID) 489 // cancel child workflow 490 h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, isChildWorkflowOnly) 491 // send cancel request 492 h.getDecisions(true) 493 // cancel request initiated 494 h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID) 495 496 // cancel request failed 497 h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID) 498 require.Equal(t, decisionStateStarted, d.getState()) 499 500 // child workflow completed 501 h.handleChildWorkflowExecutionClosed(workflowID) 502 require.Equal(t, decisionStateCompleted, d.getState()) 503 } 504 505 func Test_MarkerStateMachine(t *testing.T) { 506 t.Parallel() 507 h := newDecisionsHelper() 508 509 // record marker for side effect 510 d := h.recordSideEffectMarker(1, []byte{}) 511 require.Equal(t, decisionStateCreated, d.getState()) 512 513 // send decisions 514 decisions := h.getDecisions(true) 515 require.Equal(t, decisionStateCompleted, d.getState()) 516 require.Equal(t, 1, len(decisions)) 517 require.Equal(t, s.DecisionTypeRecordMarker, decisions[0].GetDecisionType()) 518 } 519 520 func Test_UpsertSearchAttributesDecisionStateMachine(t *testing.T) { 521 t.Parallel() 522 h := newDecisionsHelper() 523 524 attr := &s.SearchAttributes{} 525 d := h.upsertSearchAttributes("1", attr) 526 require.Equal(t, decisionStateCreated, d.getState()) 527 528 decisions := h.getDecisions(true) 529 require.Equal(t, decisionStateCompleted, d.getState()) 530 require.Equal(t, 1, len(decisions)) 531 require.Equal(t, s.DecisionTypeUpsertWorkflowSearchAttributes, decisions[0].GetDecisionType()) 532 } 533 534 func Test_CancelExternalWorkflowStateMachine_Succeed(t *testing.T) { 535 t.Parallel() 536 domain := "test-domain" 537 workflowID := "test-workflow-id" 538 runID := "test-run-id" 539 cancellationID := "1" 540 initiatedEventID := int64(28) 541 childWorkflowOnly := false 542 h := newDecisionsHelper() 543 544 // request cancel external workflow 545 decision := h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, childWorkflowOnly) 546 require.False(t, decision.isDone()) 547 d := h.getDecision(makeDecisionID(decisionTypeCancellation, cancellationID)) 548 require.Equal(t, decisionStateCreated, d.getState()) 549 550 // send decisions 551 decisions := h.getDecisions(true) 552 require.Equal(t, 1, len(decisions)) 553 require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType()) 554 require.Equal( 555 t, 556 &s.RequestCancelExternalWorkflowExecutionDecisionAttributes{ 557 Domain: common.StringPtr(domain), 558 WorkflowId: common.StringPtr(workflowID), 559 RunId: common.StringPtr(runID), 560 Control: []byte(cancellationID), 561 ChildWorkflowOnly: common.BoolPtr(childWorkflowOnly), 562 }, 563 decisions[0].RequestCancelExternalWorkflowExecutionDecisionAttributes, 564 ) 565 566 // cancel request initiated 567 h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID) 568 require.Equal(t, decisionStateInitiated, d.getState()) 569 570 // cancel requested 571 h.handleExternalWorkflowExecutionCancelRequested(initiatedEventID, workflowID) 572 require.Equal(t, decisionStateCompleted, d.getState()) 573 574 // mark the cancel request failed now will make it invalid state transition 575 err := runAndCatchPanic(func() { 576 h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID) 577 }) 578 require.NotNil(t, err) 579 } 580 581 func Test_CancelExternalWorkflowStateMachine_Failed(t *testing.T) { 582 t.Parallel() 583 domain := "test-domain" 584 workflowID := "test-workflow-id" 585 runID := "test-run-id" 586 cancellationID := "2" 587 initiatedEventID := int64(28) 588 childWorkflowOnly := false 589 h := newDecisionsHelper() 590 591 // request cancel external workflow 592 decision := h.requestCancelExternalWorkflowExecution(domain, workflowID, runID, cancellationID, childWorkflowOnly) 593 require.False(t, decision.isDone()) 594 d := h.getDecision(makeDecisionID(decisionTypeCancellation, cancellationID)) 595 require.Equal(t, decisionStateCreated, d.getState()) 596 597 // send decisions 598 decisions := h.getDecisions(true) 599 require.Equal(t, 1, len(decisions)) 600 require.Equal(t, s.DecisionTypeRequestCancelExternalWorkflowExecution, decisions[0].GetDecisionType()) 601 require.Equal( 602 t, 603 &s.RequestCancelExternalWorkflowExecutionDecisionAttributes{ 604 Domain: common.StringPtr(domain), 605 WorkflowId: common.StringPtr(workflowID), 606 RunId: common.StringPtr(runID), 607 Control: []byte(cancellationID), 608 ChildWorkflowOnly: common.BoolPtr(childWorkflowOnly), 609 }, 610 decisions[0].RequestCancelExternalWorkflowExecutionDecisionAttributes, 611 ) 612 613 // cancel request initiated 614 h.handleRequestCancelExternalWorkflowExecutionInitiated(initiatedEventID, workflowID, cancellationID) 615 require.Equal(t, decisionStateInitiated, d.getState()) 616 617 // cancel request failed 618 h.handleRequestCancelExternalWorkflowExecutionFailed(initiatedEventID, workflowID) 619 require.Equal(t, decisionStateCompleted, d.getState()) 620 621 // mark the cancel request succeed now will make it invalid state transition 622 err := runAndCatchPanic(func() { 623 h.handleExternalWorkflowExecutionCancelRequested(initiatedEventID, workflowID) 624 }) 625 require.NotNil(t, err) 626 } 627 628 func runAndCatchPanic(f func()) (err *workflowPanicError) { 629 // panic handler 630 defer func() { 631 if p := recover(); p != nil { 632 topLine := "runAndCatchPanic [panic]:" 633 st := getStackTraceRaw(topLine, 7, 0) 634 err = newWorkflowPanicError(p, st) // Fail decision on panic 635 } 636 }() 637 638 f() 639 return nil 640 }