finite_machine 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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