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