MINT-statemachine 1.2.2

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.
Files changed (40) hide show
  1. data/CHANGES +135 -0
  2. data/LICENSE +16 -0
  3. data/MINT-statemachine.gemspec +27 -0
  4. data/README.rdoc +69 -0
  5. data/Rakefile +88 -0
  6. data/TODO +2 -0
  7. data/lib/statemachine.rb +26 -0
  8. data/lib/statemachine/action_invokation.rb +83 -0
  9. data/lib/statemachine/builder.rb +383 -0
  10. data/lib/statemachine/generate/dot_graph.rb +1 -0
  11. data/lib/statemachine/generate/dot_graph/dot_graph_statemachine.rb +127 -0
  12. data/lib/statemachine/generate/java.rb +1 -0
  13. data/lib/statemachine/generate/java/java_statemachine.rb +265 -0
  14. data/lib/statemachine/generate/src_builder.rb +48 -0
  15. data/lib/statemachine/generate/util.rb +50 -0
  16. data/lib/statemachine/parallelstate.rb +196 -0
  17. data/lib/statemachine/state.rb +102 -0
  18. data/lib/statemachine/statemachine.rb +279 -0
  19. data/lib/statemachine/stub_context.rb +26 -0
  20. data/lib/statemachine/superstate.rb +53 -0
  21. data/lib/statemachine/transition.rb +76 -0
  22. data/lib/statemachine/version.rb +17 -0
  23. data/spec/action_invokation_spec.rb +101 -0
  24. data/spec/builder_spec.rb +243 -0
  25. data/spec/default_transition_spec.rb +111 -0
  26. data/spec/generate/dot_graph/dot_graph_stagemachine_spec.rb +27 -0
  27. data/spec/generate/java/java_statemachine_spec.rb +349 -0
  28. data/spec/history_spec.rb +107 -0
  29. data/spec/noodle.rb +23 -0
  30. data/spec/sm_action_parameterization_spec.rb +99 -0
  31. data/spec/sm_activation_spec.rb +116 -0
  32. data/spec/sm_entry_exit_actions_spec.rb +99 -0
  33. data/spec/sm_odds_n_ends_spec.rb +67 -0
  34. data/spec/sm_parallel_state_spec.rb +207 -0
  35. data/spec/sm_simple_spec.rb +26 -0
  36. data/spec/sm_super_state_spec.rb +55 -0
  37. data/spec/sm_turnstile_spec.rb +76 -0
  38. data/spec/spec_helper.rb +121 -0
  39. data/spec/transition_spec.rb +107 -0
  40. metadata +115 -0
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require 'statemachine/generate/dot_graph/dot_graph_statemachine'
3
+
4
+ describe Statemachine::Statemachine, "(Turn Stile)" do
5
+ include TurnstileStatemachine
6
+
7
+ before(:each) do
8
+ remove_test_dir("dot")
9
+ @output = test_dir("dot")
10
+ create_turnstile
11
+ end
12
+
13
+ # it "should output to console when no output dir provided" do
14
+ # # Note - this test doesn't verify output to the console, but it does
15
+ # # ensure that the to_dot call does not fail if not output is provided.
16
+ # @sm.to_dot
17
+ # end
18
+
19
+ it "should generate a basic graph declaration" do
20
+ @sm.to_dot(:output => @output)
21
+
22
+ dot = load_lines(@output, "main.dot")
23
+
24
+ dot.should_not equal(nil)
25
+ dot[0].include?("digraph").should == true
26
+ end
27
+ end
@@ -0,0 +1,349 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
+ require 'statemachine/generate/java/java_statemachine'
3
+
4
+ describe Statemachine::Statemachine, "(Java)" do
5
+
6
+ before(:each) do
7
+ remove_test_dir("java")
8
+ @output = test_dir("java")
9
+ @sm = Statemachine.build {}
10
+ end
11
+
12
+ def generate_turnstile_sm
13
+ @sm = Statemachine.build do
14
+ trans :locked, :coin, :unlocked, :unlock
15
+ trans :unlocked, :pass, :locked, :lock
16
+ trans :locked, :pass, :locked, :alarm
17
+ trans :unlocked, :coin, :locked, :thanks
18
+ end
19
+
20
+ @sm.to_java(:output => @output, :name => "JavaTurnstile", :package => "test.turnstile")
21
+ end
22
+
23
+ def generate_complex_turnstile_sm
24
+ @sm = Statemachine.build do
25
+ superstate :operational do
26
+ on_entry :operate
27
+ on_exit :beep
28
+ state :locked do
29
+ on_entry :lock
30
+ event :coin, :unlocked
31
+ event :pass, :locked, :alarm
32
+ end
33
+ state :unlocked do
34
+ on_entry :unlock
35
+ event :coin, :unlocked, :thanks
36
+ event :pass, :locked
37
+ end
38
+ event :diagnose, :diagnostics
39
+ end
40
+ state :diagnostics do
41
+ on_entry :disable
42
+ on_exit :beep
43
+ event :operate, :operational
44
+ end
45
+ stub_context :verbose => true
46
+ end
47
+
48
+ @sm.to_java(:output => @output, :name => "JavaTurnstile", :package => "test.turnstile")
49
+ end
50
+
51
+ # def load_lines(*segs)
52
+ # filename = File.join(*segs)
53
+ # File.should exist( filename)
54
+ # return IO.read(filename).split("\n")
55
+ # end
56
+
57
+ def empty_sm_lines
58
+ @sm.to_java(:name => "JavaTest", :output => @output, :package => "test.blank")
59
+ return load_lines(@output, "test", "blank", "JavaTest.java")
60
+ end
61
+
62
+ def turnstile_lines
63
+ generate_turnstile_sm
64
+ return load_lines(@output, "test", "turnstile", "JavaTurnstile.java")
65
+ end
66
+
67
+ def turnstile_context_lines
68
+ generate_turnstile_sm
69
+ return load_lines(@output, "test", "turnstile", "JavaTurnstileContext.java")
70
+ end
71
+
72
+ def complex_turnstile_lines
73
+ generate_complex_turnstile_sm
74
+ return load_lines(@output, "test", "turnstile", "JavaTurnstile.java")
75
+ end
76
+
77
+ def find_lines_after(lines, goose)
78
+ while !lines.empty?
79
+ return lines if lines.shift.strip == goose
80
+ end
81
+ raise "Can't find desired line: #{goose}"
82
+ end
83
+
84
+ it "should error if no output dir is specified" do
85
+ lambda { @sm.to_java }.should raise_error("Please specify an output directory. (:output => 'where/you/want/your/code')")
86
+ end
87
+
88
+ it "should error if output dir doesn't exist" do
89
+ lambda { @sm.to_java(:output => "/blah") }.should raise_error("Output dir '/blah' doesn't exist.")
90
+ end
91
+
92
+ it "should error if no class name is specified" do
93
+ lambda { @sm.to_java(:output => @output) }.should raise_error("Please specify a name for the statemachine. (:name => 'SomeName')")
94
+ end
95
+
96
+ it "should generate a class" do
97
+ @sm.to_java(:name => "JavaTest", :output => @output)
98
+ File.should exist(File.join(@output, "JavaTest.java"))
99
+ end
100
+
101
+ it "should generate a class in a package" do
102
+ lines = empty_sm_lines
103
+
104
+ lines.shift.should == "// This file was generated by the Ruby Statemachine Library (http://slagyr.github.com/statemachine)."
105
+ lines.shift.include?("// Generated at ").should == true
106
+ lines.shift.should == "package test.blank;"
107
+ lines.shift.should == ""
108
+ lines.shift.should == "public class JavaTest"
109
+ lines.shift.should == "{"
110
+ lines.last.should == "}"
111
+ end
112
+
113
+ it "should generate the constructor" do
114
+ lines = find_lines_after(empty_sm_lines, "// Statemachine constructor")
115
+
116
+ lines.shift.should == " public JavaTest(JavaTestContext context)"
117
+ lines.shift.should == " {"
118
+ lines.shift.should == " this.context = context;"
119
+ lines.shift.should == " }"
120
+ lines.shift.should == ""
121
+ end
122
+
123
+ it "should generate sm boilerplate code" do
124
+ lines = find_lines_after(empty_sm_lines, "// The following is boiler plate code standard to all statemachines")
125
+
126
+ lines.shift.should == " public JavaTestContext getContext()"
127
+ lines.shift.should == " {"
128
+ lines.shift.should == " return context;"
129
+ lines.shift.should == " }"
130
+ lines.shift.should == ""
131
+ lines.shift.should == " public State getState()"
132
+ lines.shift.should == " {"
133
+ lines.shift.should == " return state;"
134
+ lines.shift.should == " }"
135
+ lines.shift.should == ""
136
+ lines.shift.should == " public void setState(State newState)"
137
+ lines.shift.should == " {"
138
+ lines.shift.should == " state = newState;"
139
+ lines.shift.should == " }"
140
+ end
141
+
142
+ it "should generate the sm exception" do
143
+ lines = empty_sm_lines
144
+ lines = find_lines_after(lines, "// Standard exception class added to all statemachines.")
145
+
146
+ lines.shift.should == " public static class StatemachineException extends RuntimeException"
147
+ lines.shift.should == " {"
148
+ lines.shift.should == " public StatemachineException(State state, String event)"
149
+ lines.shift.should == " {"
150
+ lines.shift.should == " super(\"Missing transition from '\" + state.getClass().getSimpleName() + \"' with the '\" + event + \"' event.\");"
151
+ lines.shift.should == " }"
152
+ lines.shift.should == " }"
153
+ end
154
+
155
+ it "should generate base state" do
156
+ lines = empty_sm_lines
157
+ lines = find_lines_after(lines, "// The base state")
158
+
159
+ lines.shift.should == " public static abstract class State"
160
+ lines.shift.should == " {"
161
+ lines.shift.should == " protected JavaTest statemachine;"
162
+ lines.shift.should == ""
163
+ lines.shift.should == " public State(JavaTest statemachine)"
164
+ lines.shift.should == " {"
165
+ lines.shift.should == " this.statemachine = statemachine;"
166
+ lines.shift.should == " }"
167
+ lines.shift.should == ""
168
+ lines.shift.should == " }"
169
+ end
170
+
171
+ it "should generate the context" do
172
+ @sm.to_java(:name => "JavaTest", :output => @output, :package => "com.blah")
173
+
174
+ filename = File.join(@output, "com", "blah", "JavaTestContext.java")
175
+ File.should exist(filename)
176
+ lines = IO.read(filename).split("\n")
177
+
178
+ lines.shift.should == "// This file was generated by the Ruby Statemachine Library (http://slagyr.github.com/statemachine)."
179
+ lines.shift.include?("// Generated at ").should == true
180
+ lines.shift.should == "package com.blah;"
181
+ lines.shift.should == ""
182
+ lines.shift.should == "public interface JavaTestContext"
183
+ lines.shift.should == "{"
184
+ lines.shift.should == " // Actions"
185
+ lines.shift.should == "}"
186
+ lines.should be_empty
187
+ end
188
+
189
+ it "should generate the state instance variables" do
190
+ lines = find_lines_after(turnstile_lines, "// Instance variables")
191
+
192
+ lines.shift.should == " public final State LOCKED = new LockedState(this);"
193
+ lines.shift.should == " public final State UNLOCKED = new UnlockedState(this);"
194
+ lines.shift.should == " private State state = LOCKED;"
195
+ lines.shift.should == ""
196
+ lines.shift.should == " private JavaTurnstileContext context;"
197
+ end
198
+
199
+ it "should generate all the sm event handlers" do
200
+ lines = find_lines_after(turnstile_lines, "// Event delegation")
201
+
202
+ lines.shift.should == " public void coin()"
203
+ lines.shift.should == " {"
204
+ lines.shift.should == " state.coin();"
205
+ lines.shift.should == " }"
206
+ lines.shift.should == ""
207
+ lines.shift.should == " public void pass()"
208
+ lines.shift.should == " {"
209
+ lines.shift.should == " state.pass();"
210
+ lines.shift.should == " }"
211
+ lines.shift.should == ""
212
+ end
213
+
214
+ it "should generate all the default event handlers" do
215
+ lines = find_lines_after(turnstile_lines, "// The base state")
216
+
217
+ lines.shift.should == " public static abstract class State"
218
+ lines.shift.should == " {"
219
+ lines.shift.should == " protected JavaTurnstile statemachine;"
220
+ lines.shift.should == ""
221
+ lines.shift.should == " public State(JavaTurnstile statemachine)"
222
+ lines.shift.should == " {"
223
+ lines.shift.should == " this.statemachine = statemachine;"
224
+ lines.shift.should == " }"
225
+ lines.shift.should == ""
226
+ lines.shift.should == " public void coin()"
227
+ lines.shift.should == " {"
228
+ lines.shift.should == " throw new StatemachineException(this, \"coin\");"
229
+ lines.shift.should == " }"
230
+ lines.shift.should == ""
231
+ lines.shift.should == " public void pass()"
232
+ lines.shift.should == " {"
233
+ lines.shift.should == " throw new StatemachineException(this, \"pass\");"
234
+ lines.shift.should == " }"
235
+ lines.shift.should == ""
236
+ lines.shift.should == " }"
237
+ end
238
+
239
+ it "should generate state classes" do
240
+ lines = find_lines_after(turnstile_lines, "// State implementations")
241
+
242
+ lines.shift.should == " public static class LockedState extends State"
243
+ lines.shift.should == " {"
244
+ lines.shift.should == " public LockedState(JavaTurnstile statemachine)"
245
+ lines.shift.should == " {"
246
+ lines.shift.should == " super(statemachine);"
247
+ lines.shift.should == " }"
248
+ lines.shift.should == ""
249
+ lines.shift.should == " public void coin()"
250
+ lines.shift.should == " {"
251
+ lines.shift.should == " statemachine.getContext().unlock();"
252
+ lines.shift.should == " statemachine.setState(statemachine.UNLOCKED);"
253
+ lines.shift.should == " }"
254
+ lines.shift.should == ""
255
+ lines.shift.should == " public void pass()"
256
+ lines.shift.should == " {"
257
+ lines.shift.should == " statemachine.getContext().alarm();"
258
+ lines.shift.should == " statemachine.setState(statemachine.LOCKED);"
259
+ lines.shift.should == " }"
260
+ lines.shift.should == ""
261
+ lines.shift.should == " }"
262
+ lines.shift.should == ""
263
+ lines.shift.should == " public static class UnlockedState extends State"
264
+ lines.shift.should == " {"
265
+ lines.shift.should == " public UnlockedState(JavaTurnstile statemachine)"
266
+ lines.shift.should == " {"
267
+ lines.shift.should == " super(statemachine);"
268
+ lines.shift.should == " }"
269
+ lines.shift.should == ""
270
+ lines.shift.should == " public void coin()"
271
+ lines.shift.should == " {"
272
+ lines.shift.should == " statemachine.getContext().thanks();"
273
+ lines.shift.should == " statemachine.setState(statemachine.LOCKED);"
274
+ lines.shift.should == " }"
275
+ lines.shift.should == ""
276
+ lines.shift.should == " public void pass()"
277
+ lines.shift.should == " {"
278
+ lines.shift.should == " statemachine.getContext().lock();"
279
+ lines.shift.should == " statemachine.setState(statemachine.LOCKED);"
280
+ lines.shift.should == " }"
281
+ lines.shift.should == ""
282
+ lines.shift.should == " }"
283
+ lines.shift.should == ""
284
+ end
285
+
286
+ it "should generate all the context methods" do
287
+ lines = find_lines_after(turnstile_context_lines, "// Actions")
288
+
289
+ lines.shift.should == " void alarm();"
290
+ lines.shift.should == " void lock();"
291
+ lines.shift.should == " void thanks();"
292
+ lines.shift.should == " void unlock();"
293
+ end
294
+
295
+ it "should generate complext state instances" do
296
+ lines = find_lines_after(complex_turnstile_lines, "// Instance variables")
297
+
298
+ lines.shift.should == " public final State DIAGNOSTICS = new DiagnosticsState(this);"
299
+ lines.shift.should == " public final State LOCKED = new LockedState(this);"
300
+ lines.shift.should == " public final State UNLOCKED = new UnlockedState(this);"
301
+ lines.shift.should == " public final State OPERATIONAL = LOCKED;"
302
+ lines.shift.should == " private State state = LOCKED;"
303
+ end
304
+
305
+ it "should generate entry actions on startup" do
306
+ lines = find_lines_after(complex_turnstile_lines, "// Statemachine constructor")
307
+
308
+ lines.shift.should == " public JavaTurnstile(JavaTurnstileContext context)"
309
+ lines.shift.should == " {"
310
+ lines.shift.should == " this.context = context;"
311
+ lines.shift.should == " context.operate();"
312
+ lines.shift.should == " context.lock();"
313
+ lines.shift.should == " }"
314
+ lines.shift.should == ""
315
+ end
316
+
317
+ it "should add entry/exit actions to each transition" do
318
+ lines = find_lines_after(complex_turnstile_lines, "public static class LockedState extends State")
319
+
320
+ lines.shift.should == " {"
321
+ lines.shift.should == " public LockedState(JavaTurnstile statemachine)"
322
+ lines.shift.should == " {"
323
+ lines.shift.should == " super(statemachine);"
324
+ lines.shift.should == " }"
325
+ lines.shift.should == ""
326
+ lines.shift.should == " public void coin()"
327
+ lines.shift.should == " {"
328
+ lines.shift.should == " statemachine.setState(statemachine.UNLOCKED);"
329
+ lines.shift.should == " statemachine.getContext().unlock();"
330
+ lines.shift.should == " }"
331
+ lines.shift.should == ""
332
+ lines.shift.should == " public void diagnose()"
333
+ lines.shift.should == " {"
334
+ lines.shift.should == " statemachine.getContext().beep();"
335
+ lines.shift.should == " statemachine.setState(statemachine.DIAGNOSTICS);"
336
+ lines.shift.should == " statemachine.getContext().disable();"
337
+ lines.shift.should == " }"
338
+ lines.shift.should == ""
339
+ lines.shift.should == " public void pass()"
340
+ lines.shift.should == " {"
341
+ lines.shift.should == " statemachine.getContext().alarm();"
342
+ lines.shift.should == " statemachine.setState(statemachine.LOCKED);"
343
+ lines.shift.should == " statemachine.getContext().lock();" # this has been added since my modifications don't know why'
344
+ lines.shift.should == " }"
345
+ lines.shift.should == ""
346
+ lines.shift.should == " }"
347
+ end
348
+
349
+ end
@@ -0,0 +1,107 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "History States" do
4
+
5
+ before(:each) do
6
+ @sm = Statemachine.build do
7
+ superstate :operate do
8
+ trans :on, :toggle, :off
9
+ trans :off, :toggle, :on
10
+ event :fiddle, :middle
11
+ end
12
+ trans :middle, :fiddle, :operate_H
13
+ trans :middle, :dream, :on_H
14
+ trans :middle, :faddle, :on
15
+ startstate :middle
16
+ end
17
+ end
18
+
19
+ it "no history allowed for concrete states" do
20
+ lambda {
21
+ @sm.dream
22
+ }.should raise_error(Statemachine::StatemachineException, "No history exists for 'on' state since it is not a super state.")
23
+ end
24
+
25
+ it "error when trying to use history that doesn't exist yet" do
26
+ lambda {
27
+ @sm.fiddle
28
+ }.should raise_error(Statemachine::StatemachineException, "'operate' superstate doesn't have any history yet.")
29
+ end
30
+
31
+ it "reseting the statemachine resets history" do
32
+ @sm.faddle
33
+ @sm.fiddle
34
+ @sm.get_state(:operate).history_id.should eql(:on)
35
+
36
+ @sm.reset
37
+ @sm.get_state(:operate).history_id.should eql(nil)
38
+ end
39
+
40
+ end
41
+
42
+ describe "History Default" do
43
+
44
+ before(:each) do
45
+ @sm = Statemachine.build do
46
+ superstate :operate do
47
+ trans :on, :toggle, :off
48
+ trans :off, :toggle, :on
49
+ event :fiddle, :middle
50
+ default_history :on
51
+ end
52
+ trans :middle, :fiddle, :operate_H
53
+ startstate :middle
54
+ trans :middle, :faddle, :on
55
+ end
56
+ end
57
+
58
+ it "default history" do
59
+ @sm.fiddle
60
+ @sm.state.should eql(:on)
61
+ end
62
+
63
+ it "reseting the statemachine resets history" do
64
+ @sm.faddle
65
+ @sm.toggle
66
+ @sm.fiddle
67
+ @sm.get_state(:operate).history_id.should eql(:off)
68
+
69
+ @sm.reset
70
+ @sm.get_state(:operate).history_id.should eql(:on)
71
+ end
72
+
73
+ end
74
+
75
+ describe "Nested Superstates" do
76
+
77
+ before(:each) do
78
+ @sm = Statemachine.build do
79
+
80
+ superstate :grandpa do
81
+ trans :start, :go, :daughter
82
+ event :sister, :great_auntie
83
+
84
+ superstate :papa do
85
+ state :son
86
+ state :daughter
87
+ end
88
+ end
89
+
90
+ state :great_auntie do
91
+ event :foo, :grandpa_H
92
+ end
93
+
94
+ end
95
+ end
96
+
97
+ it "should use history of sub superstates when transitioning itto it's own history" do
98
+ @sm.go
99
+ @sm.sister
100
+ @sm.foo
101
+
102
+ @sm.state.should eql(:daughter)
103
+ end
104
+
105
+ end
106
+
107
+