statemachine 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +15 -0
- data/TODO +4 -0
- data/lib/statemachine.rb +5 -0
- data/lib/statemachine/builder.rb +111 -0
- data/lib/statemachine/proc_calling.rb +6 -41
- data/lib/statemachine/state.rb +7 -30
- data/lib/statemachine/state_machine.rb +63 -54
- data/lib/statemachine/super_state.rb +10 -42
- data/lib/statemachine/transition.rb +23 -21
- data/lib/statemachine/version.rb +2 -2
- data/spec/builder_spec.rb +131 -0
- data/spec/sm_action_parameterization_spec.rb +38 -53
- data/spec/sm_entry_exit_actions_spec.rb +35 -27
- data/spec/sm_odds_n_ends_spec.rb +47 -5
- data/spec/sm_simple_spec.rb +3 -36
- data/spec/sm_super_state_spec.rb +13 -54
- data/spec/sm_turnstile_spec.rb +18 -20
- data/spec/spec_helper.rb +15 -11
- data/spec/transition_spec.rb +68 -58
- metadata +7 -3
@@ -6,56 +6,58 @@ module StateMachine
|
|
6
6
|
|
7
7
|
include ProcCalling
|
8
8
|
|
9
|
-
attr_reader :
|
10
|
-
attr_accessor :
|
9
|
+
attr_reader :origin_id, :event, :action
|
10
|
+
attr_accessor :destination_id
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
12
|
+
def initialize(origin_id, destination_id, event, action)
|
13
|
+
@origin_id = origin_id
|
14
|
+
@destination_id = destination_id
|
15
15
|
@event = event
|
16
16
|
@action = action
|
17
17
|
end
|
18
18
|
|
19
|
-
def invoke(origin, args)
|
20
|
-
|
19
|
+
def invoke(origin, statemachine, args)
|
20
|
+
destination = statemachine.get_state(@destination_id)
|
21
|
+
exits, entries = exits_and_entries(origin, destination)
|
21
22
|
exits.each { |exited_state| exited_state.exit(args) }
|
22
23
|
|
23
|
-
call_proc(@action, args, "transition action from #{origin} invoked by '#{event}' event") if @action
|
24
|
-
|
25
|
-
entries.each { |entered_state| entered_state.enter(args) }
|
24
|
+
call_proc(@action, args, "transition action from #{origin} invoked by '#{@event}' event") if @action
|
26
25
|
|
27
|
-
terminal_state =
|
28
|
-
while terminal_state and terminal_state.
|
26
|
+
terminal_state = destination
|
27
|
+
while terminal_state and not terminal_state.is_concrete?
|
29
28
|
terminal_state = terminal_state.start_state
|
30
|
-
terminal_state
|
29
|
+
entries << terminal_state
|
31
30
|
end
|
31
|
+
terminal_state.activate if terminal_state
|
32
|
+
|
33
|
+
entries.each { |entered_state| entered_state.enter(args) }
|
32
34
|
end
|
33
35
|
|
34
|
-
def exits_and_entries(origin)
|
36
|
+
def exits_and_entries(origin, destination)
|
35
37
|
exits = []
|
36
|
-
entries = exits_and_entries_helper(exits, origin)
|
38
|
+
entries = exits_and_entries_helper(exits, origin, destination)
|
37
39
|
|
38
40
|
return exits, entries.reverse
|
39
41
|
end
|
40
42
|
|
41
43
|
def to_s
|
42
|
-
return "#{
|
44
|
+
return "#{@origin_id} ---#{@event}---> #{@destination_id} : #{action}"
|
43
45
|
end
|
44
46
|
|
45
47
|
private
|
46
48
|
|
47
|
-
def exits_and_entries_helper(exits, exit_state)
|
48
|
-
entries = entries_to_destination(exit_state)
|
49
|
+
def exits_and_entries_helper(exits, exit_state, destination)
|
50
|
+
entries = entries_to_destination(exit_state, destination)
|
49
51
|
return entries if entries
|
50
52
|
return [] if exit_state == nil
|
51
53
|
|
52
54
|
exits << exit_state
|
53
|
-
exits_and_entries_helper(exits, exit_state.superstate)
|
55
|
+
exits_and_entries_helper(exits, exit_state.superstate, destination)
|
54
56
|
end
|
55
57
|
|
56
|
-
def entries_to_destination(exit_state)
|
58
|
+
def entries_to_destination(exit_state, destination)
|
57
59
|
entries = []
|
58
|
-
state =
|
60
|
+
state = destination
|
59
61
|
while state
|
60
62
|
entries << state
|
61
63
|
return entries if exit_state == state.superstate
|
data/lib/statemachine/version.rb
CHANGED
@@ -0,0 +1,131 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "Builder" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@log = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def check_switch(sm)
|
10
|
+
sm.state.should_be :off
|
11
|
+
|
12
|
+
sm.toggle
|
13
|
+
@log[0].should_eql "toggle on"
|
14
|
+
sm.state.should_be :on
|
15
|
+
|
16
|
+
sm.toggle
|
17
|
+
@log[1].should_eql "toggle off"
|
18
|
+
sm.state.should_be :off
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "Building a the switch, relaxed" do
|
22
|
+
sm = StateMachine.build do |b|
|
23
|
+
b.trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
|
24
|
+
b.trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
|
25
|
+
end
|
26
|
+
|
27
|
+
check_switch sm
|
28
|
+
end
|
29
|
+
|
30
|
+
specify "Building a the switch, strict" do
|
31
|
+
sm = StateMachine.build do |b|
|
32
|
+
b.state(:off) { |s| s.event :toggle, :on, Proc.new { @log << "toggle on" } }
|
33
|
+
b.state(:on) { |s| s.event :toggle, :off, Proc.new { @log << "toggle off" } }
|
34
|
+
end
|
35
|
+
|
36
|
+
check_switch sm
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "Adding a superstate to the switch" do
|
40
|
+
sm = StateMachine.build do |b|
|
41
|
+
b.superstate :operation do |o|
|
42
|
+
o.event :admin, :testing, lambda { @log << "testing" }
|
43
|
+
o.trans :off, :toggle, :on, lambda { @log << "toggle on" }
|
44
|
+
o.trans :on, :toggle, :off, lambda { @log << "toggle off" }
|
45
|
+
o.start_state :on
|
46
|
+
end
|
47
|
+
b.trans :testing, :resume, :operation, lambda { @log << "resuming" }
|
48
|
+
b.start_state :off
|
49
|
+
end
|
50
|
+
|
51
|
+
sm.state.should_be :off
|
52
|
+
sm.toggle
|
53
|
+
sm.admin
|
54
|
+
sm.state.should_be :testing
|
55
|
+
sm.resume
|
56
|
+
sm.state.should_be :on
|
57
|
+
@log.join(",").should_eql "toggle on,testing,resuming"
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "entry exit actions" do
|
61
|
+
sm = StateMachine.build do |sm|
|
62
|
+
sm.state :off do |off|
|
63
|
+
off.on_entry { @log << "enter off" }
|
64
|
+
off.event :toggle, :on, lambda { @log << "toggle on" }
|
65
|
+
off.on_exit { @log << "exit off" }
|
66
|
+
end
|
67
|
+
sm.trans :on, :toggle, :off, lambda { @log << "toggle off" }
|
68
|
+
end
|
69
|
+
|
70
|
+
sm.toggle
|
71
|
+
sm.state.should_be :on
|
72
|
+
sm.toggle
|
73
|
+
sm.state.should_be :off
|
74
|
+
|
75
|
+
@log.join(",").should_eql "exit off,toggle on,toggle off,enter off"
|
76
|
+
end
|
77
|
+
|
78
|
+
specify "History state" do
|
79
|
+
sm = StateMachine.build do |b|
|
80
|
+
b.superstate :operation do |o|
|
81
|
+
o.event :admin, :testing, lambda { @log << "testing" }
|
82
|
+
o.state :off do |off|
|
83
|
+
off.on_entry { @log << "enter off" }
|
84
|
+
off.event :toggle, :on, lambda { @log << "toggle on" }
|
85
|
+
end
|
86
|
+
o.trans :on, :toggle, :off, lambda { @log << "toggle off" }
|
87
|
+
o.start_state :on
|
88
|
+
end
|
89
|
+
b.trans :testing, :resume, :operation_H, lambda { @log << "resuming" }
|
90
|
+
b.start_state :off
|
91
|
+
end
|
92
|
+
|
93
|
+
sm.admin
|
94
|
+
sm.resume
|
95
|
+
sm.state.should_be :off
|
96
|
+
|
97
|
+
@log.join(",").should_eql "testing,resuming,enter off"
|
98
|
+
end
|
99
|
+
|
100
|
+
specify "entry and exit action created from superstate builder" do
|
101
|
+
sm = StateMachine.build do |b|
|
102
|
+
b.trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
|
103
|
+
b.on_entry_of(:off) { @log << "entering off" }
|
104
|
+
b.trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
|
105
|
+
b.on_exit_of(:on) { @log << "exiting on" }
|
106
|
+
end
|
107
|
+
|
108
|
+
sm.toggle
|
109
|
+
sm.toggle
|
110
|
+
|
111
|
+
@log.join(",").should_eql "toggle on,exiting on,toggle off,entering off"
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
specify "superstate as startstate" do
|
116
|
+
|
117
|
+
lambda do
|
118
|
+
sm = StateMachine.build do |b|
|
119
|
+
b.superstate :mario_bros do |m|
|
120
|
+
m.trans :luigi, :bother, :mario
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sm.state.should_be :luigi
|
125
|
+
end.should_not_raise(Exception)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
end
|
131
|
+
|
@@ -5,110 +5,95 @@ context "State Machine Odds And Ends" do
|
|
5
5
|
|
6
6
|
setup do
|
7
7
|
create_switch
|
8
|
-
@sm.run
|
9
8
|
end
|
10
9
|
|
11
10
|
specify "action with one parameter" do
|
12
|
-
@sm.
|
11
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |value| @status = value } }
|
13
12
|
@sm.set "blue"
|
14
|
-
@status.
|
15
|
-
@sm.state.
|
13
|
+
@status.should_eql "blue"
|
14
|
+
@sm.state.should_be :on
|
16
15
|
end
|
17
16
|
|
18
17
|
specify "action with two parameters" do
|
19
|
-
@sm.
|
18
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b| @status = [a, b].join(",") } }
|
20
19
|
@sm.set "blue", "green"
|
21
|
-
@status.
|
22
|
-
@sm.state.
|
20
|
+
@status.should_eql "blue,green"
|
21
|
+
@sm.state.should_be :on
|
23
22
|
end
|
24
23
|
|
25
24
|
specify "action with three parameters" do
|
26
|
-
@sm.
|
25
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
27
26
|
@sm.set "blue", "green", "red"
|
28
|
-
@status.
|
29
|
-
@sm.state.
|
27
|
+
@status.should_eql "blue,green,red"
|
28
|
+
@sm.state.should_be :on
|
30
29
|
end
|
31
30
|
|
32
31
|
specify "action with four parameters" do
|
33
|
-
@sm.
|
32
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d| @status = [a, b, c, d].join(",") } }
|
34
33
|
@sm.set "blue", "green", "red", "orange"
|
35
|
-
@status.
|
36
|
-
@sm.state.
|
34
|
+
@status.should_eql "blue,green,red,orange"
|
35
|
+
@sm.state.should_be :on
|
37
36
|
end
|
38
37
|
|
39
38
|
specify "action with five parameters" do
|
40
|
-
@sm.
|
39
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e| @status = [a, b, c, d, e].join(",") } }
|
41
40
|
@sm.set "blue", "green", "red", "orange", "yellow"
|
42
|
-
@status.
|
43
|
-
@sm.state.
|
41
|
+
@status.should_eql "blue,green,red,orange,yellow"
|
42
|
+
@sm.state.should_be :on
|
44
43
|
end
|
45
44
|
|
46
45
|
specify "action with six parameters" do
|
47
|
-
@sm.
|
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(",") } }
|
48
47
|
@sm.set "blue", "green", "red", "orange", "yellow", "indigo"
|
49
|
-
@status.
|
50
|
-
@sm.state.
|
48
|
+
@status.should_eql "blue,green,red,orange,yellow,indigo"
|
49
|
+
@sm.state.should_be :on
|
51
50
|
end
|
52
51
|
|
53
52
|
specify "action with seven parameters" do
|
54
|
-
@sm.
|
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(",") } }
|
55
54
|
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet"
|
56
|
-
@status.
|
57
|
-
@sm.state.
|
55
|
+
@status.should_eql "blue,green,red,orange,yellow,indigo,violet"
|
56
|
+
@sm.state.should_be :on
|
58
57
|
end
|
59
58
|
|
60
59
|
specify "action with eight parameters" do
|
61
|
-
@sm.
|
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(",") } }
|
62
61
|
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet"
|
63
|
-
@status.
|
64
|
-
@sm.state.
|
65
|
-
end
|
66
|
-
|
67
|
-
specify "To many parameters" do
|
68
|
-
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h, i| @status = [a, b, c, d, e, f, g, h, i].join(",") } )
|
69
|
-
begin
|
70
|
-
@sm.process_event(:set, "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet", "Yikes!")
|
71
|
-
rescue StateMachine::StateMachineException => e
|
72
|
-
e.message.should_equal "Too many arguments(9). (transition action from 'off' state invoked by 'set' event)"
|
73
|
-
end
|
62
|
+
@status.should_eql "blue,green,red,orange,yellow,indigo,violet,ultra-violet"
|
63
|
+
@sm.state.should_be :on
|
74
64
|
end
|
75
65
|
|
76
66
|
specify "calling process_event with parameters" do
|
77
|
-
@sm.
|
67
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
78
68
|
@sm.process_event(:set, "blue", "green", "red")
|
79
|
-
@status.
|
80
|
-
@sm.state.
|
69
|
+
@status.should_eql "blue,green,red"
|
70
|
+
@sm.state.should_be :on
|
81
71
|
end
|
82
72
|
|
83
73
|
specify "Insufficient params" do
|
84
|
-
@sm.
|
85
|
-
|
86
|
-
|
87
|
-
rescue StateMachine::StateMachineException => e
|
88
|
-
e.message.should_equal "Insufficient parameters. (transition action from 'off' state invoked by 'set' event)"
|
89
|
-
end
|
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(StateMachine::StateMachineException,
|
76
|
+
"Insufficient parameters. (transition action from 'off' state invoked by 'set' event)")
|
90
77
|
end
|
91
78
|
|
92
79
|
specify "infinate args" do
|
93
|
-
|
80
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |*a| @status = a.join(",") } }
|
94
81
|
@sm.set(1, 2, 3)
|
95
|
-
@status.
|
82
|
+
@status.should_eql "1,2,3"
|
96
83
|
|
97
84
|
@sm.state = :off
|
98
85
|
@sm.set(1, 2, 3, 4, 5, 6)
|
99
|
-
@status.
|
86
|
+
@status.should_eql "1,2,3,4,5,6"
|
100
87
|
end
|
101
88
|
|
102
89
|
specify "Insufficient params when params are infinate" do
|
103
|
-
@sm.
|
90
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, *b| @status = a.to_s + ":" + b.join(",") } }
|
104
91
|
@sm.set(1, 2, 3)
|
105
|
-
@status.
|
92
|
+
@status.should_eql "1:2,3"
|
106
93
|
|
107
94
|
@sm.state = :off
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
e.message.should_equal "Insufficient parameters. (transition action from 'off' state invoked by 'set' event)"
|
112
|
-
end
|
95
|
+
|
96
|
+
lambda { @sm.set }.should_raise(StateMachine::StateMachineException,
|
97
|
+
"Insufficient parameters. (transition action from 'off' state invoked by 'set' event)")
|
113
98
|
end
|
114
99
|
end
|
@@ -4,73 +4,81 @@ context "State Machine Entry and Exit Actions" do
|
|
4
4
|
|
5
5
|
setup do
|
6
6
|
@log = []
|
7
|
-
@sm = StateMachine
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
@sm = StateMachine.build do |s|
|
8
|
+
s.trans :off, :toggle, :on, Proc.new { @log << "on" }
|
9
|
+
s.trans :on, :toggle, :off, Proc.new { @log << "off" }
|
10
|
+
end
|
11
11
|
end
|
12
12
|
|
13
13
|
specify "entry action" do
|
14
|
-
@sm
|
14
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
|
15
15
|
|
16
16
|
@sm.toggle
|
17
17
|
|
18
|
-
@log.join(",").
|
18
|
+
@log.join(",").should_eql "on,entered_on"
|
19
19
|
end
|
20
20
|
|
21
21
|
specify "exit action" do
|
22
|
-
@sm
|
22
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
|
23
23
|
|
24
24
|
@sm.toggle
|
25
25
|
|
26
|
-
@log.join(",").
|
26
|
+
@log.join(",").should_eql "exited_off,on"
|
27
27
|
end
|
28
28
|
|
29
29
|
specify "exit and entry" do
|
30
|
-
@sm
|
31
|
-
@sm
|
30
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
|
31
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
|
32
32
|
|
33
33
|
@sm.toggle
|
34
34
|
|
35
|
-
@log.join(",").
|
35
|
+
@log.join(",").should_eql "exited_off,on,entered_on"
|
36
36
|
end
|
37
37
|
|
38
38
|
specify "entry and exit actions may be parameterized" do
|
39
|
-
@sm
|
40
|
-
@sm
|
39
|
+
@sm.get_state(:off).exit_action = Proc.new { |a| @log << "exited_off(#{a})" }
|
40
|
+
@sm.get_state(:on).entry_action = Proc.new { |a, b| @log << "entered_on(#{a},#{b})" }
|
41
41
|
|
42
42
|
@sm.toggle "one", "two"
|
43
43
|
|
44
|
-
@log.join(",").
|
44
|
+
@log.join(",").should_eql "exited_off(one),on,entered_on(one,two)"
|
45
45
|
end
|
46
46
|
|
47
47
|
specify "current state is set prior to exit and entry actions" do
|
48
|
-
@sm
|
49
|
-
@sm
|
48
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << @sm.state }
|
49
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << @sm.state }
|
50
50
|
|
51
51
|
@sm.toggle
|
52
52
|
|
53
|
-
@log.join(",").
|
53
|
+
@log.join(",").should_eql "off,on,on"
|
54
54
|
end
|
55
55
|
|
56
56
|
specify "current state is set prior to exit and entry actions even with super states" do
|
57
|
-
@sm
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
@sm = StateMachine::StateMachine.new
|
58
|
+
StateMachine.build(@sm) do |s|
|
59
|
+
s.superstate :off_super do |off_s|
|
60
|
+
off_s.on_exit {@log << @sm.state}
|
61
|
+
off_s.trans :off, :toggle, :on, Proc.new { @log << "on" }
|
62
|
+
off_s.event :toggle, :on, Proc.new { @log << "super_on" }
|
63
|
+
end
|
64
|
+
s.superstate :on_super do |on_s|
|
65
|
+
on_s.on_entry { @log << @sm.state }
|
66
|
+
on_s.trans :on, :toggle, :off, Proc.new { @log << "off" }
|
67
|
+
on_s.event :toggle, :off, Proc.new { @log << "super_off" }
|
68
|
+
end
|
69
|
+
s.start_state :off
|
70
|
+
end
|
63
71
|
|
64
72
|
@sm.toggle
|
65
|
-
@log.join(",").
|
73
|
+
@log.join(",").should_eql "off,super_on,on"
|
66
74
|
end
|
67
75
|
|
68
76
|
specify "entry actions invokes another event" do
|
69
|
-
@sm
|
77
|
+
@sm.get_state(:on).entry_action = Proc.new { @sm.toggle }
|
70
78
|
|
71
79
|
@sm.toggle
|
72
|
-
@log.join(",").
|
73
|
-
@sm.state.
|
80
|
+
@log.join(",").should_eql "on,off"
|
81
|
+
@sm.state.should_be :off
|
74
82
|
end
|
75
83
|
|
76
84
|
|