feat: tests

This commit is contained in:
x88 2020-07-30 00:49:41 +03:00
parent 2bb4ad47ad
commit 9bb9547070
9 changed files with 418 additions and 29 deletions

View File

@ -2,6 +2,8 @@ package fsm
import (
"errors"
"fmt"
"strings"
"sync"
)
@ -63,7 +65,7 @@ type trEvent struct {
isInternal bool
}
type EventDesc struct {
type Event struct {
Name string
SrcState []string
@ -80,15 +82,16 @@ type Callback func(event string, args ...interface{}) (interface{}, error)
type Callbacks map[string]Callback
// TODO: Exports
func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]Callback) *FSM {
// Add validation, chains building
func MustNewFSM(machineName, initialState string, events []Event, callbacks map[string]Callback) *FSM {
machineName = strings.TrimSpace(machineName)
initialState = strings.TrimSpace(initialState)
if name == "" {
panic("name cannot be empty")
if machineName == "" {
panic("machine name cannot be empty")
}
if initial == "" {
panic("initialState state cannot be empty")
if initialState == "" {
panic("initial state state cannot be empty")
}
// to remove
@ -97,9 +100,9 @@ func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]C
}
f := &FSM{
name: name,
currentState: initial,
initialState: initial,
name: machineName,
currentState: initialState,
initialState: initialState,
transitions: make(map[trKey]*trEvent),
finStates: make(map[string]bool),
callbacks: make(map[string]Callback),
@ -113,28 +116,33 @@ func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]C
// Validate events
for _, event := range events {
event.Name = strings.TrimSpace(event.Name)
event.DstState = strings.TrimSpace(event.DstState)
if event.Name == "" {
panic("cannot init empty event")
}
// TODO: Check transition when all events added
if len(event.SrcState) == 0 {
panic("event must have min one source available state")
}
if event.DstState == "" {
panic("event dest cannot be empty, use StateGlobalDone for finish or external state")
}
if _, ok := allEvents[event.Name]; ok {
panic("duplicate event")
panic(fmt.Sprintf("duplicate event \"%s\"", event.Name))
}
allEvents[event.Name] = true
allStates[event.DstState] = true
trimmedSourcesCounter := 0
for _, sourceState := range event.SrcState {
sourceState := strings.TrimSpace(sourceState)
if sourceState == "" {
continue
}
tKey := trKey{
sourceState,
event.Name,
@ -151,7 +159,7 @@ func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]C
f.transitions[tKey] = &trEvent{event.DstState, event.IsInternal}
// For using provider, event must use with IsGlobal = true
if sourceState == initial {
if sourceState == initialState {
if f.initialEvent != "" {
panic("machine entry event already exist")
}
@ -159,6 +167,11 @@ func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]C
}
allSources[sourceState] = true
trimmedSourcesCounter++
}
if trimmedSourcesCounter == 0 {
panic("event must have minimum one source available state")
}
}
@ -169,7 +182,7 @@ func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]C
// Validate callbacks
for event, callback := range callbacks {
if event == "" {
panic("callback name cannot be empty")
panic("callback machineName cannot be empty")
}
if _, ok := allEvents[event]; !ok {
@ -275,18 +288,30 @@ func (f *FSM) EntryEvent() (event string) {
}
func (f *FSM) EventsList() (events []string) {
var eventsMap = map[string]bool{}
if len(f.transitions) > 0 {
for trKey, trEvent := range f.transitions {
if !trEvent.isInternal {
events = append(events, trKey.event)
eventsMap[trKey.event] = true
if _, exists := eventsMap[trKey.event]; !exists {
events = append(events, trKey.event)
}
}
}
}
if len(eventsMap) > 0 {
for event := range eventsMap {
events = append(events, event)
}
}
return
}
func (f *FSM) StatesList() (states []string) {
allStates := map[string]bool{}
func (f *FSM) StatesSourcesList() (states []string) {
var allStates = map[string]bool{}
if len(f.transitions) > 0 {
for trKey, _ := range f.transitions {
allStates[trKey.source] = true

View File

@ -0,0 +1,77 @@
package fsm
const (
FSM1Name = "fsm1"
// Init process from global idle state
FSM1StateInit = StateGlobalIdle
// Set up data
FSM1StateStage1 = "state_fsm1_stage1"
// Process data
FSM1StateStage2 = "state_fsm1_stage2"
// Cancelled with internal event
FSM1StateCanceledByInternal = "state_fsm1_canceled1"
// Cancelled with external event
FSM1StateCanceled2 = "state_fsm1_canceled2"
// Out endpoint to switch
FSM1StateOutToFSM2 = "state_fsm1_out_to_fsm2"
FSM1StateOutToFSM3 = "state_fsm1_out_to_fsm3"
// Events
EventFSM1Init = "event_fsm1_init"
EventFSM1Cancel = "event_fsm1_cancel"
EventFSM1Process = "event_fsm1_process"
// Internal events
EventFSM1Internal = "event_internal_fsm1"
EventFSM1CancelByInternal = "event_internal_fsm1_cancel"
EventFSM1InternalOut2 = "event_internal_fsm1_out"
)
var (
testingEvents = []Event{
// Init
{Name: EventFSM1Init, SrcState: []string{FSM1StateInit}, DstState: FSM1StateStage1},
{Name: EventFSM1Internal, SrcState: []string{FSM1StateStage1}, DstState: FSM1StateStage2, IsInternal: true},
// Cancellation events
{Name: EventFSM1CancelByInternal, SrcState: []string{FSM1StateStage2}, DstState: FSM1StateCanceledByInternal, IsInternal: true},
{Name: EventFSM1Cancel, SrcState: []string{FSM1StateStage2}, DstState: FSM1StateCanceled2},
// Out
{Name: EventFSM1Process, SrcState: []string{FSM1StateStage2}, DstState: FSM1StateOutToFSM2},
{Name: EventFSM1InternalOut2, SrcState: []string{FSM1StateStage2}, DstState: FSM1StateOutToFSM3, IsInternal: true},
}
testingCallbacks = Callbacks{
EventFSM1Init: actionSetUpData,
EventFSM1InternalOut2: actionEmitOut2,
EventFSM1Process: actionProcessData,
}
)
type testMachineFSM struct {
*FSM
}
/*func new() fsm_pool.IStateMachine {
machine := &testMachineFSM{}
machine.FSM = MustNewFSM(
FSM1Name,
FSM1StateInit,
testingEvents,
testingCallbacks,
)
return machine
}*/
func actionSetUpData(event string, args ...interface{}) (response interface{}, err error) {
return
}
func actionProcessData(event string, args ...interface{}) (response interface{}, err error) {
return
}
func actionEmitOut2(event string, args ...interface{}) (response interface{}, err error) {
return
}

View File

@ -0,0 +1,225 @@
package fsm
import (
"log"
"testing"
)
var testingFSM *FSM
func init() {
testingFSM = MustNewFSM(
FSM1Name,
FSM1StateInit,
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 compareArrays(src, dst []string) bool {
if len(src) != len(dst) {
return false
}
// create a map of string -> int
diff := make(map[string]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)
}
}
if len(diff) == 0 {
return true
}
return false
}
func TestMustNewFSM_Empty_Name_Panic(t *testing.T) {
defer func() {
compareRecoverStr(t, recover(), "machine name cannot be empty")
}()
testingFSM = MustNewFSM(
"",
"init_state",
[]Event{},
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",
"",
[]Event{},
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",
[]Event{},
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",
[]Event{
{Name: "", SrcState: []string{"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",
[]Event{
{Name: "event", SrcState: []string{}, 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",
[]Event{
{Name: "event", SrcState: []string{"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 entry event already exist")
}()
testingFSM = MustNewFSM(
"fsm",
"init_state",
[]Event{
{Name: "event1", SrcState: []string{"init_state"}, DstState: "state"},
{Name: "event2", SrcState: []string{"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",
[]Event{
{Name: "event1", SrcState: []string{"init_state"}, DstState: "state2"},
{Name: "event2", SrcState: []string{"state2"}, DstState: "init_state"},
},
nil,
)
t.Errorf("did not panic on initialize without final state")
}
func TestFSM_Name(t *testing.T) {
if testingFSM.Name() != FSM1Name {
t.Errorf("expected machine name \"%s\"", FSM1Name)
}
}
func TestFSM_EntryEvent(t *testing.T) {
if testingFSM.InitialState() != FSM1StateInit {
t.Errorf("expected initial state \"%s\"", FSM1StateInit)
}
}
func TestFSM_EventsList(t *testing.T) {
eventsList := []string{
EventFSM1Init,
EventFSM1Cancel,
EventFSM1Process,
}
if !compareArrays(testingFSM.EventsList(), eventsList) {
t.Error("expected public events", eventsList)
}
}
func TestFSM_StatesList(t *testing.T) {
log.Println(testingFSM.StatesSourcesList())
statesList := []string{
FSM1StateInit,
FSM1StateStage2,
FSM1StateStage2,
}
if !compareArrays(testingFSM.StatesSourcesList(), statesList) {
t.Error("expected stages", statesList)
}
}

View File

@ -21,7 +21,7 @@ type IStateMachine interface {
EventsList() []string
StatesList() []string
StatesSourcesList() []string
IsFinState(state string) bool
}
@ -93,7 +93,7 @@ func Init(machines ...IStateMachine) *FSMPoolProvider {
// Fill up states with initial and exit states checking
for _, machine := range machines {
machineName := machine.Name()
machineStates := machine.StatesList()
machineStates := machine.StatesSourcesList()
for _, state := range machineStates {
if machine.IsFinState(state) {
// If state is initial for another machine,

View File

@ -25,7 +25,7 @@ func New() fsm_pool.IStateMachine {
machine.FSM = fsm.MustNewFSM(
fsmName,
stateConstructorEntryPoint,
[]fsm.EventDesc{
[]fsm.Event{
// {Name: "", SrcState: []string{""}, DstState: ""},
// Init

View File

@ -36,7 +36,7 @@ func New() fsm_pool.IStateMachine {
machine.FSM = fsm.MustNewFSM(
fsmName,
fsm.StateGlobalIdle,
[]fsm.EventDesc{
[]fsm.Event{
// {Name: "", SrcState: []string{""}, DstState: ""},
// Init

View File

@ -0,0 +1,48 @@
package requests
import (
"errors"
"github.com/p2p-org/dc4bc/fsm/config"
)
// Requests
type ProposalParticipantsListRequest []ProposalParticipantsEntryRequest
type ProposalParticipantsEntryRequest struct {
// Public title for address, such as name, nickname, organization
Title string
PublicKey []byte
}
func (r *ProposalParticipantsListRequest) Validate() error {
if len(*r) < config.ParticipantsMinCount {
return errors.New("too few participants")
}
for _, participant := range *r {
if len(participant.Title) < 3 {
return errors.New("title too short")
}
if len(participant.Title) > 150 {
return errors.New("title too long")
}
if len(participant.PublicKey) < 10 {
return errors.New("pub key too short")
}
}
return nil
}
type ProposalParticipantConfirmationRequest struct {
// Public title for address, such as name, nickname, organization
ParticipantId string
EncryptedInvitation string
}
func (r *ProposalParticipantConfirmationRequest) Validate() error {
return nil
}

View File

@ -0,0 +1,14 @@
package responses
// Responses
type ProposalParticipantInvitationsResponse []*ProposalParticipantInvitationEntryResponse
type ProposalParticipantInvitationEntryResponse struct {
// Public title for address, such as name, nickname, organization
Title string
// Key for link invitations to participants
PubKeyFingerprint string
// Encrypted with public key secret
EncryptedInvitation string
}

8
go.mod
View File

@ -6,16 +6,16 @@ require (
github.com/looplab/fsm v0.1.0
github.com/makiuchi-d/gozxing v0.0.0-20190830103442-eaff64b1ceb7
github.com/mattn/go-gtk v0.0.0-20191030024613-af2e013261f5
github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f // indirect
github.com/p2p-org/dc4bc v0.0.0-00010101000000-000000000000
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.6.1 // indirect
github.com/stretchr/testify v1.6.1
go.dedis.ch/kyber/v3 v3.0.9
gocv.io/x/gocv v0.23.0
golang.org/x/image v0.0.0-20200618115811-c13761719519
golang.org/x/text v0.3.3 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
)
replace golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5
replace go.dedis.ch/kyber/v3 => github.com/corestario/kyber/v3 v3.0.0-20200218082721-8ed10c357c05
replace github.com/p2p-org/dc4bc => /home/tellme/PROJECTS/go/src/github.com/p2p-org/dc4bc