Skip to content

Commit 5b04184

Browse files
committed
Refactor tests
1 parent ecd7bcd commit 5b04184

File tree

4 files changed

+423
-96
lines changed

4 files changed

+423
-96
lines changed

act/statemachine.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"reflect"
66
"runtime"
77
"strings"
8+
"time"
89

910
"ergo.services/ergo/gen"
1011
"ergo.services/ergo/lib"
@@ -76,11 +77,28 @@ type StateMachine[D any] struct {
7677
stateEnterCallback StateEnterCallback[D]
7778
}
7879

80+
type Action interface {
81+
isAction()
82+
}
83+
84+
type StateTimeout[M any] struct {
85+
Duration time.Duration
86+
message M
87+
}
88+
89+
func (StateTimeout[M]) IsAction() {}
90+
91+
// state_timeout
92+
// timeout
93+
7994
// Type alias for MessageHandler callbacks.
8095
// D is the type of the data associated with the StateMachine.
8196
// M is the type of the message this handler accepts.
8297
type StateMessageHandler[D any, M any] func(gen.Atom, D, M, gen.Process) (gen.Atom, D, error)
8398

99+
// new version with actions
100+
//type StateMessageHandler[D any, M any] func(gen.Atom, D, M, gen.Process) (gen.Atom, D, []Action, error)
101+
84102
// Type alias for CallHandler callbacks.
85103
// D is the type of the data associated with the StateMachine.
86104
// M is the type of the message this handler accepts.
@@ -302,7 +320,7 @@ func (sm *StateMachine[D]) ProcessRun() (rr error) {
302320
panic(fmt.Sprintf("Error monitoring event: %v.", err))
303321
}
304322
}
305-
sm.Log().Info("StateMachine %s is now monitoring events", sm.PID)
323+
sm.Log().Info("StateMachine %s is now monitoring events", sm.PID())
306324
return nil
307325

308326
default:

tests/001_local/t018_statemachine_test.go

Lines changed: 29 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ import (
88
"ergo.services/ergo"
99
"ergo.services/ergo/act"
1010
"ergo.services/ergo/gen"
11-
"ergo.services/ergo/lib"
1211
)
1312

14-
//
15-
// this is the template for writing new tests
16-
//
17-
1813
var (
1914
t18cases []*testcase
2015
)
@@ -45,148 +40,87 @@ func (t *t18) HandleMessage(from gen.PID, message any) error {
4540
return nil
4641
}
4742

