mirror of https://github.com/certusone/dc4bc.git
feat: tests
This commit is contained in:
parent
2bb4ad47ad
commit
9bb9547070
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -25,7 +25,7 @@ func New() fsm_pool.IStateMachine {
|
|||
machine.FSM = fsm.MustNewFSM(
|
||||
fsmName,
|
||||
stateConstructorEntryPoint,
|
||||
[]fsm.EventDesc{
|
||||
[]fsm.Event{
|
||||
// {Name: "", SrcState: []string{""}, DstState: ""},
|
||||
|
||||
// Init
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
8
go.mod
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue