func TestOnSignalReceived(t *testing.T) { signal := "the-signal" ctx := deciderTestContext() decider := Transition("some-state") composedDecider := OnSignalReceived(signal, decider) for _, e := range s.SWFHistoryEventTypes() { switch e { case swf.EventTypeWorkflowExecutionSignaled: event := &swf.HistoryEvent{ EventType: s.S(e), EventId: s.L(129), WorkflowExecutionSignaledEventAttributes: &swf.WorkflowExecutionSignaledEventAttributes{ SignalName: s.S("the-signal"), }, } data := &TestingType{Field: "yes"} outcome := composedDecider(ctx, event, data) expected := decider(ctx, event, data) if !reflect.DeepEqual(outcome, expected) { t.Fatal("Outcomes not equal", outcome, expected) } default: event := &swf.HistoryEvent{ EventType: s.S(e), } outcome := composedDecider(ctx, event, nil) if !reflect.DeepEqual(outcome, ctx.Pass()) { t.Fatal("Outcome does not equal Pass", outcome) } } } }
func testContextWithSignal(scheduledEventId int, event *swf.SignalExternalWorkflowExecutionInitiatedEventAttributes) func() *FSMContext { return func() *FSMContext { correlator := &EventCorrelator{} correlator.Track(&swf.HistoryEvent{ EventId: s.I(scheduledEventId), EventType: s.S(swf.EventTypeSignalExternalWorkflowExecutionInitiated), SignalExternalWorkflowExecutionInitiatedEventAttributes: event, }) return NewFSMContext(nil, swf.WorkflowType{Name: s.S("foo"), Version: s.S("1")}, swf.WorkflowExecution{WorkflowId: s.S("id"), RunId: s.S("runid")}, correlator, "state", nil, 1) } }
func TestOnChildStarted(t *testing.T) { decider := func(ctx *FSMContext, h *swf.HistoryEvent, data interface{}) Outcome { return ctx.Goto("some-state", data, ctx.EmptyDecisions()) } composedDecider := OnChildStarted(decider) for _, et := range s.SWFHistoryEventTypes() { ctx := deciderTestContext() switch et { case enums.EventTypeChildWorkflowExecutionStarted: event := s.EventFromPayload(129, &swf.ChildWorkflowExecutionStartedEventAttributes{}) data := new(TestData) outcome := composedDecider(ctx, event, data) expected := decider(ctx, event, data) if !reflect.DeepEqual(outcome, expected) { t.Fatal("Outcomes not equal", outcome, expected) } default: event := &swf.HistoryEvent{ EventType: s.S(et), } if composedDecider(ctx, event, new(TestData)).State != "" { t.Fatal("Non nil decision") } } } }
func TestOnData(t *testing.T) { decider := Transition("some-state") typed := Typed(new(TestingType)) data := &TestingType{Field: "yes"} ctx := deciderTestContext() predicate := typed.PredicateFunc(func(data *TestingType) bool { return data.Field == "yes" }) composedDecider := OnData(predicate, decider) event := &swf.HistoryEvent{ EventId: s.L(129), EventTimestamp: aws.Time(time.Now()), EventType: s.S(swf.EventTypeWorkflowExecutionStarted), } outcome := composedDecider(ctx, event, data) expected := decider(ctx, event, data) if !reflect.DeepEqual(outcome, expected) { t.Fatal("Outcomes not equal", outcome, expected) } data.Field = "nope" outcome = composedDecider(ctx, event, data) if reflect.DeepEqual(outcome, expected) { t.Fatal("Outcomes should not be equal", outcome, expected) } }
// DefaultCanceledState is the canceled state used in an FSM if one has not been set. // It simply responds with a CancelWorkflow which attempts to Cancel the workflow. // This state will only get events if you previously attempted to cancel the workflow and it failed. func (f *FSM) DefaultCanceledState() *FSMState { return &FSMState{ Name: CanceledState, Decider: func(fsm *FSMContext, h *swf.HistoryEvent, data interface{}) Outcome { f.log("state=complete at=attempt-cancel event=%s", h) return fsm.CancelWorkflow(data, s.S("default-canceled-state")) }, } }
func TestFailWorkflow(t *testing.T) { // arrange data := &TestingType{"Some data"} fsmContext := NewFSMContext(nil, swf.WorkflowType{Name: s.S("foo"), Version: s.S("1")}, swf.WorkflowExecution{WorkflowId: s.S("id"), RunId: s.S("runid")}, nil, "state", nil, 1) details := "Some failure message" // act failDecider := FailWorkflow(s.S(details)) outcome := failDecider(fsmContext, nil, data) // assert assert.Equal(t, data, outcome.Data, "Expected data to be passed into failed outcome") failDecision := FindDecision(outcome.Decisions, failWorkflowPredicate) assert.NotNil(t, failDecision, "Expected to find a fail workflow decision in the outcome") assert.Equal(t, details, *failDecision.FailWorkflowExecutionDecisionAttributes.Details, "Expected details in the fail decision to match what was passed in") }
func TestOnActivityFailedTimedOutCanceled(t *testing.T) { activity := "test-activity" decider := Transition("some-state") composedDecider := OnActivityFailedTimedOutCanceled(activity, decider) ctx := testContextWithActivity(123, &swf.ActivityTaskScheduledEventAttributes{ ActivityId: s.S("the-id"), ActivityType: &swf.ActivityType{ Name: s.S(activity), Version: s.S("1"), }, }, ) for _, e := range s.SWFHistoryEventTypes() { switch e { case swf.EventTypeActivityTaskFailed, swf.EventTypeActivityTaskTimedOut, swf.EventTypeActivityTaskCanceled: event := &swf.HistoryEvent{ EventType: s.S(e), EventId: s.L(129), ActivityTaskCanceledEventAttributes: &swf.ActivityTaskCanceledEventAttributes{ScheduledEventId: s.L(123)}, ActivityTaskFailedEventAttributes: &swf.ActivityTaskFailedEventAttributes{ScheduledEventId: s.L(123)}, ActivityTaskTimedOutEventAttributes: &swf.ActivityTaskTimedOutEventAttributes{ScheduledEventId: s.L(123)}, } data := &TestingType{Field: "yes"} outcome := composedDecider(ctx(), event, data) expected := decider(ctx(), event, data) if !reflect.DeepEqual(outcome, expected) { t.Fatal("Outcomes not equal", outcome, expected) } default: event := &swf.HistoryEvent{ EventType: s.S(e), } outcome := composedDecider(ctx(), event, nil) if !reflect.DeepEqual(outcome, ctx().Pass()) { t.Fatal("Outcome does not equal Pass", outcome) } } } }
func TestFSMWhenStartingNewWorkflowExpectsPreprocScheduled(t *testing.T) { // arrange // {"CustomerId": 1, "SimulationResultId": 172, "S3SimulationFolder": "Customer_1/simulation172_2016-01-22"} simulationStateManager := &simulationStateManager{} simulationStateManager.FSM = simulationStateManager.setupFSM() events := []*swf.HistoryEvent{ &swf.HistoryEvent{EventType: sugar.S("DecisionTaskStarted"), EventId: sugar.I(3)}, &swf.HistoryEvent{EventType: sugar.S("DecisionTaskScheduled"), EventId: sugar.I(2)}, sugar.EventFromPayload(1, &swf.WorkflowExecutionStartedEventAttributes{ Input: fsm.StartFSMWorkflowInput(simulationStateManager.FSM, testData), }), } firstDecisionTask := testDecisionTask(0, events) // act _, decisions, _, err := simulationStateManager.Tick(firstDecisionTask) // assert assert.Nil(t, err, "Should be no errors") assert.NotNil(t, decisions, "Should have returned some decisions") assert.True(t, Find(decisions, scheduleActivityPredicateFunc("preproc")), "Should have scheduled preproc") }
func TestFSMWhenInPreprocStateAndPreprocFailsExpectsWorkflowFailed(t *testing.T) { // arrange simulationStateManager := &simulationStateManager{} simulationStateManager.FSM = simulationStateManager.setupFSM() serializedState := &fsm.SerializedState{} serializedState.StateName = "preproc" serializedState.StateData = simulationStateManager.FSM.Serialize(testData) serializedState.StateVersion = 1 markerRecordedEvent := sugar.EventFromPayload(5, &swf.MarkerRecordedEventAttributes{ MarkerName: sugar.S(fsm.StateMarker), Details: sugar.S(simulationStateManager.FSM.Serialize(serializedState)), }) events := []*swf.HistoryEvent{ &swf.HistoryEvent{EventType: sugar.S("DecisionTaskStarted"), EventId: sugar.I(9)}, &swf.HistoryEvent{EventType: sugar.S("DecisionTaskScheduled"), EventId: sugar.I(8)}, sugar.EventFromPayload(7, &swf.ActivityTaskFailedEventAttributes{ ScheduledEventId: sugar.I(6), }), sugar.EventFromPayload(6, &swf.ActivityTaskScheduledEventAttributes{ ActivityId: sugar.S(testActivityInfo.ActivityId), ActivityType: testActivityInfo.ActivityType, }), markerRecordedEvent, } first := testDecisionTask(5, events) // act _, decisions, _, err := simulationStateManager.Tick(first) // assert assert.Nil(t, err, "Should be no errors") assert.NotNil(t, decisions, "Should have returned some decisions") assert.True(t, Find(decisions, workflowCancelledPredicate), "Should have failed the workflow because preproc failed") }
func DecisionsToEvents(decisions []*swf.Decision) []*swf.HistoryEvent { var events []*swf.HistoryEvent for _, d := range decisions { if scheduleActivityPredicate(d) { event := &swf.HistoryEvent{ EventType: sugar.S("ActivityTaskCompleted"), EventId: sugar.I(7), ActivityTaskCompletedEventAttributes: &swf.ActivityTaskCompletedEventAttributes{ ScheduledEventId: sugar.I(6), }, } events = append(events, event) event = &swf.HistoryEvent{ EventType: sugar.S("ActivityTaskScheduled"), EventId: sugar.I(6), ActivityTaskScheduledEventAttributes: &swf.ActivityTaskScheduledEventAttributes{ ActivityId: sugar.S(testActivityInfo.ActivityId), ActivityType: testActivityInfo.ActivityType, }, } events = append(events, event) } if stateMarkerPredicate(d) { event := &swf.HistoryEvent{ EventType: sugar.S("MarkerRecorded"), EventId: sugar.I(5), MarkerRecordedEventAttributes: &swf.MarkerRecordedEventAttributes{ MarkerName: sugar.S(fsm.StateMarker), Details: d.RecordMarkerDecisionAttributes.Details, }, } events = append(events, event) } } return events }
func (s *simulationStateManager) startWorkflowExecution() error { _, err := s.swfAPI.StartWorkflowExecution(&swf.StartWorkflowExecutionInput{ Domain: sugar.S(config.Viper.GetString("env")), WorkflowId: sugar.S(uuid.NewV4().String()), ExecutionStartToCloseTimeout: sugar.S("120"), TaskStartToCloseTimeout: sugar.S("120"), ChildPolicy: sugar.S(swf.ChildPolicyTerminate), //you will have previously regiestered a WorkflowType that this FSM will work. WorkflowType: &swf.WorkflowType{Name: sugar.S(config.Workflow.Name), Version: sugar.S(config.Workflow.Version)}, Input: fsm.StartFSMWorkflowInput(s.FSM, &StateData{SimulationResultID: 10, CustomerID: 1, S3SimulationFolder: "folder/test"}), }) if err != nil { panic(err) } return nil }
func TestFSMWhenInPreprocStateAndPreprocSuccessfullyCompletesExpectsWorkflowComplete(t *testing.T) { // arrange simulationStateManager := &simulationStateManager{} simulationStateManager.FSM = simulationStateManager.setupFSM() serializedState := &fsm.SerializedState{} serializedState.StateName = "preproc" serializedState.StateData = simulationStateManager.FSM.Serialize(testData) serializedState.StateVersion = 1 markerRecordedEvent := sugar.EventFromPayload(5, &swf.MarkerRecordedEventAttributes{ MarkerName: sugar.S(fsm.StateMarker), Details: sugar.S(simulationStateManager.FSM.Serialize(serializedState)), }) preprocResult := `{"CustomerId":1,"SimulationResultId":172,"S3SimulationFolder":"Customer_1/simulation172_2016-01-22","FileLocation":"Customer_1/simulation172_2016-01-22/b308147a-a73c-493f-bf10-478219419057_scanpattern.zip","ZoxFileLocation":"Customer_1/simulation172_2016-01-22/035c0f01-d53a-4b36-bbaa-21650a0e2a64_voxel.zip","CoarseZoxFileLocation":"Customer_1/simulation172_2016-01-22/035c0f01-d53a-4b36-bbaa-21650a0e2a64_voxel.zip","MediumZoxFileLocation":"Customer_1/simulation172_2016-01-22/035c0f01-d53a-4b36-bbaa-21650a0e2a64_voxel.zip","FineZoxFileLocation":"Customer_1/simulation172_2016-01-22/035c0f01-d53a-4b36-bbaa-21650a0e2a64_voxel.zip","sizeX":0.001,"sizeY":0.001,"sizeZ":0.001}` events := []*swf.HistoryEvent{ &swf.HistoryEvent{EventType: sugar.S("DecisionTaskStarted"), EventId: sugar.I(9)}, &swf.HistoryEvent{EventType: sugar.S("DecisionTaskScheduled"), EventId: sugar.I(8)}, sugar.EventFromPayload(7, &swf.ActivityTaskCompletedEventAttributes{ ScheduledEventId: sugar.I(6), Result: sugar.S(preprocResult), }), sugar.EventFromPayload(6, &swf.ActivityTaskScheduledEventAttributes{ ActivityId: sugar.S(testActivityInfo.ActivityId), ActivityType: testActivityInfo.ActivityType, }), markerRecordedEvent, } first := testDecisionTask(5, events) // act _, decisions, _, err := simulationStateManager.Tick(first) // assert assert.Nil(t, err, "Should be no errors") assert.NotNil(t, decisions, "Should have returned some decisions") assert.True(t, Find(decisions, workflowCompletedPredicate), "Should have completed the workflow") workflowCopmletedDecision := FindDecision(decisions, workflowCompletedPredicate) assert.Equal(t, preprocResult, *workflowCopmletedDecision.CompleteWorkflowExecutionDecisionAttributes.Result, "Should have passed the result from preproc onto next decision") }
func deciderTestContext() *FSMContext { return NewFSMContext(nil, swf.WorkflowType{Name: s.S("foo"), Version: s.S("1")}, swf.WorkflowExecution{WorkflowID: s.S("id"), RunID: s.S("runid")}, nil, "state", nil, 1) }
package simulationworkflow import ( // "github.com/3dsim/workflow/logger" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/swf" "github.com/sclasen/swfsm/fsm" "github.com/sclasen/swfsm/sugar" "github.com/stretchr/testify/assert" "time" "testing" ) var testActivityInfo = fsm.ActivityInfo{ActivityId: "activityId", ActivityType: &swf.ActivityType{Name: sugar.S("activity"), Version: sugar.S("activityVersion")}} var testWorkflowExecution = &swf.WorkflowExecution{WorkflowId: sugar.S("workflow-id"), RunId: sugar.S("run-id")} var testWorkflowType = &swf.WorkflowType{Name: sugar.S("workflow-name"), Version: sugar.S("workflow-version")} var testData = &StateData{ CustomerID: 1, S3SimulationFolder: "Customer_1/simulation172_2016-01-22", SimulationResultID: 172, } func TestFSMWhenStartingNewWorkflowExpectsPreprocScheduled(t *testing.T) { // arrange // {"CustomerId": 1, "SimulationResultId": 172, "S3SimulationFolder": "Customer_1/simulation172_2016-01-22"} simulationStateManager := &simulationStateManager{} simulationStateManager.FSM = simulationStateManager.setupFSM()
decisions := f.EmptyDecisions() switch *lastEvent.EventType { case swf.EventTypeActivityTaskCompleted: logger.Log.Info("Previous scheduled activity completed successfully") decision := &swf.Decision{} decision.DecisionType = aws.String(swf.DecisionTypeCompleteWorkflowExecution) decision.CompleteWorkflowExecutionDecisionAttributes = &swf.CompleteWorkflowExecutionDecisionAttributes{ Result: lastEvent.ActivityTaskCompletedEventAttributes.Result, } decisions = append(decisions, decision) case swf.EventTypeActivityTaskFailed: logger.Log.Error("Activity task failed", "lastEvent", lastEvent) decision := &swf.Decision{} decision.DecisionType = aws.String(swf.DecisionTypeFailWorkflowExecution) decision.FailWorkflowExecutionDecisionAttributes = &swf.FailWorkflowExecutionDecisionAttributes{ Reason: sugar.S("Preproc failed"), } decisions = append(decisions, decision) } //if the event was unexpected just stay here return f.Stay(stateData, decisions) } func (s *simulationStateManager) startWorkflowExecution() error { _, err := s.swfAPI.StartWorkflowExecution(&swf.StartWorkflowExecutionInput{ Domain: sugar.S(config.Viper.GetString("env")), WorkflowId: sugar.S(uuid.NewV4().String()), ExecutionStartToCloseTimeout: sugar.S("120"), TaskStartToCloseTimeout: sugar.S("120"), ChildPolicy: sugar.S(swf.ChildPolicyTerminate), //you will have previously regiestered a WorkflowType that this FSM will work.