finite_machine 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FiniteMachine, 'can?' do
6
+
7
+ it "allows to check if event can be fired" do
8
+ fsm = FiniteMachine.define do
9
+ initial :green
10
+
11
+ events {
12
+ event :slow, :green => :yellow
13
+ event :stop, :yellow => :red
14
+ event :ready, :red => :yellow
15
+ event :go, :yellow => :green
16
+ }
17
+ end
18
+
19
+ expect(fsm.current).to eql(:green)
20
+
21
+ expect(fsm.can?(:slow)).to be_true
22
+ expect(fsm.can?(:stop)).to be_false
23
+ expect(fsm.can?(:ready)).to be_false
24
+ expect(fsm.can?(:go)).to be_false
25
+
26
+ fsm.slow
27
+ expect(fsm.current).to eql(:yellow)
28
+
29
+ expect(fsm.can?(:slow)).to be_false
30
+ expect(fsm.can?(:stop)).to be_true
31
+ expect(fsm.can?(:ready)).to be_false
32
+ expect(fsm.can?(:go)).to be_true
33
+
34
+ fsm.stop
35
+ expect(fsm.current).to eql(:red)
36
+
37
+ expect(fsm.can?(:slow)).to be_false
38
+ expect(fsm.can?(:stop)).to be_false
39
+ expect(fsm.can?(:ready)).to be_true
40
+ expect(fsm.can?(:go)).to be_false
41
+
42
+ fsm.ready
43
+ expect(fsm.current).to eql(:yellow)
44
+
45
+ expect(fsm.can?(:slow)).to be_false
46
+ expect(fsm.can?(:stop)).to be_true
47
+ expect(fsm.can?(:ready)).to be_false
48
+ expect(fsm.can?(:go)).to be_true
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FiniteMachine, 'define' do
6
+
7
+ it "creates system state machine" do
8
+ fsm = FiniteMachine.define do
9
+ initial :green
10
+
11
+ events {
12
+ event :slow, :green => :yellow
13
+ event :stop, :yellow => :red
14
+ event :ready, :red => :yellow
15
+ event :go, :yellow => :green
16
+ }
17
+ end
18
+
19
+ expect(fsm.current).to eql(:green)
20
+
21
+ fsm.slow
22
+ expect(fsm.current).to eql(:yellow)
23
+ fsm.stop
24
+ expect(fsm.current).to eql(:red)
25
+ fsm.ready
26
+ expect(fsm.current).to eql(:yellow)
27
+ fsm.go
28
+ expect(fsm.current).to eql(:green)
29
+ end
30
+
31
+ xit "creates multiple machines"
32
+ end
@@ -0,0 +1,256 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FiniteMachine, 'events' do
6
+
7
+ it "allows for hash rocket syntax to describe transition" do
8
+ fsm = FiniteMachine.define do
9
+ initial :green
10
+
11
+ events {
12
+ event :slow, :green => :yellow
13
+ event :stop, :yellow => :red
14
+ }
15
+ end
16
+
17
+ expect(fsm.current).to eql(:green)
18
+ fsm.slow
19
+ expect(fsm.current).to eql(:yellow)
20
+ fsm.stop
21
+ expect(fsm.current).to eql(:red)
22
+ end
23
+
24
+ it "allows for (:from | :to) key pairs to describe transition" do
25
+ fsm = FiniteMachine.define do
26
+ initial :green
27
+
28
+ events {
29
+ event :slow, from: :green, to: :yellow
30
+ event :stop, from: :yellow, to: :red
31
+ }
32
+ end
33
+
34
+ expect(fsm.current).to eql(:green)
35
+ fsm.slow
36
+ expect(fsm.current).to eql(:yellow)
37
+ fsm.stop
38
+ expect(fsm.current).to eql(:red)
39
+ end
40
+
41
+ it "permits no-op event without 'to' transition" do
42
+ fsm = FiniteMachine.define do
43
+ initial :green
44
+
45
+ events {
46
+ event :noop, from: :green
47
+ event :slow, from: :green, to: :yellow
48
+ event :stop, from: :yellow, to: :red
49
+ event :ready, from: :red, to: :yellow
50
+ event :go, from: :yellow, to: :green
51
+ }
52
+ end
53
+
54
+ expect(fsm.current).to eql(:green)
55
+
56
+ expect(fsm.can?(:noop)).to be_true
57
+ expect(fsm.can?(:slow)).to be_true
58
+
59
+ fsm.noop
60
+ expect(fsm.current).to eql(:green)
61
+ fsm.slow
62
+ expect(fsm.current).to eql(:yellow)
63
+
64
+ expect(fsm.cannot?(:noop)).to be_true
65
+ expect(fsm.cannot?(:slow)).to be_true
66
+ end
67
+
68
+ it "permits event from any state with :any 'from'" do
69
+ fsm = FiniteMachine.define do
70
+ initial :green
71
+
72
+ events {
73
+ event :slow, from: :green, to: :yellow
74
+ event :stop, from: :yellow, to: :red
75
+ event :ready, from: :red, to: :yellow
76
+ event :go, from: :yellow, to: :green
77
+ event :run, from: :any, to: :green
78
+ }
79
+ end
80
+
81
+ expect(fsm.current).to eql(:green)
82
+
83
+ fsm.slow
84
+ expect(fsm.current).to eql(:yellow)
85
+ fsm.run
86
+ expect(fsm.current).to eql(:green)
87
+
88
+ fsm.slow
89
+ expect(fsm.current).to eql(:yellow)
90
+ fsm.stop
91
+ expect(fsm.current).to eql(:red)
92
+ fsm.run
93
+ expect(fsm.current).to eql(:green)
94
+
95
+ fsm.slow
96
+ expect(fsm.current).to eql(:yellow)
97
+ fsm.go
98
+ expect(fsm.current).to eql(:green)
99
+ fsm.run
100
+ expect(fsm.current).to eql(:green)
101
+ end
102
+
103
+ it "permits event from any state without 'from'" do
104
+ fsm = FiniteMachine.define do
105
+ initial :green
106
+
107
+ events {
108
+ event :slow, from: :green, to: :yellow
109
+ event :stop, from: :yellow, to: :red
110
+ event :ready, from: :red, to: :yellow
111
+ event :go, from: :yellow, to: :green
112
+ event :run, to: :green
113
+ }
114
+ end
115
+
116
+ expect(fsm.current).to eql(:green)
117
+
118
+ fsm.slow
119
+ expect(fsm.current).to eql(:yellow)
120
+ fsm.run
121
+ expect(fsm.current).to eql(:green)
122
+
123
+ fsm.slow
124
+ expect(fsm.current).to eql(:yellow)
125
+ fsm.stop
126
+ expect(fsm.current).to eql(:red)
127
+ fsm.run
128
+ expect(fsm.current).to eql(:green)
129
+
130
+ fsm.slow
131
+ expect(fsm.current).to eql(:yellow)
132
+ fsm.go
133
+ expect(fsm.current).to eql(:green)
134
+ fsm.run
135
+ expect(fsm.current).to eql(:green)
136
+ end
137
+
138
+ it "raises error on invalid transition" do
139
+ fsm = FiniteMachine.define do
140
+ initial :green
141
+
142
+ events {
143
+ event :slow, from: :green, to: :yellow
144
+ event :stop, from: :yellow, to: :red
145
+ }
146
+ end
147
+
148
+ expect(fsm.current).to eql(:green)
149
+
150
+ expect { fsm.stop }.to raise_error(FiniteMachine::TransitionError, /state 'green'/)
151
+ end
152
+
153
+ context 'when multiple from states' do
154
+ it "allows for array from key" do
155
+ fsm = FiniteMachine.define do
156
+ initial :green
157
+
158
+ events {
159
+ event :slow, :green => :yellow
160
+ event :stop, [:green, :yellow] => :red
161
+ event :ready, :red => :yellow
162
+ event :go, [:yellow, :red] => :green
163
+ }
164
+ end
165
+
166
+ expect(fsm.current).to eql(:green)
167
+
168
+ expect(fsm.can?(:slow)).to be_true
169
+ expect(fsm.can?(:stop)).to be_true
170
+ expect(fsm.cannot?(:ready)).to be_true
171
+ expect(fsm.cannot?(:go)).to be_true
172
+
173
+ fsm.slow; expect(fsm.current).to eql(:yellow)
174
+ fsm.stop; expect(fsm.current).to eql(:red)
175
+ fsm.ready; expect(fsm.current).to eql(:yellow)
176
+ fsm.go; expect(fsm.current).to eql(:green)
177
+
178
+ fsm.stop; expect(fsm.current).to eql(:red)
179
+ fsm.go; expect(fsm.current).to eql(:green)
180
+ end
181
+
182
+ it "allows for hash of states" do
183
+ fsm = FiniteMachine.define do
184
+ initial :green
185
+
186
+ events {
187
+ event :slow, :green => :yellow
188
+ event :stop, :green => :red, :yellow => :red
189
+ event :ready, :red => :yellow
190
+ event :go, :yellow => :green, :red => :green
191
+ }
192
+ end
193
+
194
+ expect(fsm.current).to eql(:green)
195
+
196
+ expect(fsm.can?(:slow)).to be_true
197
+ expect(fsm.can?(:stop)).to be_true
198
+ expect(fsm.cannot?(:ready)).to be_true
199
+ expect(fsm.cannot?(:go)).to be_true
200
+
201
+ fsm.slow; expect(fsm.current).to eql(:yellow)
202
+ fsm.stop; expect(fsm.current).to eql(:red)
203
+ fsm.ready; expect(fsm.current).to eql(:yellow)
204
+ fsm.go; expect(fsm.current).to eql(:green)
205
+
206
+ fsm.stop; expect(fsm.current).to eql(:red)
207
+ fsm.go; expect(fsm.current).to eql(:green)
208
+ end
209
+ end
210
+
211
+ it "groups events with the same name" do
212
+ fsm = FiniteMachine.define do
213
+ initial :green
214
+
215
+ events {
216
+ event :stop, :green => :yellow
217
+ event :stop, :yellow => :red
218
+ event :stop, :red => :pink
219
+ }
220
+ end
221
+
222
+ expect(fsm.current).to eql(:green)
223
+
224
+ expect(fsm.can?(:stop)).to be_true
225
+
226
+ fsm.stop
227
+ expect(fsm.current).to eql(:yellow)
228
+ fsm.stop
229
+ expect(fsm.current).to eql(:red)
230
+ fsm.stop
231
+ expect(fsm.current).to eql(:pink)
232
+ end
233
+
234
+ it "returns values for events" do
235
+ fsm = FiniteMachine.define do
236
+ initial :neutral
237
+
238
+ events {
239
+ event :start, :neutral => :engine_on
240
+ event :drive, :engine_on => :running, if: -> { return false }
241
+ event :stop, :any => :neutral
242
+ }
243
+
244
+ callbacks {
245
+ on_enter(:drive) { }
246
+ on_exit(:stop) { }
247
+ }
248
+ end
249
+
250
+ expect(fsm.current).to eql(:neutral)
251
+ expect(fsm.start).to eql(FiniteMachine::SUCCEEDED)
252
+ expect(fsm.drive).to eql(FiniteMachine::CANCELLED)
253
+ expect(fsm.stop).to eql(FiniteMachine::SUCCEEDED)
254
+ expect(fsm.stop).to eql(FiniteMachine::NOTRANSITION)
255
+ end
256
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FiniteMachine, 'finished?' do
6
+
7
+ it "allows to specify terminal state" do
8
+ fsm = FiniteMachine.define do
9
+ initial :green
10
+ terminal :red
11
+
12
+ events {
13
+ event :slow, :green => :yellow
14
+ event :stop, :yellow => :red
15
+ }
16
+ end
17
+
18
+ expect(fsm.current).to eql(:green)
19
+ expect(fsm.finished?).to be_false
20
+
21
+ fsm.slow
22
+ expect(fsm.current).to eql(:yellow)
23
+ expect(fsm.finished?).to be_false
24
+
25
+ fsm.stop
26
+ expect(fsm.current).to eql(:red)
27
+ expect(fsm.finished?).to be_true
28
+ end
29
+
30
+ it "checks without terminal state" do
31
+ fsm = FiniteMachine.define do
32
+ initial :green
33
+
34
+ events {
35
+ event :slow, :green => :yellow
36
+ event :stop, :yellow => :red
37
+ }
38
+ end
39
+
40
+ expect(fsm.current).to eql(:green)
41
+ expect(fsm.finished?).to be_false
42
+
43
+ fsm.slow
44
+ expect(fsm.current).to eql(:yellow)
45
+ expect(fsm.finished?).to be_false
46
+
47
+ fsm.stop
48
+ expect(fsm.current).to eql(:red)
49
+ expect(fsm.finished?).to be_false
50
+ end
51
+ end
@@ -0,0 +1,196 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FiniteMachine, ':if, :unless' do
6
+ before(:all) {
7
+ Car = Class.new do
8
+ def turn_engine_on
9
+ @engine_on = true
10
+ end
11
+
12
+ def turn_engine_off
13
+ @engine_on = false
14
+ end
15
+
16
+ def engine_on?
17
+ !!@engine_on
18
+ end
19
+ end
20
+ }
21
+
22
+ it "allows to cancel event with :if option" do
23
+ called = []
24
+
25
+ fsm = FiniteMachine.define do
26
+ initial :green
27
+
28
+ events {
29
+ event :slow, :green => :yellow, if: -> { return false }
30
+ event :stop, :yellow => :red
31
+ }
32
+
33
+ callbacks {
34
+ # generic callbacks
35
+ on_enter do |event| called << 'on_enter' end
36
+ on_transition do |event| called << 'on_transition' end
37
+ on_exit do |event| called << 'on_exit' end
38
+
39
+ # state callbacks
40
+ on_enter :green do |event| called << 'on_enter_green' end
41
+ on_enter :yellow do |event| called << "on_enter_yellow" end
42
+
43
+ on_transition :green do |event| called << 'on_transition_green' end
44
+ on_transition :yellow do |event| called << "on_transition_yellow" end
45
+
46
+ on_exit :green do |event| called << 'on_exit_green' end
47
+ on_exit :yellow do |event| called << "on_exit_yellow" end
48
+ }
49
+ end
50
+
51
+ expect(fsm.current).to eql(:green)
52
+ called = []
53
+ fsm.slow
54
+ expect(fsm.current).to eql(:green)
55
+ expect(called).to eql([])
56
+ end
57
+
58
+ it "allows to cancel event with :unless option" do
59
+ called = []
60
+
61
+ fsm = FiniteMachine.define do
62
+ initial :green
63
+
64
+ events {
65
+ event :slow, :green => :yellow, unless: -> { return true }
66
+ event :stop, :yellow => :red
67
+ }
68
+
69
+ callbacks {
70
+ # generic callbacks
71
+ on_enter do |event| called << 'on_enter' end
72
+ on_transition do |event| called << 'on_transition' end
73
+ on_exit do |event| called << 'on_exit' end
74
+
75
+ # state callbacks
76
+ on_enter :green do |event| called << 'on_enter_green' end
77
+ on_enter :yellow do |event| called << "on_enter_yellow" end
78
+
79
+ on_transition :green do |event| called << 'on_transition_green' end
80
+ on_transition :yellow do |event| called << "on_transition_yellow" end
81
+
82
+ on_exit :green do |event| called << 'on_exit_green' end
83
+ on_exit :yellow do |event| called << "on_exit_yellow" end
84
+ }
85
+ end
86
+
87
+ expect(fsm.current).to eql(:green)
88
+ called = []
89
+ fsm.slow
90
+ expect(fsm.current).to eql(:green)
91
+ expect(called).to eql([])
92
+ end
93
+
94
+ it "allows to combine conditionals" do
95
+ conditions = []
96
+
97
+ fsm = FiniteMachine.define do
98
+ initial :green
99
+
100
+ events {
101
+ event :slow, :green => :yellow,
102
+ if: [ -> { conditions << 'first_if'; return true },
103
+ -> { conditions << 'second_if'; return true}],
104
+ unless: -> { conditions << 'first_unless'; return true }
105
+ event :stop, :yellow => :red
106
+ }
107
+ end
108
+
109
+ expect(fsm.current).to eql(:green)
110
+ fsm.slow
111
+ expect(fsm.current).to eql(:green)
112
+ expect(conditions).to eql([
113
+ 'first_if',
114
+ 'second_if',
115
+ 'first_unless'
116
+ ])
117
+ end
118
+
119
+ it "specifies :if and :unless with proc" do
120
+ car = Car.new
121
+
122
+ fsm = FiniteMachine.define do
123
+ initial :neutral
124
+
125
+ target car
126
+
127
+ events {
128
+ event :start, :neutral => :one, if: -> (car) { car.engine_on? }
129
+ event :shift, :one => :two
130
+ }
131
+ end
132
+ car.turn_engine_off
133
+ expect(car.engine_on?).to be_false
134
+ expect(fsm.current).to eql(:neutral)
135
+ fsm.start
136
+ expect(fsm.current).to eql(:neutral)
137
+
138
+ car.turn_engine_on
139
+ expect(car.engine_on?).to be_true
140
+ expect(fsm.current).to eql(:neutral)
141
+ fsm.start
142
+ expect(fsm.current).to eql(:one)
143
+ end
144
+
145
+ it "specifies :if and :unless with symbol" do
146
+ car = Car.new
147
+
148
+ fsm = FiniteMachine.define do
149
+ initial :neutral
150
+
151
+ target car
152
+
153
+ events {
154
+ event :start, :neutral => :one, if: :engine_on?
155
+ event :shift, :one => :two
156
+ }
157
+ end
158
+ car.turn_engine_off
159
+ expect(car.engine_on?).to be_false
160
+ expect(fsm.current).to eql(:neutral)
161
+ fsm.start
162
+ expect(fsm.current).to eql(:neutral)
163
+
164
+ car.turn_engine_on
165
+ expect(car.engine_on?).to be_true
166
+ expect(fsm.current).to eql(:neutral)
167
+ fsm.start
168
+ expect(fsm.current).to eql(:one)
169
+ end
170
+
171
+ it "specifies :if and :unless with string" do
172
+ car = Car.new
173
+
174
+ fsm = FiniteMachine.define do
175
+ initial :neutral
176
+
177
+ target car
178
+
179
+ events {
180
+ event :start, :neutral => :one, if: "engine_on?"
181
+ event :shift, :one => :two
182
+ }
183
+ end
184
+ car.turn_engine_off
185
+ expect(car.engine_on?).to be_false
186
+ expect(fsm.current).to eql(:neutral)
187
+ fsm.start
188
+ expect(fsm.current).to eql(:neutral)
189
+
190
+ car.turn_engine_on
191
+ expect(car.engine_on?).to be_true
192
+ expect(fsm.current).to eql(:neutral)
193
+ fsm.start
194
+ expect(fsm.current).to eql(:one)
195
+ end
196
+ end