fix lint errors
Some checks failed
Lint / golangci-lint (push) Failing after 9m53s
Machine Tests / test (push) Successful in 40s

This commit is contained in:
Matthew Rich 2024-04-04 13:08:50 -07:00
parent 8b92e9e143
commit 85e39191b8
10 changed files with 180 additions and 181 deletions

View File

@ -1,67 +1,67 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import ( import (
"errors" "errors"
) )
type State string type State string
type Stater interface { type Stater interface {
AddStates(name ...State) AddStates(name ...State)
GetState(name string) State GetState(name string) State
AddTransition(trigger string, source State, dest State) AddTransition(trigger string, source State, dest State)
AddSubscription(transition string, subscription Subscriber) error AddSubscription(transition string, subscription Subscriber) error
AddModel(m Modeler) AddModel(m Modeler)
Trigger(transition string) error Trigger(transition string) error
CurrentState() State CurrentState() State
} }
type Definition struct { type Definition struct {
states []State states []State
triggers map[string]Transitioner triggers map[string]Transitioner
model Modeler model Modeler
} }
func New(initial State) Stater { func New(initial State) Stater {
return &Definition{ model: NewModel(initial), triggers: make(map[string]Transitioner) } return &Definition{model: NewModel(initial), triggers: make(map[string]Transitioner)}
} }
func (d *Definition) AddStates(name ...State) { func (d *Definition) AddStates(name ...State) {
d.states = append(d.states, name...) d.states = append(d.states, name...)
} }
func (d *Definition) AddTransition(trigger string, source State, dest State) { func (d *Definition) AddTransition(trigger string, source State, dest State) {
d.triggers[trigger] = NewTransition(trigger, source, dest) d.triggers[trigger] = NewTransition(trigger, source, dest)
} }
func (d *Definition) GetState(name string) State { func (d *Definition) GetState(name string) State {
var r State var r State
for _,s := range(d.states) { for _, s := range d.states {
if string(s) == name { if string(s) == name {
return s return s
} }
} }
return r return r
} }
func (d *Definition) AddModel(m Modeler) { func (d *Definition) AddModel(m Modeler) {
d.model = m d.model = m
} }
func (d *Definition) Trigger(transition string) error { func (d *Definition) Trigger(transition string) error {
return d.triggers[transition].Run(d.model) return d.triggers[transition].Run(d.model)
} }
func (d *Definition) CurrentState() State { func (d *Definition) CurrentState() State {
return d.model.InspectState() return d.model.InspectState()
} }
func (d *Definition) AddSubscription(transition string, subscription Subscriber) error { func (d *Definition) AddSubscription(transition string, subscription Subscriber) error {
if t,ok := d.triggers[transition]; ok { if t, ok := d.triggers[transition]; ok {
t.Subscribe(subscription) t.Subscribe(subscription)
return nil return nil
} }
return errors.New("Transition does not exist") return errors.New("Transition does not exist")
} }

View File

@ -1,69 +1,69 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import( import (
"log"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"log"
"testing"
) )
func setupStater(initial State) Stater { func setupStater(initial State) Stater {
s := New(initial) s := New(initial)
if s == nil { if s == nil {
log.Fatal("Failed creating new Stater") log.Fatal("Failed creating new Stater")
} }
return s return s
} }
func TestMachineStater(t *testing.T) { func TestMachineStater(t *testing.T) {
s := setupStater("disconnected") s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected") s.AddStates("disconnected", "start_connection", "connected")
r := s.GetState("connected") r := s.GetState("connected")
if r != "connected" { if r != "connected" {
t.Errorf("Failed checking state: %s", r) t.Errorf("Failed checking state: %s", r)
} }
} }
func TestMachineCurrentState(t *testing.T) { func TestMachineCurrentState(t *testing.T) {
s := setupStater("disconnected") s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected") s.AddStates("disconnected", "start_connection", "connected")
r := s.CurrentState() r := s.CurrentState()
if r != "disconnected" { if r != "disconnected" {
t.Errorf("Failed checking state: %s", r) t.Errorf("Failed checking state: %s", r)
} }
} }
func TestMachineAddTransition(t *testing.T) { func TestMachineAddTransition(t *testing.T) {
s := setupStater("disconnected") s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected") s.AddStates("disconnected", "start_connection", "connected")
s.AddTransition("connect", "disconnected", "start_connection") s.AddTransition("connect", "disconnected", "start_connection")
s.AddModel(setupModel("disconnected")) s.AddModel(setupModel("disconnected"))
// worker gets a trigger message // worker gets a trigger message
assert.Nil(t, s.Trigger("connect")) assert.Nil(t, s.Trigger("connect"))
// machine generates transition event mesages // machine generates transition event mesages
if s.CurrentState() != "start_connection" { if s.CurrentState() != "start_connection" {
t.Errorf("State transition failed for: connect - %s", s.CurrentState()) t.Errorf("State transition failed for: connect - %s", s.CurrentState())
} }
} }
func TestMachineAddSubscription(t *testing.T) { func TestMachineAddSubscription(t *testing.T) {
x := setupSubscriber() x := setupSubscriber()
s := setupStater("disconnected") s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected") s.AddStates("disconnected", "start_connection", "connected")
s.AddTransition("connect", "disconnected", "start_connection") s.AddTransition("connect", "disconnected", "start_connection")
assert.Nil(t, s.AddSubscription("connect", x)) assert.Nil(t, s.AddSubscription("connect", x))
s.AddModel(setupModel("disconnected")) s.AddModel(setupModel("disconnected"))
s.Trigger("connect") s.Trigger("connect")
exitMessage := <- *x.(*EventChannel) exitMessage := <-*x.(*EventChannel)
enterMessage := <- *x.(*EventChannel) enterMessage := <-*x.(*EventChannel)
if exitMessage.on == EXITSTATEEVENT && exitMessage.source == "disconnected" { if exitMessage.on == EXITSTATEEVENT && exitMessage.source == "disconnected" {
if enterMessage.on == ENTERSTATEEVENT && enterMessage.dest == "start_connection" { if enterMessage.on == ENTERSTATEEVENT && enterMessage.dest == "start_connection" {
return return
} }
} }
t.Errorf("Unexpected event message in state transition notification: exit: %s, enter: %s", exitMessage, enterMessage) t.Errorf("Unexpected event message in state transition notification: exit: %s, enter: %s", exitMessage, enterMessage)
} }

