From 7cd0f80178de8a4760dd8f42f2d8490f5ade4dd6 Mon Sep 17 00:00:00 2001 From: Nate Mortensen Date: Wed, 17 Apr 2024 18:59:54 -0700 Subject: [PATCH] Add tests for persistence/workflow_execution_info.go (#5918) * Add tests for persistence/workflow_execution_info.go Additionally rename file to match snake_case standard. * Fix formatting issues --- ...tionInfo.go => workflow_execution_info.go} | 0 .../workflow_execution_info_test.go | 259 ++++++++++++++++++ 2 files changed, 259 insertions(+) rename common/persistence/{workflowExecutionInfo.go => workflow_execution_info.go} (100%) create mode 100644 common/persistence/workflow_execution_info_test.go diff --git a/common/persistence/workflowExecutionInfo.go b/common/persistence/workflow_execution_info.go similarity index 100% rename from common/persistence/workflowExecutionInfo.go rename to common/persistence/workflow_execution_info.go diff --git a/common/persistence/workflow_execution_info_test.go b/common/persistence/workflow_execution_info_test.go new file mode 100644 index 0000000000..dd205f8519 --- /dev/null +++ b/common/persistence/workflow_execution_info_test.go @@ -0,0 +1,259 @@ +// The MIT License (MIT) + +// Copyright (c) 2017-2020 Uber Technologies Inc. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package persistence + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + workflowStates = map[string]int{ + "Void": WorkflowStateVoid, + "Created": WorkflowStateCreated, + "Running": WorkflowStateRunning, + "Completed": WorkflowStateCompleted, + "Zombie": WorkflowStateZombie, + } + workflowCloseStatuses = map[string]int{ + "None": WorkflowCloseStatusNone, + "Completed": WorkflowCloseStatusCompleted, + "Failed": WorkflowCloseStatusFailed, + "Canceled": WorkflowCloseStatusCanceled, + "Terminated": WorkflowCloseStatusTerminated, + "ContinuedAsNew": WorkflowCloseStatusContinuedAsNew, + "TimedOut": WorkflowCloseStatusTimedOut, + } +) + +func TestWorkflowExecutionInfoSetNextEventID(t *testing.T) { + info := &WorkflowExecutionInfo{NextEventID: 1} + info.SetNextEventID(2) + + assert.Equal(t, int64(2), info.NextEventID) +} + +func TestWorkflowExecutionInfoIncreaseNextEventID(t *testing.T) { + info := &WorkflowExecutionInfo{NextEventID: 1} + + info.IncreaseNextEventID() + + assert.Equal(t, int64(2), info.NextEventID) +} + +func TestWorkflowExecutionInfoSetLastFirstEventID(t *testing.T) { + info := &WorkflowExecutionInfo{LastFirstEventID: 1} + + info.SetLastFirstEventID(2) + + assert.Equal(t, int64(2), info.LastFirstEventID) +} + +func TestWorkflowExecutionInfoIsRunning(t *testing.T) { + tests := map[string]struct { + state int + expected bool + }{ + "Created": { + WorkflowStateCreated, true, + }, + "Running": { + WorkflowStateRunning, true, + }, + "Completed": { + WorkflowStateCompleted, false, + }, + "Zombie": { + WorkflowStateZombie, false, + }, + "Corrupted": { + WorkflowStateCorrupted, false, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + info := &WorkflowExecutionInfo{ + State: test.state, + } + assert.Equal(t, test.expected, info.IsRunning()) + }) + } +} + +type transitionChecker func(executionInfo *WorkflowExecutionInfo, toState, closeStatus int) bool +type stateChecker func(executionInfo *WorkflowExecutionInfo, closeStatus int) bool +type testCase struct { + name string + fromState int + fromStatus int + toState int + toStatus int +} + +func TestWorkflowExecutionInfoUpdateWorkflowStateCloseStatus(t *testing.T) { + expectedTransitions := map[int]transitionChecker{ + WorkflowStateVoid: toAnyState(), + WorkflowStateCreated: toStates(map[int]stateChecker{ + WorkflowStateCreated: withCloseStatusNone(), + WorkflowStateRunning: withCloseStatusNone(), + WorkflowStateCompleted: withCloseStatus(WorkflowCloseStatusTerminated, WorkflowCloseStatusTimedOut, WorkflowCloseStatusContinuedAsNew), + WorkflowStateZombie: withCloseStatusNone(), + }), + WorkflowStateRunning: toStates(map[int]stateChecker{ + WorkflowStateRunning: withCloseStatusNone(), + WorkflowStateCompleted: withAnyCloseStatusExcept(WorkflowCloseStatusNone), + WorkflowStateZombie: withCloseStatusNone(), + }), + WorkflowStateCompleted: toStates(map[int]stateChecker{ + WorkflowStateCompleted: func(executionInfo *WorkflowExecutionInfo, closeStatus int) bool { + return executionInfo.CloseStatus == closeStatus + }, + }), + WorkflowStateZombie: toStates(map[int]stateChecker{ + WorkflowStateCreated: withCloseStatusNone(), + WorkflowStateRunning: withCloseStatusNone(), + WorkflowStateCompleted: withAnyCloseStatusExcept(WorkflowCloseStatusNone), + WorkflowStateZombie: withAnyCloseStatusExcept(WorkflowCloseStatusNone), + }), + } + var testCases []*testCase + + // From every state with a CloseStatus of None to every state and close status + for fromStateName, fromState := range workflowStates { + for toStateName, toState := range workflowStates { + for closeStatusName, closeStatus := range workflowCloseStatuses { + testCases = append(testCases, &testCase{ + name: fmt.Sprintf("(%s,None)->(%s,%s)", fromStateName, toStateName, closeStatusName), + fromState: fromState, + fromStatus: WorkflowCloseStatusNone, + toState: toState, + toStatus: closeStatus, + }) + } + } + } + // Ensure Completed doesn't allow for changing the Status + testCases = append(testCases, &testCase{ + name: "(Completed,Failed)->(Completed, Completed)", + fromState: WorkflowStateCompleted, + fromStatus: WorkflowCloseStatusFailed, + toState: WorkflowStateCompleted, + toStatus: WorkflowCloseStatusCompleted, + }) + testCases = append(testCases, &testCase{ + name: "(Completed,Completed)->(Completed,Completed)", + fromState: WorkflowStateCompleted, + fromStatus: WorkflowCloseStatusCompleted, + toState: WorkflowStateCompleted, + toStatus: WorkflowCloseStatusCompleted, + }) + // Invalid states + testCases = append(testCases, &testCase{ + name: "(Completed,Completed)->(?,Completed)", + fromState: WorkflowStateCompleted, + fromStatus: WorkflowCloseStatusCompleted, + toState: 100000, + toStatus: WorkflowCloseStatusCompleted, + }) + testCases = append(testCases, &testCase{ + name: "(?,Completed)->(Completed,Completed)", + fromState: 100000, + fromStatus: WorkflowCloseStatusCompleted, + toState: WorkflowStateCompleted, + toStatus: WorkflowCloseStatusCompleted, + }) + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + executionInfo := &WorkflowExecutionInfo{ + State: test.fromState, + CloseStatus: test.fromStatus, + } + + var noErr bool + if isValid, ok := expectedTransitions[test.fromState]; ok { + noErr = isValid(executionInfo, test.toState, test.toStatus) + } else { + noErr = false + } + + err := executionInfo.UpdateWorkflowStateCloseStatus(test.toState, test.toStatus) + + if noErr { + assert.NoError(t, err) + assert.Equal(t, test.toState, executionInfo.State) + assert.Equal(t, test.toStatus, executionInfo.CloseStatus) + } else { + assert.Error(t, err) + } + + }) + } +} + +func toAnyState() transitionChecker { + return func(workflowExecutionInfo *WorkflowExecutionInfo, toState, closeStatus int) bool { + return true + } +} + +func toStates(validStates map[int]stateChecker) transitionChecker { + return func(workflowExecutionInfo *WorkflowExecutionInfo, toState, closeStatus int) bool { + if checker, ok := validStates[toState]; ok { + return checker(workflowExecutionInfo, closeStatus) + } + return false + } +} + +func withCloseStatusNone() stateChecker { + return withCloseStatus(WorkflowCloseStatusNone) +} + +func withCloseStatus(closeStatuses ...int) stateChecker { + return func(workflowExecutionInfo *WorkflowExecutionInfo, closeStatus int) bool { + return contains(closeStatuses, closeStatus) + } +} + +func withAnyCloseStatusExcept(closeStatuses ...int) stateChecker { + return func(workflowExecutionInfo *WorkflowExecutionInfo, closeStatus int) bool { + for _, v := range closeStatuses { + if v == closeStatus { + return false + } + } + return true + } +} + +func contains(slice []int, value int) bool { + for _, v := range slice { + if v == value { + return true + } + } + return false +}