48-
func factory_t18statemachine() gen.ProcessBehavior {
49-
return &t18statemachine{}
50-
}
51-
52-
type t18statemachine struct {
53-
act.StateMachine[t18data]
54-
tc *testcase
43+
// Test state transitions with messages and calls
44+
func factory_t18_state_transitions() gen.ProcessBehavior {
45+
return &t18_state_transitions{}
5546
}
5647

57-
type t18data struct {
58-
transitions int
59-
stateEnterCallbacks int
60-
testEventReceived bool
48+
type t18_state_transitions struct {
49+
act.StateMachine[t18_state_transitions_data]
6150
}
6251

63-
type t18transitionState1toState2 struct {
52+
type t18_state_transitions_data struct {
53+
transitions int
6454
}
6555

66-
type t18transitionState2toState1 struct {
56+
type t18_state2 struct {
6757
}
6858

69-
type t18query struct {
59+
type t18_get_transitions struct {
7060
}
7161

72-
type t18event struct {
73-
payload string
74-
}
75-
76-
func (sm *t18statemachine) Init(args ...any) (act.StateMachineSpec[t18data], error) {
62+
func (sm *t18_state_transitions) Init(args ...any) (act.StateMachineSpec[t18_state_transitions_data], error) {
7763
spec := act.NewStateMachineSpec(gen.Atom("state1"),
7864
// initial data
79-
act.WithData(t18data{}),
80-
81-
// set up a message handler for the transition state1 -> state2
82-
act.WithStateMessageHandler(gen.Atom("state1"), state1to2),
83-
84-
// set up a call handler to query the data
85-
act.WithStateCallHandler(gen.Atom("state3"), queryData),
65+
act.WithData(t18_state_transitions_data{}),
8666

87-
// set up a state enter callback
88-
act.WithStateEnterCallback(stateEnter),
67+
// set up a message handler for the transition [state1] -> [state2]
68+
act.WithStateMessageHandler(gen.Atom("state1"), t18_move_to_state2),
8969

90-
// register event handler
91-
act.WithEventHandler(gen.Event{Name: "testEvent", Node: "t18node@localhost"}, handleTestEvent),
70+
// set up a call handler to query the number of state transitions
71+
act.WithStateCallHandler(gen.Atom("state2"), t18_total_transitions),
9272
)
9373

9474
return spec, nil
9575
}
9676

97-
func state1to2(state gen.Atom, data t18data, message t18transitionState1toState2, proc gen.Process) (gen.Atom, t18data, error) {
77+
func t18_move_to_state2(state gen.Atom, data t18_state_transitions_data, message t18_state2, proc gen.Process) (gen.Atom, t18_state_transitions_data, error) {
9878
data.transitions++
9979
return gen.Atom("state2"), data, nil
10080
}
10181

102-
func queryData(state gen.Atom, data t18data, message t18query, proc gen.Process) (gen.Atom, t18data, t18data, error) {
103-
return state, data, data, nil
104-
}
105-
106-
func stateEnter(oldState gen.Atom, newState gen.Atom, data t18data, proc gen.Process) (gen.Atom, t18data, error) {
107-
data.stateEnterCallbacks++
108-
109-
if newState == gen.Atom("state2") {
110-
data.transitions++
111-
return gen.Atom("state3"), data, nil
112-
113-
}
114-
return newState, data, nil
115-
}
116-
117-
func handleTestEvent(state gen.Atom, data t18data, event t18event, proc gen.Process) (gen.Atom, t18data, error) {
118-
data.testEventReceived = true
119-
return state, data, nil
82+
func t18_total_transitions(state gen.Atom, data t18_state_transitions_data, message t18_get_transitions, proc gen.Process) (gen.Atom, t18_state_transitions_data, int, error) {
83+
return state, data, data.transitions, nil
12084
}
12185

12286
func (t *t18) TestStateMachine(input any) {
12387
defer func() {
12488
t.testcase = nil
12589
}()
12690

127-
// Register the event first, otherwise the StateMachine will not be able
128-
// to start monitoring.
129-
testEvent := gen.Atom("testEvent")
130-
token, err := t.RegisterEvent(testEvent, gen.EventOptions{})
131-
132-
pid, err := t.Spawn(factory_t18statemachine, gen.ProcessOptions{})
91+
pid, err := t.Spawn(factory_t18_state_transitions, gen.ProcessOptions{})
13392
if err != nil {
13493
t.Log().Error("unable to spawn statemachine process: %s", err)
13594
t.testcase.err <- err
13695
return
13796
}
13897

139-
// Send a message to transition to state 2. The state enter callback should
140-
// automatically transition to state 3 where another state enter callback
141-
// does not trigger any further state transitions.
142-
err = t.Send(pid, t18transitionState1toState2{})
98+
// Send a message to transition to state 2.
99+
err = t.Send(pid, t18_state2{})
143100
if err != nil {
144-
t.Log().Error("send 't18transitionState1toState2' failed: %s", err)
101+
t.Log().Error("send 't18_state2' failed: %s", err)
145102
t.testcase.err <- err
146103
return
147104
}
148105

149106
// Query the data from the state machine (and test StateCallHandler behavior)
150-
result, err := t.Call(pid, t18query{})
107+
result, err := t.Call(pid, t18_get_transitions{})
151108
if err != nil {
152-
t.Log().Error("call 't18query' failed: %s", err)
109+
t.Log().Error("call 't18_get_transitions' failed: %s", err)
153110
t.testcase.err <- err
154111
return
155112
}
156113

157-
// We expect 2 state transitions (state1 -> state2 -> state3)
158-
data := result.(t18data)
159-
if data.transitions != 2 {
160-
t.testcase.err <- fmt.Errorf("expected 2 state transitions, got %v", result)
161-
return
162-
}
163-
164-
// We expect a chain of 2 state enter callback functions to be called, one
165-
// for state2 and one for state3.
166-
if data.stateEnterCallbacks != 2 {
167-
t.testcase.err <- fmt.Errorf("expected 2 state enter function invocations, got %d", data.stateEnterCallbacks)
168-
return
169-
}
170-
171-
event := t18event{lib.RandomString(8)}
172-
t.SendEvent(testEvent, token, event)
173-
174-
// Query the data from the state machine
175-
result, err = t.Call(pid, t18query{})
176-
if err != nil {
177-
t.Log().Error("call 't18query' failed: %s", err)
178-
t.testcase.err <- err
179-
return
180-
}
181-
data = result.(t18data)
182-
if data.testEventReceived == false {
183-
t.testcase.err <- fmt.Errorf("expected test event to be received")
114+
// We expect 1 state transitions: [state1] -> [state2]
115+
data := result.(int)
116+
if data != 1 {
117+
t.testcase.err <- fmt.Errorf("expected 1 state transitions, got %v", result)
184118
return
185119
}
186120

187121
// Statemachine process should crash on invalid state transition
188122
err = t.testcase.expectProcessToTerminate(pid, t, func(p gen.Process) error {
189-
return p.Send(pid, t18transitionState2toState1{}) // we are in state3
123+
return p.Send(pid, t18_state2{}) // we are in state2
190124
})
191125
if err != nil {
192126
t.testcase.err <- err

0 commit comments

Comments
 (0)