golem_statemachine 0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,429 @@
1
+ require 'test_helper'
2
+ require 'statemachine_assertions'
3
+ require File.dirname(__FILE__)+'/../lib/golem'
4
+
5
+ # Because Monster has two statemachines, 'affect' and 'mouth', this test
6
+ # allows for validating statemachine behaviour when there are multiple
7
+ # statemachines defined within the same class.
8
+ class MonsterTest < Test::Unit::TestCase
9
+ include StatemachineAssertions
10
+
11
+ def setup
12
+ @klass = Class.new
13
+ @klass.instance_eval do
14
+ include Golem
15
+ end
16
+ end
17
+
18
+ def test_define_statemachine
19
+ @klass.instance_eval do
20
+ define_statemachine do
21
+ initial_state :test
22
+ end
23
+ end
24
+
25
+ assert_kind_of Golem::Model::StateMachine, @klass.statemachines[:statemachine]
26
+ end
27
+
28
+ def test_define_invalid_statemachine
29
+ assert_raise Golem::DefinitionSyntaxError do
30
+ @klass.instance_eval do
31
+ define_statemachine do
32
+ # no inital_state
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def test_define_multiple_statemachines
39
+ @klass.instance_eval do
40
+ define_statemachine(:alpha) do
41
+ initial_state :test
42
+ end
43
+
44
+ define_statemachine(:beta) do
45
+ initial_state :test
46
+ end
47
+ end
48
+
49
+ assert_kind_of Golem::Model::StateMachine, @klass.statemachines[:alpha]
50
+ assert_kind_of Golem::Model::StateMachine, @klass.statemachines[:beta]
51
+ assert_not_same @klass.statemachines[:alpha], @klass.statemachines[:beta]
52
+
53
+ # check that defining a default statemachine after defining named statemachines is okay
54
+ @klass.instance_eval do
55
+ define_statemachine do
56
+ initial_state :test
57
+ end
58
+ end
59
+
60
+ assert_kind_of Golem::Model::StateMachine, @klass.statemachines[:statemachine]
61
+ assert_not_same @klass.statemachines[:alpha], @klass.statemachines[:statemachine]
62
+ assert_not_same @klass.statemachines[:beta], @klass.statemachines[:statemachine]
63
+ end
64
+
65
+ def test_define_duplicate_statemachine
66
+ @klass.instance_eval do
67
+ define_statemachine(:alpha) do
68
+ initial_state :test
69
+ end
70
+ end
71
+
72
+ assert_raise ArgumentError do
73
+ @klass.instance_eval do
74
+ define_statemachine(:alpha) do
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def test_define_multiple_invalid_statemachines
81
+ assert_raise Golem::DefinitionSyntaxError do
82
+ @klass.instance_eval do
83
+ define_statemachine(:alpha) do
84
+ initial_state :one
85
+ end
86
+
87
+ define_statemachine(:beta) do
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def test_define_state_attribute
94
+ @klass.instance_eval do
95
+ define_statemachine do
96
+ initial_state :one
97
+ end
98
+
99
+ define_statemachine(:alpha) do
100
+ initial_state :two
101
+ end
102
+
103
+ define_statemachine(:beta) do
104
+ state_attribute :foo
105
+ initial_state :three
106
+ end
107
+ end
108
+
109
+ obj = @klass.new
110
+ assert_equal :one, obj.state
111
+ assert_equal :two, obj.alpha_state
112
+ assert_equal :three, obj.foo
113
+ end
114
+
115
+
116
+ def test_define_states
117
+ @klass.instance_eval do
118
+ define_statemachine do
119
+ initial_state :one
120
+ state :one
121
+ state :two
122
+ end
123
+
124
+ define_statemachine(:alpha) do
125
+ initial_state :one
126
+ state :one
127
+ state :two
128
+ end
129
+
130
+ define_statemachine(:beta) do
131
+ initial_state :three
132
+ state :one
133
+ state :two
134
+ state :three
135
+ end
136
+ end
137
+
138
+ assert_equal :one, @klass.statemachines[:statemachine].states[:one].name
139
+ assert_equal 2, ([:one, :two] & @klass.statemachines[:statemachine].states.collect{|s| s.name}).size
140
+ assert_equal 2, ([:one, :two] & @klass.statemachines[:alpha].states.collect{|s| s.name}).size
141
+ assert_equal 3, ([:one, :two, :three] & @klass.statemachines[:beta].states.collect{|s| s.name}).size
142
+ assert_not_same @klass.statemachines[:statemachine].states[:one], @klass.statemachines[:statemachine].states[:two]
143
+ assert_not_same @klass.statemachines[:alpha].states[:one], @klass.statemachines[:beta].states[:one]
144
+ end
145
+
146
+ def test_define_events
147
+ @klass.instance_eval do
148
+ define_statemachine(:alpha) do
149
+ initial_state :one
150
+ state :one do
151
+ on :go, :to => :two
152
+ end
153
+ state :two do
154
+ on :go do
155
+ transition :to => :three
156
+ end
157
+ end
158
+ state :three, :to => :one do
159
+ on :go, :to => :one do
160
+ transition
161
+ end
162
+ end
163
+ end
164
+
165
+ define_statemachine(:beta) do
166
+ initial_state :b
167
+ state :a do
168
+ on :doit, :to => :b
169
+ on :foo, :action => Proc.new{|arg| puts arg}
170
+ end
171
+ state :b do
172
+ on :doit do
173
+ transition :to => :a
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ assert_equal [:go], @klass.statemachines[:alpha].events.collect{|e| e.name}
180
+ ev = @klass.statemachines[:alpha].events[:go]
181
+ assert_equal :go, ev.name
182
+
183
+ assert_equal_arrays [:doit, :foo], @klass.statemachines[:beta].events.collect{|e| e.name}
184
+ end
185
+
186
+
187
+ def test_define_transitions_in_event_to
188
+ sm = sm_def_helper do
189
+ state :a do
190
+ on :go, :to => :b
191
+ end
192
+ state :b do
193
+ on :go, :to => :a do
194
+ transition # inherits on :to
195
+ transition :to => :b # overrides on :to
196
+ end
197
+ end
198
+ end
199
+
200
+ assert_transition_on_event sm, :go, :a, :b
201
+ assert_transition_on_event sm, :go, :b, :a
202
+ assert_transition_on_event sm, :go, :b, :b
203
+ assert_no_transition_on_event sm, :go, :a, :a
204
+ end
205
+
206
+
207
+ def test_define_self_transitions
208
+ sm = sm_def_helper do
209
+ state :a do
210
+ on :go
211
+ end
212
+ state :b do
213
+ on :go do
214
+ transition :to => :self
215
+ end
216
+ end
217
+ end
218
+
219
+ assert_transition_on_event sm, :go, :a, :a
220
+ assert_transition_on_event sm, :go, :b, :b
221
+ assert_no_transition_on_event sm, :go, :a, :b
222
+ assert_no_transition_on_event sm, :go, :b, :a
223
+ end
224
+
225
+
226
+ def test_define_state_actions
227
+ enter_b_proc = Proc.new{puts "hi!"}
228
+
229
+ sm = sm_def_helper do
230
+ state :a do
231
+ enter :hi
232
+ exit :bye
233
+ end
234
+ state :b do
235
+ enter enter_b_proc
236
+ exit do
237
+ puts "bye!"
238
+ end
239
+ end
240
+ end
241
+
242
+ assert_equal :hi, sm.states[:a].callbacks[:on_enter].callback
243
+ assert_equal :bye, sm.states[:a].callbacks[:on_exit].callback
244
+ assert_equal enter_b_proc, sm.states[:b].callbacks[:on_enter].callback
245
+ assert_equal Proc, sm.states[:b].callbacks[:on_exit].callback.class
246
+ end
247
+
248
+
249
+ def test_define_transition_guards
250
+ sm = sm_def_helper do
251
+ state :a do
252
+ on :go_1 do
253
+ transition :to => :self do
254
+ guard :ready?
255
+ guard :steady?
256
+ end
257
+ end
258
+ on :go_2 do
259
+ transition :to => :self, :if => :ready?
260
+ end
261
+ on :go_3 do
262
+ transition :to => :self, :if => :ready? do
263
+ guard :steady?
264
+ end
265
+ end
266
+ on :go_4, :if => :ready?
267
+ on :go_5, :if => :ready? do
268
+ transition :to => :b, :if => :steady? do
269
+ guard :maybe?
270
+ end
271
+ transition :to => :c, :if => :maybe?
272
+ transition :to => :self
273
+ end
274
+ end
275
+ end
276
+
277
+ assert_transition_on_event_has_guard sm, :go_1, :a, :a, :ready?
278
+ assert_transition_on_event_has_guard sm, :go_1, :a, :a, :steady?
279
+
280
+ assert_transition_on_event_has_guard sm, :go_2, :a, :a, :ready?
281
+
282
+ assert_transition_on_event_has_guard sm, :go_3, :a, :a, :ready?
283
+ assert_transition_on_event_has_guard sm, :go_3, :a, :a, :steady?
284
+
285
+ assert_transition_on_event_has_guard sm, :go_4, :a, :a, :ready?
286
+
287
+ assert_transition_on_event_has_guard sm, :go_5, :a, :b, :ready?
288
+ assert_transition_on_event_has_guard sm, :go_5, :a, :b, :steady?
289
+ assert_transition_on_event_has_guard sm, :go_5, :a, :b, :maybe?
290
+
291
+ assert_transition_on_event_has_guard sm, :go_5, :a, :c, :ready?
292
+ assert_transition_on_event_has_guard sm, :go_5, :a, :c, :maybe?
293
+ assert_transition_on_event_does_not_have_guard sm, :go_5, :a, :c, :steady?
294
+
295
+ assert_transition_on_event_has_guard sm, :go_5, :a, :a, :ready?
296
+ assert_transition_on_event_does_not_have_guard sm, :go_5, :a, :a, :maybe?
297
+ assert_transition_on_event_does_not_have_guard sm, :go_5, :a, :a, :steady?
298
+ end
299
+
300
+ def test_define_transition_guards_failure_messages
301
+ sm = sm_def_helper do
302
+ state :a do
303
+ on :move, :if => :movable?, :guard_options => {:failure_message => "it's not movable"} do
304
+ transition :to => :b do
305
+ guard :not_stuck?, :failure_message => "it's stuck"
306
+ guard :not_busy?, :failure_message => "it's busy"
307
+ end
308
+ transition :to => :c do
309
+ guard :not_tired?, :failure_message => "it's tired"
310
+ end
311
+ transition :to => :self, :if => :not_busy?
312
+ end
313
+ on :spin do
314
+ transition :to => :self do
315
+ guard :not_tired?, :failure_message => "it's tired"
316
+ guard :not_busy?, :failure_message => "it's busy"
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ @klass.instance_eval do
323
+ attr_accessor :tired
324
+ attr_accessor :stuck
325
+ attr_accessor :movable
326
+ attr_accessor :busy
327
+
328
+ define_method(:movable?) do
329
+ @movable
330
+ end
331
+
332
+ define_method(:not_busy?) do
333
+ !@busy
334
+ end
335
+
336
+ define_method(:not_tired?) do
337
+ !@tired
338
+ end
339
+
340
+ define_method(:not_stuck?) do
341
+ !@stuck
342
+ end
343
+ end
344
+
345
+ assert_transition_on_event_has_guard sm, :move, :a, :b, :movable?
346
+ assert_transition_on_event_has_guard sm, :move, :a, :b, :not_stuck?
347
+ assert_transition_on_event_has_guard sm, :move, :a, :b, :not_busy?
348
+ assert_transition_on_event_does_not_have_guard sm, :move, :a, :b, :not_tired?
349
+
350
+ assert_transition_on_event_has_guard sm, :move, :a, :c, :movable?
351
+ assert_transition_on_event_does_not_have_guard sm, :move, :a, :c, :not_stuck?
352
+ assert_transition_on_event_does_not_have_guard sm, :move, :a, :c, :not_busy?
353
+ assert_transition_on_event_has_guard sm, :move, :a, :c, :not_tired?
354
+
355
+ assert_transition_on_event_has_guard sm, :move, :a, :a, :movable?
356
+ assert_transition_on_event_does_not_have_guard sm, :move, :a, :a, :not_stuck?
357
+ assert_transition_on_event_does_not_have_guard sm, :move, :a, :a, :not_tired?
358
+ assert_transition_on_event_has_guard sm, :move, :a, :a, :not_busy?
359
+
360
+ assert_transition_on_event_does_not_have_guard sm, :spin, :a, :a, :movable?
361
+ assert_transition_on_event_has_guard sm, :spin, :a, :a, :not_busy?
362
+ assert_transition_on_event_has_guard sm, :spin, :a, :a, :not_tired?
363
+ assert_transition_on_event_does_not_have_guard sm, :spin, :a, :a, :not_stuck?
364
+
365
+ obj = @klass.new
366
+ obj.movable = false
367
+
368
+ raised = false
369
+ begin
370
+ obj.move!
371
+ rescue Golem::ImpossibleEvent => e
372
+ assert_match(/because it's not movable/, e.message)
373
+ raised = true
374
+ ensure
375
+ # be careful -- we end up here if assert_match fails!
376
+ assert raised
377
+ end
378
+
379
+ obj.stuck = true
380
+
381
+ raised = false
382
+ begin
383
+ obj.move!
384
+ rescue Golem::ImpossibleEvent => e
385
+ assert_match(/because it's not movable/, e.message)
386
+ raised = true
387
+ ensure
388
+ # be careful -- we end up here if assert_match fails!
389
+ assert raised
390
+ end
391
+
392
+ obj.tired = true
393
+ obj.movable = true
394
+
395
+ assert_nothing_raised do
396
+ obj.move! # follows only available transition --> to self
397
+ end
398
+ assert_equal :a, obj.state
399
+
400
+
401
+ obj.busy = true
402
+ obj.tired = false
403
+
404
+ # make sure second the guard (is_busy?) works
405
+ raised = false
406
+ begin
407
+ obj.spin!
408
+ rescue Golem::ImpossibleEvent => e
409
+ assert_match(/because it's busy/, e.message)
410
+ raised = true
411
+ ensure
412
+ # be careful -- we end up here if assert_match fails!
413
+ assert raised
414
+ end
415
+ end
416
+
417
+ private
418
+
419
+ def sm_def_helper(&block)
420
+ @klass.instance_eval do
421
+ define_statemachine do
422
+ initial_state :a
423
+ instance_eval(&block) if block_given?
424
+ end
425
+ end
426
+ return @klass.statemachines[:statemachine]
427
+ end
428
+ end
429
+
@@ -0,0 +1,110 @@
1
+ require 'test_helper'
2
+ require File.dirname(__FILE__)+'/../examples/monster'
3
+
4
+ # Because the Monster class has two statemachines ('affect' and 'mouth')
5
+ # this test allows for validating statemachine behaviour when there are
6
+ # multiple statemachines defined within the same class.
7
+ class MonsterTest < Test::Unit::TestCase
8
+
9
+ def setup
10
+ @monster = Monster.new
11
+ end
12
+
13
+ def test_initial_state
14
+ assert_equal :sleeping, @monster.affect.initial_state
15
+ assert_equal :closed, @monster.mouth.initial_state
16
+ end
17
+
18
+ def test_non_existent_event
19
+ assert_raise(NoMethodError){@monster.dance!}
20
+ end
21
+
22
+ def test_machine_state_accessors
23
+ assert_equal :sleeping, @monster.affect_state
24
+
25
+ assert_equal :closed, @monster.mouth_state
26
+ end
27
+
28
+ def test_event_possibility_within_states
29
+ assert_equal :sleeping, @monster.affect_state
30
+ assert_equal :closed, @monster.mouth_state
31
+
32
+ assert_raise(Golem::ImpossibleEvent){@monster.feed!}
33
+ assert_raise(Golem::ImpossibleEvent){@monster.lullaby!}
34
+ assert_nothing_raised{@monster.wake_up!}
35
+ assert_equal :hungry, @monster.affect_state
36
+
37
+ assert_raise(Golem::ImpossibleEvent){@monster.wake_up!}
38
+ assert_nothing_raised{@monster.lullaby!}
39
+ assert_equal :hungry, @monster.affect_state
40
+
41
+ assert_raise(Golem::ImpossibleEvent){@monster.feed!(:hamburger)}
42
+ assert_nothing_raised{@monster.tickle!}
43
+ assert_nothing_raised{@monster.feed!(:hamburger)}
44
+ assert_equal :satiated, @monster.affect_state
45
+
46
+ assert_raise(Golem::ImpossibleEvent){@monster.feed!}
47
+ assert_raise(Golem::ImpossibleEvent){@monster.wake_up!}
48
+ end
49
+
50
+ def test_transition_decisions
51
+ @monster.wake_up!
52
+
53
+ assert_equal :hungry, @monster.affect_state
54
+
55
+ @monster.tickle!
56
+
57
+ @monster.feed!(:toast) # monster doesn't care for toast
58
+
59
+ assert_equal :hungry, @monster.affect_state
60
+ assert @monster.deeds.include?(:ate_toast)
61
+ assert !@monster.deeds.include?(:barfed)
62
+
63
+ @monster.feed!(:tofu) # monster hates tofu
64
+
65
+ assert @monster.deeds.include?(:barfed)
66
+ assert_equal :hungry, @monster.affect_state
67
+
68
+ @monster.feed!(:hamburger) # monster likes hamburger
69
+
70
+ assert_equal :satiated, @monster.affect_state
71
+ assert @monster.deeds.include?(:ate_tasty_hamburger)
72
+ end
73
+
74
+ def test_state_actions
75
+ @monster.wake_up!
76
+
77
+ assert_raise(Golem::ImpossibleEvent){@monster.wake_up!}
78
+ assert_equal [:stretched, :grumbled], @monster.deeds
79
+ # The transition didn't take place, so :hungry's enter action should not have been executed
80
+ # (i.e. we should only see one :grumbled).
81
+
82
+ @monster.tickle!
83
+ @monster.feed!(:toast)
84
+ @monster.lullaby!
85
+ @monster.tickle!
86
+ @monster.feed!(:tofu)
87
+
88
+ assert_equal [:stretched, :grumbled, :giggled, :grumbled, :ate_toast, :grumbled, :yawned, :grumbled, :giggled,
89
+ :grumbled, :barfed, :grumbled], @monster.deeds
90
+ # Note that `grumble` is executed even when we transition from :hungry back to :hungry.
91
+ # Enter and exit actions are to be executed even for self-transitions, however they should NOT be executed when
92
+ # transition fails to occur (i.e. when an ImpossibleEvent is raised).
93
+ end
94
+
95
+ def test_transition_actions
96
+ @monster.wake_up!
97
+ @monster.tickle!
98
+ @monster.feed!(:toast)
99
+ @monster.lullaby!
100
+ @monster.tickle!
101
+ @monster.feed!(:tofu)
102
+ @monster.feed!(:hamburger)
103
+ @monster.lullaby!
104
+ @monster.lullaby!
105
+
106
+ assert_equal [:stretched, :grumbled, :giggled, :grumbled, :ate_toast, :grumbled, :yawned, :grumbled, :giggled,
107
+ :grumbled, :barfed, :grumbled, :ate_tasty_hamburger, :yawned], @monster.deeds
108
+ end
109
+ end
110
+