mirror of https://github.com/certusone/dc4bc.git
317 lines
6.9 KiB
Go
317 lines
6.9 KiB
Go
package fsm
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
testName = "fsm_test"
|
|
// Init process from global idle state
|
|
stateInit = StateGlobalIdle
|
|
// Set up data
|
|
stateStage1 = State("state_stage1")
|
|
// Process data
|
|
stateStage2 = State("state_stage2")
|
|
// Canceled with internal event
|
|
stateCanceledByInternal = State("state_canceled")
|
|
// Canceled with external event
|
|
stateCanceled2 = State("state_canceled2")
|
|
// Out endpoint to switch
|
|
stateOutToFSM2 = State("state_out_to_fsm2")
|
|
|
|
// Events
|
|
eventInit = Event("event_init")
|
|
eventCancel = Event("event_cancel")
|
|
eventProcess = Event("event_process")
|
|
|
|
// Internal events
|
|
eventInternal = Event("event_internal")
|
|
eventCancelByInternal = Event("event_internal_cancel")
|
|
eventInternalOut2 = Event("event_internal_out")
|
|
)
|
|
|
|
var (
|
|
testingFSM *FSM
|
|
|
|
testingEvents = []EventDesc{
|
|
// Init
|
|
{Name: eventInit, SrcState: []State{stateInit}, DstState: stateStage1},
|
|
{Name: eventInternal, SrcState: []State{stateStage1}, DstState: stateStage2, IsInternal: true},
|
|
|
|
// Cancellation events
|
|
{Name: eventCancelByInternal, SrcState: []State{stateStage2}, DstState: stateCanceledByInternal, IsInternal: true},
|
|
{Name: eventCancel, SrcState: []State{stateStage2}, DstState: stateCanceled2},
|
|
|
|
// Out
|
|
{Name: eventProcess, SrcState: []State{stateStage2}, DstState: stateOutToFSM2},
|
|
{Name: eventInternalOut2, SrcState: []State{stateStage2}, DstState: stateOutToFSM2, IsInternal: true},
|
|
}
|
|
|
|
testingCallbacks = Callbacks{
|
|
eventInit: func(event Event, args ...interface{}) (Event, interface{}, error) {
|
|
return event, nil, nil
|
|
},
|
|
eventInternalOut2: func(event Event, args ...interface{}) (Event, interface{}, error) {
|
|
return event, nil, nil
|
|
},
|
|
eventProcess: func(event Event, args ...interface{}) (Event, interface{}, error) {
|
|
return event, nil, nil
|
|
},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
testingFSM = MustNewFSM(
|
|
testName,
|
|
stateInit,
|
|
testingEvents,
|
|
testingCallbacks,
|
|
)
|
|
}
|
|
|
|
func compareRecoverStr(t *testing.T, r interface{}, assertion string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
msg, ok := r.(string)
|
|
if !ok {
|
|
t.Error("not asserted recover:", r)
|
|
}
|
|
if msg != assertion {
|
|
t.Error("not asserted recover:", msg)
|
|
}
|
|
}
|
|
|
|
func compareStatesArr(src, dst []State) bool {
|
|
if len(src) != len(dst) {
|
|
return false
|
|
}
|
|
// create a map of string -> int
|
|
diff := make(map[State]int, len(src))
|
|
for _, _x := range src {
|
|
// 0 value for int is 0, so just increment a counter for the string
|
|
diff[_x]++
|
|
}
|
|
for _, _y := range dst {
|
|
// If the string _y is not in diff bail out early
|
|
if _, ok := diff[_y]; !ok {
|
|
return false
|
|
}
|
|
diff[_y] -= 1
|
|
if diff[_y] == 0 {
|
|
delete(diff, _y)
|
|
}
|
|
}
|
|
return len(diff) == 0
|
|
}
|
|
|
|
func compareEventsArr(src, dst []Event) bool {
|
|
if len(src) != len(dst) {
|
|
return false
|
|
}
|
|
// create a map of string -> int
|
|
diff := make(map[Event]int, len(src))
|
|
for _, _x := range src {
|
|
// 0 value for int is 0, so just increment a counter for the string
|
|
diff[_x]++
|
|
}
|
|
for _, _y := range dst {
|
|
// If the string _y is not in diff bail out early
|
|
if _, ok := diff[_y]; !ok {
|
|
return false
|
|
}
|
|
diff[_y] -= 1
|
|
if diff[_y] == 0 {
|
|
delete(diff, _y)
|
|
}
|
|
}
|
|
return len(diff) == 0
|
|
}
|
|
|
|
func TestMustNewFSM_Empty_Name_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "machine name cannot be empty")
|
|
}()
|
|
testingFSM = MustNewFSM(
|
|
"",
|
|
"init_state",
|
|
[]EventDesc{},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on empty machine name")
|
|
}
|
|
|
|
func TestMustNewFSM_Empty_Initial_State_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "initial state state cannot be empty")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"",
|
|
[]EventDesc{},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on empty initial")
|
|
}
|
|
|
|
func TestMustNewFSM_Empty_Events_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "cannot init fsm with empty events")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"init_state",
|
|
[]EventDesc{},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on empty events list")
|
|
}
|
|
|
|
func TestMustNewFSM_Event_Empty_Name_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "cannot init empty event")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"init_state",
|
|
[]EventDesc{
|
|
{Name: "", SrcState: []State{"init_state"}, DstState: StateGlobalDone},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on empty event name")
|
|
}
|
|
|
|
func TestMustNewFSM_Event_Empty_Source_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "event must have minimum one source available state")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"init_state",
|
|
[]EventDesc{
|
|
{Name: "event", SrcState: []State{}, DstState: StateGlobalDone},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on empty event sources")
|
|
}
|
|
|
|
func TestMustNewFSM_States_Min_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "machine must contain at least two states")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"init_state",
|
|
[]EventDesc{
|
|
{Name: "event", SrcState: []State{"init_state"}, DstState: StateGlobalDone},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on less than two states")
|
|
}
|
|
|
|
func TestMustNewFSM_State_Entry_Conflict_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "machine must contain at least two states")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"init_state",
|
|
[]EventDesc{
|
|
{Name: "event1", SrcState: []State{"init_state"}, DstState: "state"},
|
|
{Name: "event2", SrcState: []State{"init_state"}, DstState: "state"},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on initialize with conflict in entry state")
|
|
}
|
|
|
|
func TestMustNewFSM_State_Final_Not_Found_Panic(t *testing.T) {
|
|
defer func() {
|
|
compareRecoverStr(t, recover(), "cannot initialize machine without final states")
|
|
}()
|
|
|
|
testingFSM = MustNewFSM(
|
|
"fsm",
|
|
"init_state",
|
|
[]EventDesc{
|
|
{Name: "event1", SrcState: []State{"init_state"}, DstState: "state2"},
|
|
{Name: "event2", SrcState: []State{"state2"}, DstState: "init_state"},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
t.Errorf("did not panic on initialize without final state")
|
|
}
|
|
|
|
func TestFSM_Name(t *testing.T) {
|
|
if testingFSM.Name() != testName {
|
|
t.Errorf("expected machine name \"%s\"", testName)
|
|
}
|
|
}
|
|
|
|
func TestFSM_EntryEvent(t *testing.T) {
|
|
if testingFSM.InitialState() != stateInit {
|
|
t.Errorf("expected initial state \"%s\"", stateInit)
|
|
}
|
|
}
|
|
|
|
func TestFSM_EventsList(t *testing.T) {
|
|
eventsList := []Event{
|
|
eventInit,
|
|
eventCancel,
|
|
eventProcess,
|
|
}
|
|
|
|
if !compareEventsArr(testingFSM.EventsList(), eventsList) {
|
|
t.Error("expected public events", eventsList)
|
|
}
|
|
|
|
}
|
|
|
|
func TestFSM_StatesList(t *testing.T) {
|
|
statesList := []State{
|
|
stateInit,
|
|
stateStage1,
|
|
stateStage2,
|
|
}
|
|
|
|
if !compareStatesArr(testingFSM.StatesList(), statesList) {
|
|
t.Error("expected states", statesList)
|
|
}
|
|
}
|
|
|
|
func TestFSM_CopyWithState(t *testing.T) {
|
|
testingFSM1 := MustNewFSM(
|
|
testName,
|
|
stateInit,
|
|
testingEvents,
|
|
testingCallbacks,
|
|
)
|
|
testingFSM2 := testingFSM.MustCopyWithState(stateStage2)
|
|
|
|
if testingFSM1.State() != stateInit {
|
|
t.Fatal("expect unchanged source state")
|
|
}
|
|
|
|
if testingFSM2.State() != stateStage2 {
|
|
t.Fatal("expect changed source state")
|
|
}
|
|
|
|
}
|