-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmsm_test.go
More file actions
161 lines (132 loc) · 5.31 KB
/
msm_test.go
File metadata and controls
161 lines (132 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package msm_test
import (
"fmt"
"testing"
"github.com/agmt/go-msm"
"go.uber.org/zap"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type PlayerFSM struct {
*msm.FSM
invalidDisks map[string]bool
}
type EventPlay struct{}
type EventOpenClose struct{}
type EventStop struct{}
type EventPause struct{}
type EventEndPause struct{}
type EventCdDetected struct{ Name string }
type PlayerStorage struct {
}
func (p *PlayerFSM) StartPlayback(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventPlay) {
fmt.Println("Starting playback")
}
func (p *PlayerFSM) OpenDrawer(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventOpenClose) {
fmt.Println("Opening drawer")
}
func (p *PlayerFSM) CloseDrawer(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventOpenClose) {
fmt.Println("Closing drawer")
}
func (p *PlayerFSM) StoreCDInfo(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventCdDetected) {
fmt.Println("Storing CD info")
}
func (p *PlayerFSM) StoreCDInfoAndAutoStart(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventCdDetected) {
fmt.Println("Storing CD info and auto-starting")
}
func (p *PlayerFSM) StopPlayback(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventStop) {
fmt.Println("Stopping playback")
}
func (p *PlayerFSM) PausePlayback(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventPause) {
fmt.Println("Pausing playback")
}
func (p *PlayerFSM) ResumePlayback(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventEndPause) {
fmt.Println("Resuming playback")
}
func (p *PlayerFSM) StopAndOpenDrawer(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventOpenClose) {
fmt.Println("Stopping and opening drawer")
}
func (p *PlayerFSM) InvalidDisk(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventCdDetected) bool {
return p.invalidDisks[event.Name]
}
func (p *PlayerFSM) Brake(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventCdDetected) {
fmt.Println("Player has been broken")
}
func (p *PlayerFSM) GoodDiskFormat(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventCdDetected) bool {
return true
}
func (p *PlayerFSM) AutoStart(fsm *msm.FSMInstance, s *PlayerStorage, state msm.State, source string, event *EventCdDetected) bool {
return false
}
func NewPlayerFSM(manager *msm.FSMManager) *PlayerFSM {
f := &PlayerFSM{
FSM: manager.NewFSM(
"player_fsm",
func() *PlayerStorage { return &PlayerStorage{} },
),
invalidDisks: make(map[string]bool),
}
f.invalidDisks["invalid"] = true
// Define transitions in a table-like structure
f.SetTransition(
f.RowA(msm.StartState, EventPlay{}, "Playing", f.StartPlayback),
f.RowA(msm.StartState, EventOpenClose{}, "Open", f.OpenDrawer),
f.Row(msm.StartState, EventStop{}, msm.StartState),
f.RowA("Open", EventOpenClose{}, "Empty", f.CloseDrawer),
f.RowA("Empty", EventOpenClose{}, "Open", f.OpenDrawer),
f.RowAG("Empty", EventCdDetected{}, "Broken", f.Brake, f.InvalidDisk), // Guards will be called sequentially and 1st `true` will cause a transition
f.RowAG("Empty", EventCdDetected{}, msm.StartState, f.StoreCDInfo, f.GoodDiskFormat),
f.RowAG("Empty", EventCdDetected{}, "Playing", f.StoreCDInfoAndAutoStart, f.AutoStart), // Never called as `GoodDiskFormat()` always returns `true`
f.RowA("Playing", EventStop{}, msm.StartState, f.StopPlayback),
f.RowA("Playing", EventPause{}, "Paused", f.PausePlayback),
f.RowA("Playing", EventOpenClose{}, "Open", f.StopAndOpenDrawer),
f.RowA("Paused", EventEndPause{}, "Playing", f.ResumePlayback),
f.RowA("Paused", EventStop{}, msm.StartState, f.StopPlayback),
f.RowA("Paused", EventOpenClose{}, "Open", f.StopAndOpenDrawer),
)
f.SetTerminal("Broken")
return f
}
func TestPlayerFSM(t *testing.T) {
logger := zap.NewExample()
db, _ := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
manager := msm.NewFSMManager(db, logger)
playerFSM := NewPlayerFSM(manager)
manager.AutoMigrate()
manager.Start()
fsmID := playerFSM.CreateFSM()
// Simulate events and check expected state after each
transitions := []struct {
event msm.Event
expectedState string
}{
{EventPlay{}, "Playing"},
{EventOpenClose{}, "Open"},
{EventOpenClose{}, "Empty"},
{EventCdDetected{Name: "good"}, string(msm.StartState)},
{EventPlay{}, "Playing"},
{EventPause{}, "Paused"},
{EventEndPause{}, "Playing"},
{EventOpenClose{}, "Open"},
{EventOpenClose{}, "Empty"},
{EventCdDetected{Name: "invalid"}, "Broken"},
}
for i, transition := range transitions {
e := transition.event
fmt.Printf("Processing event: %T\n", e)
playerFSM.Process(fsmID, "tst", e)
manager.Sync() // wait for processing
var results []msm.FSMState
db.Table("fsm_states").Find(&results)
if results[0].State != transition.expectedState {
t.Errorf("After transition #%d %T, expected state %s but got %s", i, e, transition.expectedState, results[0].State)
t.Fail()
}
}
var results []msm.FSMState
db.Table("fsm_states").Find(&results)
if results[0].Terminated != true {
t.Errorf("Expected FSM to be terminated, but it is not: %+v", results[0])
t.Fail()
}
}