feat: fixes, tests, actions

This commit is contained in:
x88 2020-08-09 15:02:50 +03:00
parent 1b26930d57
commit 8f3152dcf8
7 changed files with 213 additions and 82 deletions

View File

@ -21,6 +21,10 @@ import (
const (
StateGlobalIdle = State("__idle")
StateGlobalDone = State("__done")
EventRunDefault EventRunMode = iota
EventRunBefore
EventRunAfter
)
type State string
@ -39,6 +43,8 @@ func (e *Event) IsEmpty() bool {
return e.String() == ""
}
type EventRunMode uint8
// Response returns result for processing with clientMocks events
type Response struct {
// Returns machine execution result state
@ -55,6 +61,8 @@ type FSM struct {
// May be mapping must require pair source + event?
transitions map[trKey]*trEvent
autoTransitions map[State]*trEvent
callbacks Callbacks
initialEvent Event
@ -77,10 +85,11 @@ type trKey struct {
// Transition lightweight event description
type trEvent struct {
event Event
dstState State
isInternal bool
isStateBefore bool
event Event
dstState State
isInternal bool
isAuto bool
runMode EventRunMode
}
type EventDesc struct {
@ -94,8 +103,10 @@ type EventDesc struct {
// Internal events, cannot be emitted from external call
IsInternal bool
// Set dst state before execute action
IsDstInit bool
// Event must run without manual call
IsAuto bool
AutoRunMode EventRunMode
}
type Callback func(event Event, args ...interface{}) (Event, interface{}, error)
@ -121,12 +132,13 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
}
f := &FSM{
name: machineName,
currentState: initialState,
initialState: initialState,
transitions: make(map[trKey]*trEvent),
finStates: make(map[State]bool),
callbacks: make(map[Event]Callback),
name: machineName,
currentState: initialState,
initialState: initialState,
transitions: make(map[trKey]*trEvent),
autoTransitions: make(map[State]*trEvent),
finStates: make(map[State]bool),
callbacks: make(map[Event]Callback),
}
allEvents := make(map[Event]bool)
@ -177,19 +189,40 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
panic("duplicate dst for pair `source + event`")
}
f.transitions[tKey] = &trEvent{
trEvent := &trEvent{
tKey.event,
event.DstState,
event.IsInternal,
event.IsDstInit,
event.IsAuto,
event.AutoRunMode,
}
if trEvent.isAuto && trEvent.runMode == EventRunDefault {
trEvent.runMode = EventRunAfter
}
f.transitions[tKey] = trEvent
// For using provider, event must use with IsGlobal = true
if sourceState == initialState {
if f.initialEvent != "" {
panic("machine entry event already exist")
if f.initialEvent == "" {
f.initialEvent = event.Name
}
f.initialEvent = event.Name
}
if event.IsAuto {
if event.AutoRunMode != EventRunBefore && event.AutoRunMode != EventRunAfter {
panic("{AutoRunMode} not set for auto event")
}
if _, ok := f.autoTransitions[sourceState]; ok {
panic(fmt.Sprintf(
"auto event \"%s\" already exists for state \"%s\"",
event.Name,
sourceState,
))
}
f.autoTransitions[sourceState] = trEvent
}
allSources[sourceState] = true
@ -260,14 +293,28 @@ func (f *FSM) do(trEvent *trEvent, args ...interface{}) (resp *Response, err err
// f.eventMu.Lock()
// defer f.eventMu.Unlock()
if trEvent.isStateBefore {
err = f.SetState(trEvent.event)
if err != nil {
resp = &Response{
State: f.State(),
}
return resp, err
// Process auto event
if autoEvent, ok := f.autoTransitions[f.State()]; ok {
autoEventResp := &Response{
State: f.State(),
}
if autoEvent.runMode == EventRunBefore {
if callback, ok := f.callbacks[autoEvent.event]; ok {
outEvent, autoEventResp.Data, err = callback(autoEvent.event, args...)
if err != nil {
return autoEventResp, err
}
}
if outEvent.IsEmpty() || autoEvent.event == outEvent {
err = f.SetState(autoEvent.event)
} else {
err = f.SetState(outEvent)
}
if err != nil {
return autoEventResp, err
}
}
outEvent = ""
}
resp = &Response{
@ -282,13 +329,35 @@ func (f *FSM) do(trEvent *trEvent, args ...interface{}) (resp *Response, err err
}
}
if !trEvent.isStateBefore {
if outEvent.IsEmpty() || trEvent.event == outEvent {
err = f.SetState(trEvent.event)
} else {
err = f.SetState(outEvent)
}
// Set state when callback executed
if outEvent.IsEmpty() || trEvent.event == outEvent {
err = f.SetState(trEvent.event)
} else {
err = f.SetState(outEvent)
}
// Process auto event
if autoEvent, ok := f.autoTransitions[f.State()]; ok {
autoEventResp := &Response{
State: f.State(),
}
if autoEvent.runMode == EventRunAfter {
if callback, ok := f.callbacks[autoEvent.event]; ok {
outEvent, autoEventResp.Data, err = callback(autoEvent.event, args...)
if err != nil {
return autoEventResp, err
}
}
if outEvent.IsEmpty() || autoEvent.event == outEvent {
err = f.SetState(autoEvent.event)
} else {
err = f.SetState(outEvent)
}
if err != nil {
return autoEventResp, err
}
}
outEvent = ""
}
resp.State = f.State()

View File

@ -48,14 +48,14 @@ var (
}
testingCallbacks = Callbacks{
eventInit: func(event Event, args ...interface{}) (interface{}, error) {
return nil, nil
eventInit: func(event Event, args ...interface{}) (Event, interface{}, error) {
return event, nil, nil
},
eventInternalOut2: func(event Event, args ...interface{}) (interface{}, error) {
return nil, nil
eventInternalOut2: func(event Event, args ...interface{}) (Event, interface{}, error) {
return event, nil, nil
},
eventProcess: func(event Event, args ...interface{}) (interface{}, error) {
return nil, nil
eventProcess: func(event Event, args ...interface{}) (Event, interface{}, error) {
return event, nil, nil
},
}
)

View File

@ -3,7 +3,6 @@ package fsm_pool
import (
"errors"
"fmt"
"github.com/depools/dc4bc/fsm/fsm"
)
@ -23,6 +22,8 @@ type MachineProvider interface {
GlobalInitialEvent() fsm.Event
EntryEvent() fsm.Event
EventsList() []fsm.Event
StatesSourcesList() []fsm.State
@ -39,9 +40,10 @@ type FSMStatesMapper map[fsm.State]string
type FSMPool struct {
fsmInitialEvent fsm.Event
// Pool mapper by names
mapper FSMMapper
events FSMEventsMapper
states FSMStatesMapper
mapper FSMMapper
events FSMEventsMapper
states FSMStatesMapper
entryEvents map[fsm.State]fsm.Event
}
func Init(machines ...MachineProvider) *FSMPool {
@ -49,9 +51,10 @@ func Init(machines ...MachineProvider) *FSMPool {
panic("cannot initialize empty pool")
}
p := &FSMPool{
mapper: make(FSMMapper),
events: make(FSMEventsMapper),
states: make(FSMStatesMapper),
mapper: make(FSMMapper),
events: make(FSMEventsMapper),
states: make(FSMStatesMapper),
entryEvents: make(map[fsm.State]fsm.Event),
}
allInitStatesMap := make(map[fsm.State]string)
@ -84,7 +87,7 @@ func Init(machines ...MachineProvider) *FSMPool {
}
// Setup entry event for machines pool if available
if initialEvent := machine.GlobalInitialEvent(); initialEvent != "" {
if initialEvent := machine.GlobalInitialEvent(); !initialEvent.IsEmpty() {
if p.fsmInitialEvent != "" {
panic("duplicate entry event initialization")
}
@ -92,6 +95,10 @@ func Init(machines ...MachineProvider) *FSMPool {
p.fsmInitialEvent = initialEvent
}
if machineEntryEvent := machine.EntryEvent(); !machineEntryEvent.IsEmpty() {
p.entryEvents[machine.InitialState()] = machineEntryEvent
}
p.mapper[machineName] = machine
}
@ -153,3 +160,17 @@ func (p *FSMPool) MachineByState(state fsm.State) (MachineProvider, error) {
}
return machine, nil
}
/*func (p *FSMPool) Do(machine MachineProvider, event fsm.Event, args ...interface{}) (resp *fsm.Response, err error) {
panic("llslsl")
resp, err = machine.Do(event, args...)
if err != nil {
return resp, err
}
if machine.IsFinState(resp.State) {
log.Println("Final!!!!")
}
return
}
*/

View File

@ -44,7 +44,7 @@ const (
var (
testing1Events = []fsm.EventDesc{
// Init
{Name: eventFSM1Init, SrcState: []fsm.State{fsm1StateInit}, DstState: fsm1StateStage1, IsDstInit: true},
{Name: eventFSM1Init, SrcState: []fsm.State{fsm1StateInit}, DstState: fsm1StateStage1, IsAuto: true, AutoRunMode: fsm.EventRunAfter},
{Name: eventFSM1Internal, SrcState: []fsm.State{fsm1StateStage1}, DstState: fsm1StateStage2, IsInternal: true},
// Cancellation events
@ -73,21 +73,24 @@ func NewFSM1() MachineProvider {
return machine
}
func (m *testMachineFSM1) actionFSM1SetUpData(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *testMachineFSM1) actionFSM1SetUpData(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
m.data = testVal1
return m.DoInternal(eventFSM1Internal)
outEvent = eventFSM1Internal
return
}
func (m *testMachineFSM1) actionFSM1ProcessData(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *testMachineFSM1) actionFSM1ProcessData(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
if len(args) == 1 {
if val, ok := args[0].(int); ok {
m.data -= val
}
}
return m.data, nil
response = m.data
return
}
func (m *testMachineFSM1) actionFSM1EmitOut2(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *testMachineFSM1) actionFSM1EmitOut2(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
return
}

View File

@ -3,14 +3,13 @@ package dkg_proposal_fsm
import (
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"sync"
)
const (
FsmName = "dkg_proposal_fsm"
StateDkgInitial = signature_proposal_fsm.StateValidationCompleted
StateDkgInitial = StateDkgPubKeysAwaitConfirmations
StateDkgPubKeysAwaitConfirmations = fsm.State("state_dkg_pub_keys_await_confirmations")
// Cancelled
@ -43,7 +42,7 @@ const (
StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed")
// Events
EventDKGPubKeysSendingRequiredInternal = fsm.Event("event_dkg_pub_key_sending_required_internal")
eventDKGPubKeysSendingRequiredAuto = fsm.Event("event_dkg_pub_key_sending_required_internal")
EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received")
EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error")
@ -88,7 +87,7 @@ func New() internal.DumpedMachineProvider {
// Init
// Switch to pub keys required
{Name: EventDKGPubKeysSendingRequiredInternal, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true},
// {Name: eventDKGPubKeysSendingRequiredAuto, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunAfter},
// Pub keys sending
{Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations},

View File

@ -7,6 +7,8 @@ import (
"crypto/x509"
"encoding/base64"
"fmt"
"github.com/depools/dc4bc/fsm/fsm"
dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/depools/dc4bc/fsm/types/requests"
"github.com/depools/dc4bc/fsm/types/responses"
@ -93,41 +95,58 @@ func TestCreate_Negative(t *testing.T) {
}
}
func Test_Workflow(t *testing.T) {
testFSMInstance, err := Create(testTransactionId)
if err != nil {
t.Fatalf("expected nil error, got {%s}", err)
func compareErrNil(t *testing.T, got error) {
if got != nil {
t.Fatalf("expected nil error, got {%s}", got)
}
}
if testFSMInstance == nil {
func compareFSMInstanceNotNil(t *testing.T, got *FSMInstance) {
if got == nil {
t.Fatalf("expected {*FSMInstance}")
}
}
func compareDumpNotZero(t *testing.T, got []byte) {
if len(got) == 0 {
t.Fatalf("expected non zero dump, when executed without error")
}
}
func compareFSMResponseNotNil(t *testing.T, got *fsm.Response) {
if got == nil {
t.Fatalf("expected {*fsm.FSMResponse} got nil")
}
}
func compareState(t *testing.T, expected fsm.State, got fsm.State) {
if got != expected {
t.Fatalf("expected state {%s} got {%s}", expected, got)
}
}
func Test_Workflow(t *testing.T) {
testFSMInstance, err := Create(testTransactionId)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if testFSMInstance.machine.Name() != spf.FsmName {
t.Fatalf("expected machine name {%s}", spf.FsmName)
}
if testFSMInstance.machine.State() != spf.StateParticipantsConfirmationsInit {
t.Fatalf("expected inital state {%s}", spf.StateParticipantsConfirmationsInit)
}
compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State())
fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest)
if err != nil {
t.Fatalf("expected nil error, got {%s}", err)
}
compareErrNil(t, err)
if len(dump) == 0 {
t.Fatalf("expected non zero dump, when executed without error")
}
compareDumpNotZero(t, dump)
if fsmResponse == nil {
t.Fatalf("expected {*fsm.FSMResponse}")
}
compareFSMResponseNotNil(t, fsmResponse)
if fsmResponse.State != spf.StateAwaitParticipantsConfirmations {
t.Fatalf("expected state {%s}", spf.StateAwaitParticipantsConfirmations)
}
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse)
@ -163,17 +182,17 @@ func Test_Workflow(t *testing.T) {
tm = tm.Add(10 * time.Second)
for _, participant := range participantsMap {
participantsCount := len(participantsMap)
participantCounter := participantsCount
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
if err != nil {
t.Fatalf("expected nil error, got {%s}", err)
}
compareErrNil(t, err)
if testFSMInstance == nil {
t.Fatalf("expected {*FSMInstance}")
}
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
@ -191,6 +210,24 @@ func Test_Workflow(t *testing.T) {
DecryptedInvitation: string(encrypted),
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
if participantCounter > 0 {
if fsmResponse.State != spf.StateAwaitParticipantsConfirmations {
t.Fatalf("expected state {%s} got {%s}", spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
}
} else {
if fsmResponse.State != dpf.StateDkgInitial {
t.Fatalf("expected state {%s} got {%s}", dpf.StateDkgInitial, fsmResponse.State)
}
}
log.Println(fsmResponse.State, err)
}
}

View File

@ -2,6 +2,7 @@ package signature_proposal_fsm
import (
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"sync"
)
@ -17,6 +18,7 @@ const (
StateValidationCanceledByParticipant = fsm.State("state_sig_proposal_canceled_by_participant")
StateValidationCanceledByTimeout = fsm.State("state_sig_proposal_canceled_by_timeout")
// Out state
StateValidationCompleted = fsm.State("state_sig_proposal_completed")
EventInitProposal = fsm.Event("event_sig_proposal_init")
@ -60,7 +62,7 @@ func New() internal.DumpedMachineProvider {
// eventProposalValidate internal or from client?
// yay
// Exit point
{Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: fsm.State("state_dkg_pub_keys_sending_required"), IsInternal: true},
{Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: dkg_proposal_fsm.StateDkgPubKeysAwaitConfirmations, IsInternal: true},
// nan
{Name: eventSetValidationCanceledByTimeout, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByTimeout, IsInternal: true},
},