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.
package machine
import (
"errors"
"errors"
)
type State string
type Stater interface {
AddStates(name ...State)
GetState(name string) State
AddTransition(trigger string, source State, dest State)
AddSubscription(transition string, subscription Subscriber) error
AddModel(m Modeler)
Trigger(transition string) error
CurrentState() State
AddStates(name ...State)
GetState(name string) State
AddTransition(trigger string, source State, dest State)
AddSubscription(transition string, subscription Subscriber) error
AddModel(m Modeler)
Trigger(transition string) error
CurrentState() State
}
type Definition struct {
states []State
triggers map[string]Transitioner
model Modeler
states []State
triggers map[string]Transitioner
model Modeler
}
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) {
d.states = append(d.states, name...)
d.states = append(d.states, name...)
}
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 {
var r State
for _,s := range(d.states) {
if string(s) == name {
return s
}
}
return r
var r State
for _, s := range d.states {
if string(s) == name {
return s
}
}
return r
}
func (d *Definition) AddModel(m Modeler) {
d.model = m
d.model = m
}
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 {
return d.model.InspectState()
return d.model.InspectState()
}
func (d *Definition) AddSubscription(transition string, subscription Subscriber) error {
if t,ok := d.triggers[transition]; ok {
t.Subscribe(subscription)
if t, ok := d.triggers[transition]; ok {
t.Subscribe(subscription)
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.
package machine
import(
"log"
"testing"
import (
"github.com/stretchr/testify/assert"
"log"
"testing"
)
func setupStater(initial State) Stater {
s := New(initial)
if s == nil {
log.Fatal("Failed creating new Stater")
}
return s
s := New(initial)
if s == nil {
log.Fatal("Failed creating new Stater")
}
return s
}
func TestMachineStater(t *testing.T) {
s := setupStater("disconnected")
s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected")
r := s.GetState("connected")
if r != "connected" {
t.Errorf("Failed checking state: %s", r)
}
s.AddStates("disconnected", "start_connection", "connected")
r := s.GetState("connected")
if r != "connected" {
t.Errorf("Failed checking state: %s", r)
}
}
func TestMachineCurrentState(t *testing.T) {
s := setupStater("disconnected")
s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected")
r := s.CurrentState()
if r != "disconnected" {
t.Errorf("Failed checking state: %s", r)
}
s.AddStates("disconnected", "start_connection", "connected")
r := s.CurrentState()
if r != "disconnected" {
t.Errorf("Failed checking state: %s", r)
}
}
func TestMachineAddTransition(t *testing.T) {
s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected")
s.AddTransition("connect", "disconnected", "start_connection")
s.AddModel(setupModel("disconnected"))
// worker gets a trigger message
assert.Nil(t, s.Trigger("connect"))
// machine generates transition event mesages
if s.CurrentState() != "start_connection" {
t.Errorf("State transition failed for: connect - %s", s.CurrentState())
}
s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected")
s.AddTransition("connect", "disconnected", "start_connection")
s.AddModel(setupModel("disconnected"))
// worker gets a trigger message
assert.Nil(t, s.Trigger("connect"))
// machine generates transition event mesages
if s.CurrentState() != "start_connection" {
t.Errorf("State transition failed for: connect - %s", s.CurrentState())
}
}
func TestMachineAddSubscription(t *testing.T) {
x := setupSubscriber()
s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected")
s.AddTransition("connect", "disconnected", "start_connection")
assert.Nil(t, s.AddSubscription("connect", x))
s.AddModel(setupModel("disconnected"))
s.Trigger("connect")
exitMessage := <- *x.(*EventChannel)
enterMessage := <- *x.(*EventChannel)
if exitMessage.on == EXITSTATEEVENT && exitMessage.source == "disconnected" {
if enterMessage.on == ENTERSTATEEVENT && enterMessage.dest == "start_connection" {
return
}
}
t.Errorf("Unexpected event message in state transition notification: exit: %s, enter: %s", exitMessage, enterMessage)
x := setupSubscriber()
s := setupStater("disconnected")
s.AddStates("disconnected", "start_connection", "connected")
s.AddTransition("connect", "disconnected", "start_connection")
assert.Nil(t, s.AddSubscription("connect", x))
s.AddModel(setupModel("disconnected"))
s.Trigger("connect")
exitMessage := <-*x.(*EventChannel)
enterMessage := <-*x.(*EventChannel)
if exitMessage.on == EXITSTATEEVENT && exitMessage.source == "disconnected" {
if enterMessage.on == ENTERSTATEEVENT && enterMessage.dest == "start_connection" {
return
}
}
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.
package machine
import (
"fmt"
"fmt"
)
const (
ENTERSTATEEVENT = iota
EXITSTATEEVENT
ENTERSTATEEVENT = iota
EXITSTATEEVENT
)
type Eventtype int
type EventMessage struct {
on Eventtype
source State
dest State
on Eventtype
source State
dest State
}
type EventChannel chan EventMessage
func (e *EventChannel) Notify(m *EventMessage) {
*e <- *m
*e <- *m
}
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.
package machine
type Modeler interface {
Trigger(transition Transitioner) error
ChangeState(target State) State
InspectState() State
Trigger(transition Transitioner) error
ChangeState(target State) State
InspectState() State
}
type Model struct {
state State
state State
}
func NewModel(initial State) Modeler {
return &Model{ state: initial }
return &Model{state: initial}
}
func (m *Model) Trigger(transition Transitioner) error {
return transition.Run(m)
return transition.Run(m)
}
func (m *Model) ChangeState(target State) State {
oldState := m.state
m.state = target
return oldState
oldState := m.state
m.state = target
return oldState
}
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.
package machine
import (
"testing"
"testing"
)
func setupModel(initial State) Modeler {
return NewModel(initial)
return NewModel(initial)
}
func TestMachineModel(t *testing.T) {
m := setupModel("")
if m == nil {
t.Errorf("Failed creating new model")
}
m := setupModel("")
if m == nil {
t.Errorf("Failed creating new model")
}
}
func TestMachineModelChangeState(t *testing.T) {
m := setupModel("")
oldState := m.ChangeState("connected")
if oldState != "" {
t.Errorf("Failed changing state in model")
}
if m.InspectState() != "connected" {
t.Errorf("Failed changing state in model")
}
m := setupModel("")
oldState := m.ChangeState("connected")
if oldState != "" {
t.Errorf("Failed changing state in model")
}
if m.InspectState() != "connected" {
t.Errorf("Failed changing state in model")
}
}

View File

@ -1,12 +1,11 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine
type mState struct {
name string
name string
}
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.
package machine
import (
"testing"
"testing"
)
func TestMachineState(t *testing.T) {
s := NewState("connected")
if s == nil {
t.Errorf("Failed creating new state")
}
s := NewState("connected")
if s == nil {
t.Errorf("Failed creating new state")
}
}

View File

@ -1,7 +1,7 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package machine
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.
package machine
import (
"fmt"
"errors"
_ "errors"
"fmt"
)
type Transitioner interface {
Run(m Modeler) error
Subscribe(s Subscriber)
Run(m Modeler) error
Subscribe(s Subscriber)
}
type Transition struct {
trigger string
source State
dest State
subscriptions []Subscriber
trigger string
source State
dest State
subscriptions []Subscriber
}
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 {
currentState := m.InspectState()
if currentState == r.source {
res := m.ChangeState(r.dest)
if res == currentState {
r.Notify(EXITSTATEEVENT, currentState, r.dest)
r.Notify(ENTERSTATEEVENT, currentState, r.dest)
return nil
}
}
return errors.New(fmt.Sprintf("Transition from %s to %s failed on model state %s", r.source, r.dest, currentState))
currentState := m.InspectState()
if currentState == r.source {
res := m.ChangeState(r.dest)
if res == currentState {
r.Notify(EXITSTATEEVENT, currentState, r.dest)
r.Notify(ENTERSTATEEVENT, currentState, r.dest)
return nil
}
}
return fmt.Errorf("Transition from %s to %s failed on model state %s", r.source, r.dest, currentState)
}
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) {
for _,s := range r.subscriptions {
s.Notify(&EventMessage{ on: on, source: source, dest: dest})
}
for _, s := range r.subscriptions {
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.
package machine
import (
"log"
"testing"
"github.com/stretchr/testify/assert"
"log"
"testing"
)
func setupTransition() Transitioner {
t := NewTransition("open", "closed", "open")
if t == nil {
log.Fatal("Failed creating new transition")
}
return t
t := NewTransition("open", "closed", "open")
if t == nil {
log.Fatal("Failed creating new transition")
}
return t
}
func setupSubscriber() Subscriber {
c := make(EventChannel, 2)
return &c
c := make(EventChannel, 2)
return &c
}
func TestNewTransition(t *testing.T) {
s := NewTransition("connect", "disconnected", "connected")
if s == nil {
t.Errorf("Failed creating new transition")
}
s := NewTransition("connect", "disconnected", "connected")
if s == nil {
t.Errorf("Failed creating new transition")
}
}
func TestTransitionExecution(t *testing.T) {
s := setupTransition()
m := setupModel("closed")
assert.Nil(t, s.Run(m))
state := m.InspectState()
if state != "open" {
t.Errorf("Failed to transition state: %s", state)
}
s := setupTransition()
m := setupModel("closed")
assert.Nil(t, s.Run(m))
state := m.InspectState()
if state != "open" {
t.Errorf("Failed to transition state: %s", state)
}
}
func TestTransitionSubscribe(t *testing.T) {
c := setupSubscriber()
s := setupTransition()
s.Subscribe(c)
m := setupModel("closed")
assert.Nil(t, s.Run(m))
exitEvent := <- *c.(*EventChannel)
enterEvent := <- *c.(*EventChannel)
if exitEvent.on != EXITSTATEEVENT {
t.Errorf("Invalid exit event")
}
if enterEvent.on != ENTERSTATEEVENT {
t.Errorf("Invalid enter event")
}
c := setupSubscriber()
s := setupTransition()
s.Subscribe(c)
m := setupModel("closed")
assert.Nil(t, s.Run(m))
exitEvent := <-*c.(*EventChannel)
enterEvent := <-*c.(*EventChannel)
if exitEvent.on != EXITSTATEEVENT {
t.Errorf("Invalid exit event")
}
if enterEvent.on != ENTERSTATEEVENT {
t.Errorf("Invalid enter event")
}
}