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.
- data/CHANGES +135 -0
- data/LICENSE +16 -0
- data/MINT-statemachine.gemspec +27 -0
- data/README.rdoc +69 -0
- data/Rakefile +88 -0
- data/TODO +2 -0
- data/lib/statemachine.rb +26 -0
- data/lib/statemachine/action_invokation.rb +83 -0
- data/lib/statemachine/builder.rb +383 -0
- data/lib/statemachine/generate/dot_graph.rb +1 -0
- data/lib/statemachine/generate/dot_graph/dot_graph_statemachine.rb +127 -0
- data/lib/statemachine/generate/java.rb +1 -0
- data/lib/statemachine/generate/java/java_statemachine.rb +265 -0
- data/lib/statemachine/generate/src_builder.rb +48 -0
- data/lib/statemachine/generate/util.rb +50 -0
- data/lib/statemachine/parallelstate.rb +196 -0
- data/lib/statemachine/state.rb +102 -0
- data/lib/statemachine/statemachine.rb +279 -0
- data/lib/statemachine/stub_context.rb +26 -0
- data/lib/statemachine/superstate.rb +53 -0
- data/lib/statemachine/transition.rb +76 -0
- data/lib/statemachine/version.rb +17 -0
- data/spec/action_invokation_spec.rb +101 -0
- data/spec/builder_spec.rb +243 -0
- data/spec/default_transition_spec.rb +111 -0
- data/spec/generate/dot_graph/dot_graph_stagemachine_spec.rb +27 -0
- data/spec/generate/java/java_statemachine_spec.rb +349 -0
- data/spec/history_spec.rb +107 -0
- data/spec/noodle.rb +23 -0
- data/spec/sm_action_parameterization_spec.rb +99 -0
- data/spec/sm_activation_spec.rb +116 -0
- data/spec/sm_entry_exit_actions_spec.rb +99 -0
- data/spec/sm_odds_n_ends_spec.rb +67 -0
- data/spec/sm_parallel_state_spec.rb +207 -0
- data/spec/sm_simple_spec.rb +26 -0
- data/spec/sm_super_state_spec.rb +55 -0
- data/spec/sm_turnstile_spec.rb +76 -0
- data/spec/spec_helper.rb +121 -0
- data/spec/transition_spec.rb +107 -0
- metadata +115 -0
data/spec/noodle.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Noodle
|
2
|
+
|
3
|
+
attr_accessor :shape, :cooked, :tasty
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@shape = "farfalla"
|
7
|
+
@cooked = false
|
8
|
+
@tasty = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def cook
|
12
|
+
@cooked = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def good
|
16
|
+
@tasty = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def transform(shape)
|
20
|
+
@shape = shape
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "State Machine Odds And Ends" do
|
4
|
+
include SwitchStatemachine
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
create_switch
|
8
|
+
end
|
9
|
+
|
10
|
+
it "action with one parameter" do
|
11
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |value| @status = value } }
|
12
|
+
@sm.set "blue"
|
13
|
+
@status.should eql("blue")
|
14
|
+
@sm.state.should equal(:on)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "action with two parameters" do
|
18
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b| @status = [a, b].join(",") } }
|
19
|
+
@sm.set "blue", "green"
|
20
|
+
@status.should eql("blue,green")
|
21
|
+
@sm.state.should equal(:on)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "action with three parameters" do
|
25
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
26
|
+
@sm.set "blue", "green", "red"
|
27
|
+
@status.should eql("blue,green,red")
|
28
|
+
@sm.state.should equal(:on)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "action with four parameters" do
|
32
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d| @status = [a, b, c, d].join(",") } }
|
33
|
+
@sm.set "blue", "green", "red", "orange"
|
34
|
+
@status.should eql("blue,green,red,orange")
|
35
|
+
@sm.state.should equal(:on)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "action with five parameters" do
|
39
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e| @status = [a, b, c, d, e].join(",") } }
|
40
|
+
@sm.set "blue", "green", "red", "orange", "yellow"
|
41
|
+
@status.should eql("blue,green,red,orange,yellow")
|
42
|
+
@sm.state.should equal(:on)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "action with six parameters" do
|
46
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e, f| @status = [a, b, c, d, e, f].join(",") } }
|
47
|
+
@sm.set "blue", "green", "red", "orange", "yellow", "indigo"
|
48
|
+
@status.should eql("blue,green,red,orange,yellow,indigo")
|
49
|
+
@sm.state.should equal(:on)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "action with seven parameters" do
|
53
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e, f, g| @status = [a, b, c, d, e, f, g].join(",") } }
|
54
|
+
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet"
|
55
|
+
@status.should eql("blue,green,red,orange,yellow,indigo,violet")
|
56
|
+
@sm.state.should equal(:on)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "action with eight parameters" do
|
60
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h| @status = [a, b, c, d, e, f, g, h].join(",") } }
|
61
|
+
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet"
|
62
|
+
@status.should eql("blue,green,red,orange,yellow,indigo,violet,ultra-violet")
|
63
|
+
@sm.state.should equal(:on)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "calling process_event with parameters" do
|
67
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
68
|
+
@sm.process_event(:set, "blue", "green", "red")
|
69
|
+
@status.should eql("blue,green,red")
|
70
|
+
@sm.state.should equal(:on)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "Insufficient params" do
|
74
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
75
|
+
lambda { @sm.set "blue", "green" }.should raise_error(Statemachine::StatemachineException,
|
76
|
+
"Insufficient parameters. (transition action from 'off' state invoked by 'set' event)")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "infinate args" do
|
80
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |*a| @status = a.join(",") } }
|
81
|
+
@sm.set(1, 2, 3)
|
82
|
+
@status.should eql("1,2,3")
|
83
|
+
|
84
|
+
@sm.state = :off
|
85
|
+
@sm.set(1, 2, 3, 4, 5, 6)
|
86
|
+
@status.should eql("1,2,3,4,5,6")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "Insufficient params when params are infinate" do
|
90
|
+
Statemachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, *b| @status = a.to_s + ":" + b.join(",") } }
|
91
|
+
@sm.set(1, 2, 3)
|
92
|
+
@status.should eql("1:2,3")
|
93
|
+
|
94
|
+
@sm.state = :off
|
95
|
+
|
96
|
+
lambda { @sm.set }.should raise_error(Statemachine::StatemachineException,
|
97
|
+
"Insufficient parameters. (transition action from 'off' state invoked by 'set' event)")
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "State Activation Callback" do
|
4
|
+
include SwitchStatemachine
|
5
|
+
include ParallelStatemachine
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
class ActivationCallback
|
9
|
+
attr_reader :called
|
10
|
+
attr_reader :state
|
11
|
+
attr_reader :abstract_states
|
12
|
+
attr_reader :atomic_states
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@called = []
|
16
|
+
@state = []
|
17
|
+
@abstract_states = []
|
18
|
+
@atomic_states =[]
|
19
|
+
|
20
|
+
end
|
21
|
+
def activate(state,abstract_states, atomic_states)
|
22
|
+
@called << true
|
23
|
+
@state << state
|
24
|
+
@abstract_states << abstract_states
|
25
|
+
@atomic_states << atomic_states
|
26
|
+
puts "activate #{@state.last} #{@abstract_states.last} #{@atomic_states.last}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@callback = ActivationCallback.new
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should fire on successful state change" do
|
34
|
+
create_switch
|
35
|
+
@sm.activation=@callback.method(:activate)
|
36
|
+
@callback.called.length.should == 0
|
37
|
+
@sm.toggle
|
38
|
+
@callback.called.length.should == 1
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should deliver new active state on state change" do
|
42
|
+
create_switch
|
43
|
+
@sm.activation=@callback.method(:activate)
|
44
|
+
@sm.toggle
|
45
|
+
@callback.state.last.should == :on
|
46
|
+
@callback.atomic_states.last.should == [:on]
|
47
|
+
@callback.abstract_states.last.should == [:root]
|
48
|
+
@sm.toggle
|
49
|
+
@callback.state.last.should == :off
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should deliver new active state on state change of parallel state machine" do
|
53
|
+
create_parallel
|
54
|
+
|
55
|
+
@sm.activation=@callback.method(:activate)
|
56
|
+
@sm.go
|
57
|
+
@callback.called.length.should == 2
|
58
|
+
@callback.state.last.should == :on
|
59
|
+
@callback.abstract_states.last.should.eql? [:operative, :root, :onoff]
|
60
|
+
@callback.atomic_states.last.should == [:locked, :on]
|
61
|
+
@sm.toggle
|
62
|
+
@callback.state.last.should == :off
|
63
|
+
@callback.abstract_states.last.should.eql? [:onoff,:operative,:root]
|
64
|
+
@callback.atomic_states.last.should.eql? [:off,:locked]
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
it "activation works for on_entry ticks as well" do
|
69
|
+
create_tick
|
70
|
+
@sm.activation=@callback.method(:activate)
|
71
|
+
@sm.toggle
|
72
|
+
@callback.called.length.should == 2
|
73
|
+
@callback.state.last.should == :off
|
74
|
+
@callback.state.first.should == :on
|
75
|
+
@callback.atomic_states.last.should == [:off]
|
76
|
+
@callback.atomic_states.first.should == [:on]
|
77
|
+
@callback.abstract_states.last.should == [:root]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "activation works for self-transitions as well" do
|
81
|
+
create_tome
|
82
|
+
@sm.activation=@callback.method(:activate)
|
83
|
+
@sm.toggle
|
84
|
+
@callback.called.length.should == 1
|
85
|
+
@callback.state.last.should == :me
|
86
|
+
@callback.atomic_states.last.should == [:me]
|
87
|
+
@callback.abstract_states.last.should == [:root]
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should activate corretly on direct entry to parallel state" do
|
91
|
+
@sm = Statemachine.build do
|
92
|
+
trans :start,:go, :unlocked
|
93
|
+
parallel :p do
|
94
|
+
statemachine :s1 do
|
95
|
+
superstate :operative do
|
96
|
+
trans :locked, :coin, :unlocked, Proc.new { @cooked = true }
|
97
|
+
trans :unlocked, :coin, :locked
|
98
|
+
end
|
99
|
+
end
|
100
|
+
statemachine :s2 do
|
101
|
+
superstate :onoff do
|
102
|
+
trans :on, :toggle, :off
|
103
|
+
trans :off, :toggle, :on
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
@sm.activation=@callback.method(:activate)
|
110
|
+
@sm.go
|
111
|
+
@callback.state.should.eql? [:unlocked,:on]
|
112
|
+
@callback.called.length.should == 2
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "State Machine Entry and Exit Actions" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@log = []
|
7
|
+
@sm = Statemachine.build do
|
8
|
+
trans :off, :toggle, :on, Proc.new { @log << "on" }
|
9
|
+
trans :on, :toggle, :off, Proc.new { @log << "off" }
|
10
|
+
end
|
11
|
+
@sm.context = self
|
12
|
+
end
|
13
|
+
|
14
|
+
it "entry action" do
|
15
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
|
16
|
+
|
17
|
+
@sm.toggle
|
18
|
+
|
19
|
+
@log.join(",").should eql("on,entered_on")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "exit action" do
|
23
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
|
24
|
+
|
25
|
+
@sm.toggle
|
26
|
+
|
27
|
+
@log.join(",").should eql("exited_off,on")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "exit and entry" do
|
31
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
|
32
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
|
33
|
+
|
34
|
+
@sm.toggle
|
35
|
+
|
36
|
+
@log.join(",").should eql("exited_off,on,entered_on")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "entry and exit actions may be parameterized" do
|
40
|
+
@sm.get_state(:off).exit_action = Proc.new { |a| @log << "exited_off(#{a})" }
|
41
|
+
@sm.get_state(:on).entry_action = Proc.new { |a, b| @log << "entered_on(#{a},#{b})" }
|
42
|
+
|
43
|
+
@sm.toggle "one", "two"
|
44
|
+
|
45
|
+
@log.join(",").should eql("exited_off(one),on,entered_on(one,two)")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "current state is set prior to exit and entry actions" do
|
49
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << @sm.state }
|
50
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << @sm.state }
|
51
|
+
|
52
|
+
@sm.toggle
|
53
|
+
|
54
|
+
@log.join(",").should eql("off,on,on")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "current state is set prior to exit and entry actions even with super states" do
|
58
|
+
@sm = Statemachine::Statemachine.new
|
59
|
+
Statemachine.build(@sm) do
|
60
|
+
superstate :off_super do
|
61
|
+
on_exit Proc.new {@log << @sm.state}
|
62
|
+
state :off
|
63
|
+
event :toggle, :on, Proc.new { @log << "super_on" }
|
64
|
+
end
|
65
|
+
superstate :on_super do
|
66
|
+
on_entry Proc.new { @log << @sm.state }
|
67
|
+
state :on
|
68
|
+
event :toggle, :off, Proc.new { @log << "super_off" }
|
69
|
+
end
|
70
|
+
startstate :off
|
71
|
+
end
|
72
|
+
@sm.context = self
|
73
|
+
|
74
|
+
@sm.toggle
|
75
|
+
@log.join(",").should eql("off,super_on,on")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "entry actions invokes another event" do
|
79
|
+
@sm.get_state(:on).entry_action = Proc.new { @sm.toggle }
|
80
|
+
|
81
|
+
@sm.toggle
|
82
|
+
@log.join(",").should eql("on,off")
|
83
|
+
@sm.state.should equal(:off)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "startstate's entry action should be called when the statemachine starts" do
|
87
|
+
the_context = self
|
88
|
+
@sm = Statemachine.build do
|
89
|
+
trans :a, :b, :c
|
90
|
+
on_entry_of :a, Proc.new { @log << "entering a" }
|
91
|
+
context the_context
|
92
|
+
end
|
93
|
+
|
94
|
+
@log.join(",").should eql("entering a")
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "State Machine Odds And Ends" do
|
4
|
+
include SwitchStatemachine
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
create_switch
|
8
|
+
end
|
9
|
+
|
10
|
+
it "method missing delegates to super in case of no event" do
|
11
|
+
$method_missing_called = false
|
12
|
+
module Blah
|
13
|
+
def method_missing(message, *args)
|
14
|
+
$method_missing_called = true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@sm.extend(Blah)
|
18
|
+
@sm.blah
|
19
|
+
$method_missing_called.should eql(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should raise TransistionMissingException when the state doesn't respond to the event" do
|
23
|
+
lambda { @sm.blah }.should raise_error(Statemachine::TransitionMissingException, "'off' state does not respond to the 'blah' event.")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should respond to valid events" do
|
27
|
+
@sm.respond_to?(:toggle).should eql(true)
|
28
|
+
@sm.respond_to?(:blah).should eql(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not crash when respond_to? called when the statemachine is not in a state" do
|
32
|
+
@sm.instance_eval { @state = nil }
|
33
|
+
lambda { @sm.respond_to?(:toggle) }.should_not raise_error
|
34
|
+
@sm.respond_to?(:toggle).should eql(false)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "set state with string" do
|
38
|
+
@sm.state.should equal(:off)
|
39
|
+
@sm.state = "on"
|
40
|
+
@sm.state.should equal(:on)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "set state with symbol" do
|
44
|
+
@sm.state.should equal(:off)
|
45
|
+
@sm.state = :on
|
46
|
+
@sm.state.should equal(:on)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "process event accepts strings" do
|
50
|
+
@sm.process_event("toggle")
|
51
|
+
@sm.state.should equal(:on)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "states without transitions are valid" do
|
55
|
+
@sm = Statemachine.build do
|
56
|
+
trans :middle, :push, :stuck
|
57
|
+
startstate :middle
|
58
|
+
end
|
59
|
+
|
60
|
+
@sm.push
|
61
|
+
@sm.state.should equal(:stuck)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require "noodle"
|
3
|
+
|
4
|
+
describe "Nested parallel" do
|
5
|
+
before(:each) do
|
6
|
+
@out_out_order = false
|
7
|
+
@locked = true
|
8
|
+
@noodle = Noodle.new
|
9
|
+
|
10
|
+
@sm = Statemachine.build do
|
11
|
+
trans :start,:go,:p
|
12
|
+
state :maintenance
|
13
|
+
parallel :p do
|
14
|
+
statemachine :s1 do
|
15
|
+
superstate :operative do
|
16
|
+
trans :locked, :coin, :unlocked, Proc.new { @cooked = true }
|
17
|
+
trans :unlocked, :coin, :locked
|
18
|
+
event :maintain, :maintenance, Proc.new { @out_of_order = true }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
statemachine :s2 do
|
22
|
+
superstate :onoff do
|
23
|
+
trans :on, :toggle, :off
|
24
|
+
trans :off, :toggle, :on
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@sm.context = @noodle
|
31
|
+
end
|
32
|
+
# @TODO add tests that set a certain state that is part of a parallel state machine
|
33
|
+
# to check if
|
34
|
+
# the other sub statemachine is set to the initial state
|
35
|
+
# the other sub state machines states doe not change if already in this parallel state machine
|
36
|
+
it "supports entering a parallel state" do
|
37
|
+
@sm.state.should eql :start
|
38
|
+
@sm.go
|
39
|
+
@sm.state.should eql :p
|
40
|
+
@sm.states_id.should == [:locked,:on]
|
41
|
+
@sm.coin
|
42
|
+
@sm.state.should eql :p
|
43
|
+
@sm.states_id.should == [:unlocked,:on]
|
44
|
+
@sm.toggle
|
45
|
+
@sm.state.should eql :p
|
46
|
+
@sm.states_id.should == [:unlocked,:off]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "supports leaving a parallel state" do
|
50
|
+
@sm.states_id.should == [:start]
|
51
|
+
@sm.go
|
52
|
+
@sm.states_id.should == [:locked,:on]
|
53
|
+
@sm.maintain
|
54
|
+
@sm.state.should == :maintenance
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
it "support testing with 'in' condition for superstates " do
|
59
|
+
@sm.go
|
60
|
+
@sm.process_event(:coin)
|
61
|
+
@sm.In(:unlocked).should == true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "support testing with 'in' condition for parallel superstates " do
|
65
|
+
@sm.go
|
66
|
+
@sm.coin
|
67
|
+
@sm.In(:onoff).should == true
|
68
|
+
@sm.In(:operative).should == true
|
69
|
+
@sm.In(:on).should == true
|
70
|
+
|
71
|
+
# @sm.is_in_state?(:second).should == true
|
72
|
+
|
73
|
+
@sm.maintain # TODO not working
|
74
|
+
@sm.In(:maintenance).should == true
|
75
|
+
end
|
76
|
+
|
77
|
+
it "supports process_event for parallel states" do
|
78
|
+
@sm.go
|
79
|
+
@sm.process_event(:coin)
|
80
|
+
@sm.In(:onoff).should == true
|
81
|
+
@sm.In(:operative).should == true
|
82
|
+
@sm.In(:on).should == true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "supports calling transition actions inside parallel state changes" do
|
86
|
+
@noodle.cooked.should equal(false)
|
87
|
+
@sm.go
|
88
|
+
@sm.process_event(:coin)
|
89
|
+
@noodle.cooked.should equal(true)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "supports calling transition actions inside parallel state changes from instant context set by process_event" do
|
93
|
+
@noodle2 = Noodle.new
|
94
|
+
@noodle2.cooked.should equal(false)
|
95
|
+
@sm.go
|
96
|
+
@sm.context = @noodle2
|
97
|
+
@sm.process_event(:coin)
|
98
|
+
@noodle2.cooked.should equal(true)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should support state recovery" do
|
102
|
+
@sm.states=[:locked,:off]
|
103
|
+
@sm.toggle
|
104
|
+
puts @sm.abstract_states
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should support parallel states inside superstates" do
|
108
|
+
@sm = Statemachine.build do
|
109
|
+
trans :start,:go,:s
|
110
|
+
state :maintenance
|
111
|
+
superstate :s do
|
112
|
+
parallel :p do
|
113
|
+
statemachine :s1 do
|
114
|
+
superstate :operative do
|
115
|
+
trans :locked, :coin, :unlocked, Proc.new { @cooked = true }
|
116
|
+
trans :unlocked, :coin, :locked
|
117
|
+
event :maintain, :maintenance, Proc.new { @out_of_order = true }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
statemachine :s2 do
|
121
|
+
superstate :onoff do
|
122
|
+
trans :on, :toggle, :off
|
123
|
+
trans :off, :toggle, :on
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
@sm.go
|
131
|
+
@sm.states.should.eql? [:locked,:off]
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should support direct transitions into an atomic state of a parallel state set" do
|
136
|
+
@sm = Statemachine.build do
|
137
|
+
trans :start,:go, :unlocked
|
138
|
+
state :maintenance
|
139
|
+
superstate :s do
|
140
|
+
parallel :p do
|
141
|
+
statemachine :s1 do
|
142
|
+
superstate :operative do
|
143
|
+
trans :locked, :coin, :unlocked, Proc.new { @cooked = true }
|
144
|
+
trans :unlocked, :coin, :locked
|
145
|
+
event :maintain, :maintenance, Proc.new { @out_of_order = true }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
statemachine :s2 do
|
149
|
+
superstate :onoff do
|
150
|
+
trans :on, :toggle, :off
|
151
|
+
trans :off, :toggle, :on
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
@sm.go
|
159
|
+
@sm.state.should eql :p
|
160
|
+
@sm.states_id.should == [:unlocked,:on]
|
161
|
+
@sm.maintain
|
162
|
+
@sm.state.should eql :maintenance
|
163
|
+
@sm.states_id.should == [:maintenance]
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should support leaving a parallel state by an event from a super state of the parallel state" do
|
167
|
+
pending ("superstates have problems with late defined events ")
|
168
|
+
@sm = Statemachine.build do
|
169
|
+
trans :start,:go, :unlocked
|
170
|
+
state :maintenance
|
171
|
+
superstate :test do
|
172
|
+
superstate :s do
|
173
|
+
event :m, :maintenance
|
174
|
+
parallel :p do
|
175
|
+
statemachine :s1 do
|
176
|
+
trans :locked, :coin, :unlocked, Proc.new { @cooked = true }
|
177
|
+
trans :unlocked, :coin, :locked
|
178
|
+
end
|
179
|
+
statemachine :s2 do
|
180
|
+
superstate :onoff do
|
181
|
+
trans :on, :toggle, :off
|
182
|
+
trans :off, :toggle, :on
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
event :repair, :maintenance # this one does not work, event has to be defined directly after superstate definition!
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
@sm.go
|
192
|
+
@sm.state.should eql :p
|
193
|
+
@sm.states_id.should == [:unlocked,:on]
|
194
|
+
@sm.toggle
|
195
|
+
@sm.repair
|
196
|
+
@sm.state.should eql :maintenance
|
197
|
+
@sm.states_id.should == [:maintenance]
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should fail for undefined events if actual state is inside a parallel state" do
|
201
|
+
@sm.go
|
202
|
+
lambda {@sm.unknown}.should raise_error
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
end
|