diff --git a/machine.go b/machine.go index fa05c97..771ef8f 100644 --- a/machine.go +++ b/machine.go @@ -1,67 +1,67 @@ // Copyright 2024 Matthew Rich . 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") } diff --git a/machine_test.go b/machine_test.go index 821faa9..bd91176 100644 --- a/machine_test.go +++ b/machine_test.go @@ -1,69 +1,69 @@ // Copyright 2024 Matthew Rich . 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) } diff --git a/message.go b/message.go index 2f1e508..da2eada 100644 --- a/message.go +++ b/message.go @@ -1,30 +1,30 @@ // Copyright 2024 Matthew Rich . 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) } diff --git a/model.go b/model.go index 00176cb..6d598b5 100644 --- a/model.go +++ b/model.go @@ -1,31 +1,31 @@ // Copyright 2024 Matthew Rich . 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 } diff --git a/model_test.go b/model_test.go index d606dec..03e2184 100644 --- a/model_test.go +++ b/model_test.go @@ -1,29 +1,29 @@ // Copyright 2024 Matthew Rich . 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") + } } diff --git a/state.go b/state.go index 4274e02..c64b6c5 100644 --- a/state.go +++ b/state.go @@ -1,12 +1,11 @@ // Copyright 2024 Matthew Rich . 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} } - diff --git a/state_test.go b/state_test.go index a5da834..371e7fc 100644 --- a/state_test.go +++ b/state_test.go @@ -1,14 +1,14 @@ // Copyright 2024 Matthew Rich . 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") + } } diff --git a/subscription.go b/subscription.go index eeb0b25..8e1573a 100644 --- a/subscription.go +++ b/subscription.go @@ -1,7 +1,7 @@ // Copyright 2024 Matthew Rich . All rights reserved. - + package machine type Subscriber interface { - Notify(m *EventMessage) + Notify(m *EventMessage) } diff --git a/transition.go b/transition.go index 564f63b..8c8be1d 100644 --- a/transition.go +++ b/transition.go @@ -1,47 +1,47 @@ // Copyright 2024 Matthew Rich . 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}) + } } diff --git a/transition_test.go b/transition_test.go index 015e4a2..78f7b85 100644 --- a/transition_test.go +++ b/transition_test.go @@ -1,55 +1,55 @@ // Copyright 2024 Matthew Rich . 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") + } }