View File

@ -1,30 +1,30 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import ( import (
"fmt" "fmt"
) )
const ( const (
ENTERSTATEEVENT = iota ENTERSTATEEVENT = iota
EXITSTATEEVENT EXITSTATEEVENT
) )
type Eventtype int type Eventtype int
type EventMessage struct { type EventMessage struct {
on Eventtype on Eventtype
source State source State
dest State dest State
} }
type EventChannel chan EventMessage type EventChannel chan EventMessage
func (e *EventChannel) Notify(m *EventMessage) { func (e *EventChannel) Notify(m *EventMessage) {
*e <- *m *e <- *m
} }
func (m EventMessage) String() string { func (m EventMessage) String() string {
return fmt.Sprintf("{on: %d, source: %s, dest: %s}", m.on, m.source, m.dest) return fmt.Sprintf("{on: %d, source: %s, dest: %s}", m.on, m.source, m.dest)
} }

View File

@ -1,31 +1,31 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
type Modeler interface { type Modeler interface {
Trigger(transition Transitioner) error Trigger(transition Transitioner) error
ChangeState(target State) State ChangeState(target State) State
InspectState() State InspectState() State
} }
type Model struct { type Model struct {
state State state State
} }
func NewModel(initial State) Modeler { func NewModel(initial State) Modeler {
return &Model{ state: initial } return &Model{state: initial}
} }
func (m *Model) Trigger(transition Transitioner) error { func (m *Model) Trigger(transition Transitioner) error {
return transition.Run(m) return transition.Run(m)
} }
func (m *Model) ChangeState(target State) State { func (m *Model) ChangeState(target State) State {
oldState := m.state oldState := m.state
m.state = target m.state = target
return oldState return oldState
} }
func (m *Model) InspectState() State { func (m *Model) InspectState() State {
return m.state return m.state
} }

View File

@ -1,29 +1,29 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import ( import (
"testing" "testing"
) )
func setupModel(initial State) Modeler { func setupModel(initial State) Modeler {
return NewModel(initial) return NewModel(initial)
} }
func TestMachineModel(t *testing.T) { func TestMachineModel(t *testing.T) {
m := setupModel("") m := setupModel("")
if m == nil { if m == nil {
t.Errorf("Failed creating new model") t.Errorf("Failed creating new model")
} }
} }
func TestMachineModelChangeState(t *testing.T) { func TestMachineModelChangeState(t *testing.T) {
m := setupModel("") m := setupModel("")
oldState := m.ChangeState("connected") oldState := m.ChangeState("connected")
if oldState != "" { if oldState != "" {
t.Errorf("Failed changing state in model") t.Errorf("Failed changing state in model")
} }
if m.InspectState() != "connected" { if m.InspectState() != "connected" {
t.Errorf("Failed changing state in model") t.Errorf("Failed changing state in model")
} }
} }

View File

@ -1,12 +1,11 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
type mState struct { type mState struct {
name string name string
} }
func NewState(name string) *mState { func NewState(name string) *mState {
return &mState{ name: name } return &mState{name: name}
} }

View File

