statemachine 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,56 +6,58 @@ module StateMachine
6
6
 
7
7
  include ProcCalling
8
8
 
9
- attr_reader :origin, :event, :action
10
- attr_accessor :destination
9
+ attr_reader :origin_id, :event, :action
10
+ attr_accessor :destination_id
11
11
 
12
- def initialize(origin, destination, event, action)
13
- @origin = origin
14
- @destination = destination
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
- exits, entries = exits_and_entries(origin)
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 = @destination
28
- while terminal_state and terminal_state.is_superstate?
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.enter(args)
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 "#{origin.id} ---#{event}---> #{destination.id} : #{action}"
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 = @destination
60
+ state = destination
59
61
  while state
60
62
  entries << state
61
63
  return entries if exit_state == state.superstate
@@ -2,8 +2,8 @@ module StateMachine
2
2
  module VERSION
3
3
  unless defined? MAJOR
4
4
  MAJOR = 0
5
- MINOR = 0
6
- TINY = 3
5
+ MINOR = 1
6
+ TINY = 0
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
9
9
  TAG = "REL_" + [MAJOR, MINOR, TINY].join('_')
@@ -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.add(:off, :set, :on, Proc.new { |value| @status = value } )
11
+ StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |value| @status = value } }
13
12
  @sm.set "blue"
14
- @status.should_equal "blue"
15
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b| @status = [a, b].join(",") } )
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.should_equal "blue,green"
22
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } )
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.should_equal "blue,green,red"
29
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c, d| @status = [a, b, c, d].join(",") } )
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.should_equal "blue,green,red,orange"
36
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c, d, e| @status = [a, b, c, d, e].join(",") } )
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.should_equal "blue,green,red,orange,yellow"
43
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f| @status = [a, b, c, d, e, f].join(",") } )
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.should_equal "blue,green,red,orange,yellow,indigo"
50
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g| @status = [a, b, c, d, e, f, g].join(",") } )
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.should_equal "blue,green,red,orange,yellow,indigo,violet"
57
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h| @status = [a, b, c, d, e, f, g, h].join(",") } )
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.should_equal "blue,green,red,orange,yellow,indigo,violet,ultra-violet"
64
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } )
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.should_equal "blue,green,red"
80
- @sm.state.id.should_be :on
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.add(:off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } )
85
- begin
86
- @sm.set "blue", "green"
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
- @sm.add(:off, :set, :on, Proc.new { |*a| @status = a.join(",") } )
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.should_equal "1,2,3"
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.should_equal "1,2,3,4,5,6"
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.add(:off, :set, :on, Proc.new { |a, *b| @status = a.to_s + ":" + b.join(",") } )
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.should_equal "1:2,3"
92
+ @status.should_eql "1:2,3"
106
93
 
107
94
  @sm.state = :off
108
- begin
109
- @sm.set
110
- rescue StateMachine::StateMachineException => e
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::StateMachine.new
8
- @sm.add(:off, :toggle, :on, Proc.new { @log << "on" } )
9
- @sm.add(:on, :toggle, :off, Proc.new { @log << "off" } )
10
- @sm.run
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[:on].on_entry Proc.new { @log << "entered_on" }
14
+ @sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
15
15
 
16
16
  @sm.toggle
17
17
 
18
- @log.join(",").should_equal "on,entered_on"
18
+ @log.join(",").should_eql "on,entered_on"
19
19
  end
20
20
 
21
21
  specify "exit action" do
22
- @sm[:off].on_exit Proc.new { @log << "exited_off" }
22
+ @sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
23
23
 
24
24
  @sm.toggle
25
25
 
26
- @log.join(",").should_equal "exited_off,on"
26
+ @log.join(",").should_eql "exited_off,on"
27
27
  end
28
28
 
29
29
  specify "exit and entry" do
30
- @sm[:off].on_exit Proc.new { @log << "exited_off" }
31
- @sm[:on].on_entry Proc.new { @log << "entered_on" }
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(",").should_equal "exited_off,on,entered_on"
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[:off].on_exit Proc.new { |a| @log << "exited_off(#{a})" }
40
- @sm[:on].on_entry Proc.new { |a, b| @log << "entered_on(#{a},#{b})" }
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(",").should_equal "exited_off(one),on,entered_on(one,two)"
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[:off].on_exit Proc.new { @log << @sm.state.id }
49
- @sm[:on].on_entry Proc.new { @log << @sm.state.id }
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(",").should_equal "off,on,on"
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.add(:off_super, :toggle, :on, Proc.new { @log << "super_on" } )
58
- @sm.add(:on_super, :toggle, :off, Proc.new { @log << "super_off" } )
59
- @sm[:off_super].add_substates(:off)
60
- @sm[:on_super].add_substates(:on)
61
- @sm[:off_super].on_exit Proc.new { @log << @sm.state.id }
62
- @sm[:on_super].on_entry Proc.new { @log << @sm.state.id }
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(",").should_equal "off_super,super_on,on_super"
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[:on].on_entry Proc.new { @sm.toggle }
77
+ @sm.get_state(:on).entry_action = Proc.new { @sm.toggle }
70
78
 
71
79
  @sm.toggle
72
- @log.join(",").should_equal "on,off"
73
- @sm.state.id.should_be :off
80
+ @log.join(",").should_eql "on,off"
81
+ @sm.state.should_be :off
74
82
  end
75
83
 
76
84