@ -1,14 +1,14 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import ( import (
"testing" "testing"
) )
func TestMachineState(t *testing.T) { func TestMachineState(t *testing.T) {
s := NewState("connected") s := NewState("connected")
if s == nil { if s == nil {
t.Errorf("Failed creating new state") t.Errorf("Failed creating new state")
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
type Subscriber interface { type Subscriber interface {
Notify(m *EventMessage) Notify(m *EventMessage)
} }

View File

@ -1,47 +1,47 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import ( import (
"fmt" _ "errors"
"errors" "fmt"
) )
type Transitioner interface { type Transitioner interface {
Run(m Modeler) error Run(m Modeler) error
Subscribe(s Subscriber) Subscribe(s Subscriber)
} }
type Transition struct { type Transition struct {
trigger string trigger string
source State source State
dest State dest State
subscriptions []Subscriber subscriptions []Subscriber
} }
func NewTransition(trigger string, source State, dest State) Transitioner { func NewTransition(trigger string, source State, dest State) Transitioner {
return &Transition{ trigger: trigger, source: source, dest: dest } return &Transition{trigger: trigger, source: source, dest: dest}
} }
func (r *Transition) Run(m Modeler) error { func (r *Transition) Run(m Modeler) error {
currentState := m.InspectState() currentState := m.InspectState()
if currentState == r.source { if currentState == r.source {
res := m.ChangeState(r.dest) res := m.ChangeState(r.dest)
if res == currentState { if res == currentState {
r.Notify(EXITSTATEEVENT, currentState, r.dest) r.Notify(EXITSTATEEVENT, currentState, r.dest)
r.Notify(ENTERSTATEEVENT, currentState, r.dest) r.Notify(ENTERSTATEEVENT, currentState, r.dest)
return nil return nil
} }
} }
return errors.New(fmt.Sprintf("Transition from %s to %s failed on model state %s", r.source, r.dest, currentState)) return fmt.Errorf("Transition from %s to %s failed on model state %s", r.source, r.dest, currentState)
} }
func (r *Transition) Subscribe(s Subscriber) { func (r *Transition) Subscribe(s Subscriber) {
r.subscriptions = append(r.subscriptions, s) r.subscriptions = append(r.subscriptions, s)
} }
func (r *Transition) Notify(on Eventtype, source State, dest State) { func (r *Transition) Notify(on Eventtype, source State, dest State) {
for _,s := range r.subscriptions { for _, s := range r.subscriptions {
s.Notify(&EventMessage{ on: on, source: source, dest: dest}) s.Notify(&EventMessage{on: on, source: source, dest: dest})
} }
} }

View File

@ -1,55 +1,55 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved. // Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine package machine
import ( import (
"log"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"log"
"testing"
) )
func setupTransition() Transitioner { func setupTransition() Transitioner {
t := NewTransition("open", "closed", "open") t := NewTransition("open", "closed", "open")
if t == nil { if t == nil {
log.Fatal("Failed creating new transition") log.Fatal("Failed creating new transition")
} }
return t return t
} }
func setupSubscriber() Subscriber { func setupSubscriber() Subscriber {
c := make(EventChannel, 2) c := make(EventChannel, 2)
return &c return &c
} }
func TestNewTransition(t *testing.T) { func TestNewTransition(t *testing.T) {
s := NewTransition("connect", "disconnected", "connected") s := NewTransition("connect", "disconnected", "connected")
if s == nil { if s == nil {
t.Errorf("Failed creating new transition") t.Errorf("Failed creating new transition")
} }
} }
func TestTransitionExecution(t *testing.T) { func TestTransitionExecution(t *testing.T) {
s := setupTransition() s := setupTransition()
m := setupModel("closed") m := setupModel("closed")
assert.Nil(t, s.Run(m)) assert.Nil(t, s.Run(m))
state := m.InspectState() state := m.InspectState()
if state != "open" { if state != "open" {
t.Errorf("Failed to transition state: %s", state) t.Errorf("Failed to transition state: %s", state)
} }
} }
func TestTransitionSubscribe(t *testing.T) { func TestTransitionSubscribe(t *testing.T) {
c := setupSubscriber() c := setupSubscriber()
s := setupTransition() s := setupTransition()
s.Subscribe(c) s.Subscribe(c)
m := setupModel("closed") m := setupModel("closed")
assert.Nil(t, s.Run(m)) assert.Nil(t, s.Run(m))
exitEvent := <- *c.(*EventChannel) exitEvent := <-*c.(*EventChannel)
enterEvent := <- *c.(*EventChannel) enterEvent := <-*c.(*EventChannel)
if exitEvent.on != EXITSTATEEVENT { if exitEvent.on != EXITSTATEEVENT {
t.Errorf("Invalid exit event") t.Errorf("Invalid exit event")
} }
if enterEvent.on != ENTERSTATEEVENT { if enterEvent.on != ENTERSTATEEVENT {
t.Errorf("Invalid enter event") t.Errorf("Invalid enter event")
} }
} }