finite_machine 0.11.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +80 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +679 -624
  5. data/lib/finite_machine.rb +35 -45
  6. data/lib/finite_machine/async_call.rb +5 -21
  7. data/lib/finite_machine/callable.rb +4 -4
  8. data/lib/finite_machine/catchable.rb +24 -14
  9. data/lib/finite_machine/choice_merger.rb +20 -20
  10. data/lib/finite_machine/const.rb +16 -0
  11. data/lib/finite_machine/definition.rb +3 -3
  12. data/lib/finite_machine/dsl.rb +98 -151
  13. data/lib/finite_machine/env.rb +4 -2
  14. data/lib/finite_machine/event_definition.rb +7 -15
  15. data/lib/finite_machine/{events_chain.rb → events_map.rb} +40 -53
  16. data/lib/finite_machine/hook_event.rb +60 -61
  17. data/lib/finite_machine/hooks.rb +44 -36
  18. data/lib/finite_machine/listener.rb +2 -2
  19. data/lib/finite_machine/logger.rb +5 -4
  20. data/lib/finite_machine/{event_queue.rb → message_queue.rb} +76 -32
  21. data/lib/finite_machine/observer.rb +71 -34
  22. data/lib/finite_machine/safety.rb +16 -14
  23. data/lib/finite_machine/state_definition.rb +3 -5
  24. data/lib/finite_machine/state_machine.rb +93 -76
  25. data/lib/finite_machine/state_parser.rb +55 -83
  26. data/lib/finite_machine/subscribers.rb +2 -2
  27. data/lib/finite_machine/threadable.rb +3 -1
  28. data/lib/finite_machine/transition.rb +34 -34
  29. data/lib/finite_machine/transition_builder.rb +23 -32
  30. data/lib/finite_machine/transition_event.rb +12 -11
  31. data/lib/finite_machine/two_phase_lock.rb +8 -6
  32. data/lib/finite_machine/undefined_transition.rb +5 -6
  33. data/lib/finite_machine/version.rb +2 -2
  34. metadata +58 -142
  35. data/.gitignore +0 -18
  36. data/.rspec +0 -5
  37. data/.ruby-gemset +0 -1
  38. data/.ruby-version +0 -1
  39. data/.travis.yml +0 -26
  40. data/Gemfile +0 -15
  41. data/Rakefile +0 -8
  42. data/assets/finite_machine_logo.png +0 -0
  43. data/examples/atm.rb +0 -45
  44. data/examples/bug_system.rb +0 -145
  45. data/finite_machine.gemspec +0 -23
  46. data/lib/finite_machine/async_proxy.rb +0 -30
  47. data/spec/integration/system_spec.rb +0 -95
  48. data/spec/spec_helper.rb +0 -33
  49. data/spec/unit/alias_target_spec.rb +0 -108
  50. data/spec/unit/async_events_spec.rb +0 -138
  51. data/spec/unit/callable/call_spec.rb +0 -113
  52. data/spec/unit/callbacks_spec.rb +0 -942
  53. data/spec/unit/can_spec.rb +0 -98
  54. data/spec/unit/choice_spec.rb +0 -331
  55. data/spec/unit/define_spec.rb +0 -55
  56. data/spec/unit/definition_spec.rb +0 -115
  57. data/spec/unit/event_names_spec.rb +0 -19
  58. data/spec/unit/event_queue_spec.rb +0 -52
  59. data/spec/unit/events_chain/add_spec.rb +0 -25
  60. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  61. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  62. data/spec/unit/events_chain/clear_spec.rb +0 -15
  63. data/spec/unit/events_chain/events_spec.rb +0 -18
  64. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  65. data/spec/unit/events_chain/match_transition_spec.rb +0 -37
  66. data/spec/unit/events_chain/move_to_spec.rb +0 -48
  67. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  68. data/spec/unit/events_spec.rb +0 -459
  69. data/spec/unit/handlers_spec.rb +0 -152
  70. data/spec/unit/hook_event/build_spec.rb +0 -15
  71. data/spec/unit/hook_event/eql_spec.rb +0 -36
  72. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  73. data/spec/unit/hook_event/initialize_spec.rb +0 -25
  74. data/spec/unit/hook_event/notify_spec.rb +0 -14
  75. data/spec/unit/hooks/call_spec.rb +0 -24
  76. data/spec/unit/hooks/clear_spec.rb +0 -16
  77. data/spec/unit/hooks/inspect_spec.rb +0 -17
  78. data/spec/unit/hooks/register_spec.rb +0 -22
  79. data/spec/unit/if_unless_spec.rb +0 -353
  80. data/spec/unit/initial_spec.rb +0 -222
  81. data/spec/unit/inspect_spec.rb +0 -17
  82. data/spec/unit/is_spec.rb +0 -55
  83. data/spec/unit/log_transitions_spec.rb +0 -30
  84. data/spec/unit/logger_spec.rb +0 -38
  85. data/spec/unit/respond_to_spec.rb +0 -38
  86. data/spec/unit/state_parser/inspect_spec.rb +0 -25
  87. data/spec/unit/state_parser/parse_spec.rb +0 -59
  88. data/spec/unit/states_spec.rb +0 -34
  89. data/spec/unit/subscribers_spec.rb +0 -42
  90. data/spec/unit/target_spec.rb +0 -225
  91. data/spec/unit/terminated_spec.rb +0 -95
  92. data/spec/unit/transition/check_conditions_spec.rb +0 -54
  93. data/spec/unit/transition/inspect_spec.rb +0 -25
  94. data/spec/unit/transition/matches_spec.rb +0 -23
  95. data/spec/unit/transition/states_spec.rb +0 -31
  96. data/spec/unit/transition/to_state_spec.rb +0 -27
  97. data/spec/unit/trigger_spec.rb +0 -22
  98. data/spec/unit/undefined_transition/eql_spec.rb +0 -17
  99. data/tasks/console.rake +0 -11
  100. data/tasks/coverage.rake +0 -11
  101. data/tasks/spec.rake +0 -29
@@ -1,108 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::Definition, '#alias_target' do
6
-
7
- before do
8
- stub_const("Car", Class.new do
9
- def turn_reverse_lights_off
10
- @reverse_lights = false
11
- end
12
-
13
- def turn_reverse_lights_on
14
- @reverse_lights = true
15
- end
16
-
17
- def reverse_lights?
18
- @reverse_lights ||= false
19
- end
20
- end)
21
- end
22
-
23
- it "aliases target" do
24
- car = Car.new
25
- fsm = FiniteMachine.new
26
- fsm.target(car)
27
-
28
- expect(fsm.target).to eq(car)
29
- expect { fsm.car }.to raise_error(NoMethodError)
30
-
31
- fsm.alias_target(:delorean)
32
- expect(fsm.delorean).to eq(car)
33
- end
34
-
35
- it "scopes the target alias to a state machine instance" do
36
- delorean = Car.new
37
- batmobile = Car.new
38
- fsm_a = FiniteMachine.new
39
- fsm_a.target(delorean)
40
- fsm_b = FiniteMachine.new
41
- fsm_b.target(batmobile)
42
-
43
- fsm_a.alias_target(:delorean)
44
- fsm_b.alias_target(:batmobile)
45
-
46
- expect(fsm_a.delorean).to eq(delorean)
47
- expect { fsm_a.batmobile }.to raise_error(NoMethodError)
48
-
49
- expect(fsm_b.batmobile).to eq(batmobile)
50
- expect { fsm_b.delorean }.to raise_error(NoMethodError)
51
- end
52
-
53
- context 'when inside definition' do
54
- before do
55
- class Engine < FiniteMachine::Definition
56
- initial :neutral
57
-
58
- alias_target :car
59
-
60
- events {
61
- event :forward, [:reverse, :neutral] => :one
62
- event :shift, :one => :two
63
- event :shift, :two => :one
64
- event :back, [:neutral, :one] => :reverse
65
- }
66
-
67
- callbacks {
68
- on_enter :reverse do |event|
69
- car.turn_reverse_lights_on
70
- end
71
-
72
- on_exit :reverse do |event|
73
- car.turn_reverse_lights_off
74
- end
75
- }
76
-
77
- handlers {
78
- handle FiniteMachine::InvalidStateError do |exception| end
79
- }
80
- end
81
- end
82
-
83
- it "creates unique instances" do
84
- engine_a = Engine.new
85
- engine_b = Engine.new
86
- expect(engine_a).not_to be(engine_b)
87
-
88
- engine_a.forward
89
- expect(engine_a.current).to eq(:one)
90
- expect(engine_b.current).to eq(:neutral)
91
- end
92
-
93
- it "allows to create standalone machine" do
94
- car = Car.new
95
- engine = Engine.new
96
- engine.target car
97
- expect(engine.current).to eq(:neutral)
98
-
99
- engine.forward
100
- expect(engine.current).to eq(:one)
101
- expect(car.reverse_lights?).to be false
102
-
103
- engine.back
104
- expect(engine.current).to eq(:reverse)
105
- expect(car.reverse_lights?).to be true
106
- end
107
- end
108
- end
@@ -1,138 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'async_events' do
6
-
7
- it 'runs events asynchronously' do
8
- called = []
9
- fsm = FiniteMachine.define do
10
- initial :green
11
-
12
- events {
13
- event :slow, :green => :yellow
14
- event :stop, :yellow => :red
15
- event :ready, :red => :yellow
16
- event :go, :yellow => :green
17
- }
18
-
19
- callbacks {
20
- on_enter :yellow do |event, a| called << "on_enter_yellow_#{a}" end
21
- on_enter :red do |event, a| called << "on_enter_red_#{a}" end
22
- }
23
- end
24
-
25
- expect(fsm.current).to eql(:green)
26
- fsm.async.slow(:foo)
27
- fsm.event_queue.join 0.01
28
- expect(fsm.current).to eql(:yellow)
29
- expect(called).to eql([
30
- 'on_enter_yellow_foo'
31
- ])
32
- fsm.async(:stop, :bar) # execute directly
33
- fsm.event_queue.join 0.01
34
- expect(fsm.current).to eql(:red)
35
- expect(called).to match_array([
36
- 'on_enter_yellow_foo',
37
- 'on_enter_red_bar'
38
- ])
39
- end
40
-
41
- it 'correctly passes parameters to conditionals' do
42
- called = []
43
- fsm = FiniteMachine.define do
44
- events {
45
- event :go, :none => :green,
46
- if: proc { |context, arg|
47
- called << "cond_none_green(#{context},#{arg})"; true
48
- }
49
-
50
- event :stop, from: :any do
51
- choice :red, if: proc { |context, arg|
52
- called << "cond_any_red(#{context},#{arg})"; true
53
- }
54
- end
55
- }
56
- end
57
- expect(fsm.current).to eql(:none)
58
- fsm.async.go(:foo)
59
- fsm.event_queue.join 0.02
60
- expect(fsm.current).to eql(:green)
61
- expect(called).to eql(["cond_none_green(#{fsm},foo)"])
62
-
63
- expect(fsm.current).to eql(:green)
64
- fsm.async.stop(:bar)
65
- fsm.event_queue.join 0.02
66
- expect(fsm.current).to eql(:red)
67
- expect(called).to match_array([
68
- "cond_none_green(#{fsm},foo)",
69
- "cond_any_red(#{fsm},bar)"
70
- ])
71
- end
72
-
73
- it "ensure queue per thread" do
74
- called = []
75
- fsmFoo = nil
76
- fsmBar = nil
77
- foo_thread = Thread.new {
78
- fsmFoo = FiniteMachine.define do
79
- initial :green
80
- events { event :slow, :green => :yellow }
81
-
82
- callbacks {
83
- on_enter :yellow do |event, a| called << "(foo)on_enter_yellow_#{a}" end
84
- }
85
- end
86
- fsmFoo.async.slow(:foo)
87
- }
88
- bar_thread = Thread.new {
89
- fsmBar = FiniteMachine.define do
90
- initial :green
91
- events { event :slow, :green => :yellow }
92
-
93
- callbacks {
94
- on_enter :yellow do |event, a| called << "(bar)on_enter_yellow_#{a}" end
95
- }
96
- end
97
- fsmBar.async.slow(:bar)
98
- }
99
- ThreadsWait.all_waits(foo_thread, bar_thread)
100
- fsmFoo.event_queue.join(0.01)
101
- fsmBar.event_queue.join(0.01)
102
- expect(called).to match_array([
103
- '(foo)on_enter_yellow_foo',
104
- '(bar)on_enter_yellow_bar'
105
- ])
106
- expect(fsmFoo.current).to eql(:yellow)
107
- expect(fsmBar.current).to eql(:yellow)
108
- end
109
-
110
- it "permits async callback" do
111
- called = []
112
- fsm = FiniteMachine.define do
113
- initial :green, silent: false
114
-
115
- events {
116
- event :slow, :green => :yellow
117
- event :go, :yellow => :green
118
- }
119
-
120
- callbacks {
121
- on_enter :green, :async do |event| called << 'on_enter_green' end
122
- on_before :slow, :async do |event| called << 'on_before_slow' end
123
- on_exit :yellow, :async do |event| called << 'on_exit_yellow' end
124
- on_after :go, :async do |event| called << 'on_after_go' end
125
- }
126
- end
127
- fsm.slow
128
- fsm.go
129
- sleep 0.1
130
- expect(called).to match_array([
131
- 'on_enter_green',
132
- 'on_before_slow',
133
- 'on_exit_yellow',
134
- 'on_enter_green',
135
- 'on_after_go'
136
- ])
137
- end
138
- end
@@ -1,113 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::Callable, '#call' do
6
-
7
- before(:each) {
8
- stub_const("Car", Class.new do
9
- attr_reader :result
10
-
11
- def initialize
12
- @engine_on = false
13
- end
14
-
15
- def turn_engine_on
16
- @result = 'turn_engine_on'
17
- @engine_on = true
18
- end
19
-
20
- def set_engine(value = :on)
21
- @result = "set_engine(#{value})"
22
- @engine = value.to_sym == :on
23
- end
24
-
25
- def turn_engine_off
26
- @result = 'turn_engine_off'
27
- @engine_on = false
28
- end
29
-
30
- def engine_on?
31
- @result = 'engine_on'
32
- !!@engine_on
33
- end
34
- end)
35
- }
36
-
37
- let(:called) { [] }
38
-
39
- let(:target) { Car.new }
40
-
41
- let(:instance) { described_class.new(object) }
42
-
43
- context 'when string' do
44
- let(:object) { 'engine_on?' }
45
-
46
- it 'executes method on target' do
47
- instance.call(target)
48
- expect(target.result).to eql('engine_on')
49
- end
50
- end
51
-
52
- context 'when string' do
53
- let(:object) { 'set_engine(:on)' }
54
-
55
- it 'executes method with arguments' do
56
- instance.call(target)
57
- expect(target.result).to eql('set_engine(on)')
58
- end
59
- end
60
-
61
- context 'when string with arguments' do
62
- let(:object) { 'set_engine' }
63
-
64
- it 'executes method with arguments' do
65
- instance.call(target, :off)
66
- expect(target.result).to eql('set_engine(off)')
67
- end
68
- end
69
-
70
- context 'when symbol' do
71
- let(:object) { :set_engine }
72
-
73
- it 'executes method on target' do
74
- instance.call(target)
75
- expect(target.result).to eql('set_engine(on)')
76
- end
77
- end
78
-
79
- context 'when symbol with arguments' do
80
- let(:object) { :set_engine }
81
-
82
- it 'executes method on target' do
83
- instance.call(target, :off)
84
- expect(target.result).to eql('set_engine(off)')
85
- end
86
- end
87
-
88
- context 'when proc without args' do
89
- let(:object) { proc { |a| called << "block_with(#{a})" } }
90
-
91
- it 'passes arguments' do
92
- instance.call(target)
93
- expect(called).to eql(["block_with(#{target})"])
94
- end
95
- end
96
-
97
- context 'when proc with args' do
98
- let(:object) { proc { |a,b| called << "block_with(#{a},#{b})" } }
99
-
100
- it 'passes arguments' do
101
- instance.call(target, :red)
102
- expect(called).to eql(["block_with(#{target},red)"])
103
- end
104
- end
105
-
106
- context 'when unknown' do
107
- let(:object) { Object.new }
108
-
109
- it 'raises error' do
110
- expect { instance.call(target) }.to raise_error(ArgumentError)
111
- end
112
- end
113
- end
@@ -1,942 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'callbacks' do
6
-
7
- it "triggers default init event" do
8
- called = []
9
- fsm = FiniteMachine.define do
10
- initial :green, defer: true, silent: false
11
-
12
- callbacks {
13
- # generic state callbacks
14
- on_enter do |event| called << 'on_enter' end
15
- on_transition do |event| called << 'on_transition' end
16
- on_exit do |event| called << 'on_exit' end
17
-
18
- # generic event callbacks
19
- on_before do |event| called << 'on_before' end
20
- on_after do |event| called << 'on_after' end
21
-
22
- # state callbacks
23
- on_enter :none do |event| called << 'on_enter_none' end
24
- on_enter :green do |event| called << 'on_enter_green' end
25
-
26
- on_transition :none do |event| called << 'on_transition_none' end
27
- on_transition :green do |event| called << 'on_transition_green' end
28
-
29
- on_exit :none do |event| called << 'on_exit_none' end
30
- on_exit :green do |event| called << 'on_exit_green' end
31
-
32
- # event callbacks
33
- on_before :init do |event| called << 'on_before_init' end
34
- on_after :init do |event| called << 'on_after_init' end
35
- }
36
- end
37
-
38
- expect(fsm.current).to eql(:none)
39
- fsm.init
40
- expect(called).to eql([
41
- 'on_before_init',
42
- 'on_before',
43
- 'on_exit_none',
44
- 'on_exit',
45
- 'on_transition_green',
46
- 'on_transition',
47
- 'on_enter_green',
48
- 'on_enter',
49
- 'on_after_init',
50
- 'on_after'
51
- ])
52
- end
53
-
54
- it "executes callbacks in order" do
55
- called = []
56
- fsm = FiniteMachine.define do
57
- initial :green, silent: false
58
-
59
- events {
60
- event :slow, :green => :yellow
61
- event :stop, :yellow => :red
62
- event :ready, :red => :yellow
63
- event :go, :yellow => :green
64
- }
65
-
66
- callbacks {
67
- # generic callbacks
68
- on_enter do |event| called << 'on_enter' end
69
- on_transition do |event| called << 'on_transition' end
70
- on_exit do |event| called << 'on_exit' end
71
-
72
- on_before do |event| called << 'on_before' end
73
- on_after do |event| called << 'on_after' end
74
-
75
- # state callbacks
76
- on_enter :green do |event| called << 'on_enter_green' end
77
- on_enter :yellow do |event| called << "on_enter_yellow" end
78
- on_enter :red do |event| called << "on_enter_red" end
79
-
80
- on_transition :green do |event| called << 'on_transition_green' end
81
- on_transition :yellow do |event| called << "on_transition_yellow" end
82
- on_transition :red do |event| called << "on_transition_red" end
83
-
84
- on_exit :green do |event| called << 'on_exit_green' end
85
- on_exit :yellow do |event| called << "on_exit_yellow" end
86
- on_exit :red do |event| called << "on_exit_red" end
87
-
88
- # event callbacks
89
- on_before :slow do |event| called << 'on_before_slow' end
90
- on_before :stop do |event| called << "on_before_stop" end
91
- on_before :ready do |event| called << "on_before_ready" end
92
- on_before :go do |event| called << "on_before_go" end
93
-
94
- on_after :slow do |event| called << 'on_after_slow' end
95
- on_after :stop do |event| called << "on_after_stop" end
96
- on_after :ready do |event| called << "on_after_ready" end
97
- on_after :go do |event| called << "on_after_go" end
98
- }
99
- end
100
-
101
- expect(fsm.current).to eq(:green)
102
- expect(called).to eq([
103
- 'on_before',
104
- 'on_exit',
105
- 'on_transition_green',
106
- 'on_transition',
107
- 'on_enter_green',
108
- 'on_enter',
109
- 'on_after'
110
- ])
111
-
112
- called = []
113
- fsm.slow
114
- expect(called).to eql([
115
- 'on_before_slow',
116
- 'on_before',
117
- 'on_exit_green',
118
- 'on_exit',
119
- 'on_transition_yellow',
120
- 'on_transition',
121
- 'on_enter_yellow',
122
- 'on_enter',
123
- 'on_after_slow',
124
- 'on_after'
125
- ])
126
-
127
- called = []
128
- fsm.stop
129
- expect(called).to eql([
130
- 'on_before_stop',
131
- 'on_before',
132
- 'on_exit_yellow',
133
- 'on_exit',
134
- 'on_transition_red',
135
- 'on_transition',
136
- 'on_enter_red',
137
- 'on_enter',
138
- 'on_after_stop',
139
- 'on_after'
140
- ])
141
-
142
- called = []
143
- fsm.ready
144
- expect(called).to eql([
145
- 'on_before_ready',
146
- 'on_before',
147
- 'on_exit_red',
148
- 'on_exit',
149
- 'on_transition_yellow',
150
- 'on_transition',
151
- 'on_enter_yellow',
152
- 'on_enter',
153
- 'on_after_ready',
154
- 'on_after'
155
- ])
156
-
157
- called = []
158
- fsm.go
159
- expect(called).to eql([
160
- 'on_before_go',
161
- 'on_before',
162
- 'on_exit_yellow',
163
- 'on_exit',
164
- 'on_transition_green',
165
- 'on_transition',
166
- 'on_enter_green',
167
- 'on_enter',
168
- 'on_after_go',
169
- 'on_after'
170
- ])
171
- end
172
-
173
- it "maintains transition execution sequence from UML statechart" do
174
- called = []
175
- fsm = FiniteMachine.define do
176
- initial :previous, silent: false
177
-
178
- events {
179
- event :go, :previous => :next, if: -> { called << 'guard'; true}
180
- }
181
-
182
- callbacks {
183
- on_exit { |event| called << "exit_#{event.from}" }
184
- on_before { |event| called << "before_#{event.name}" }
185
- on_transition { |event| called << "transition_#{event.from}_#{event.to}"}
186
- on_enter { |event| called << "enter_#{event.to}"}
187
- on_after { |event| called << "after_#{event.name}" }
188
- }
189
- end
190
- expect(fsm.current).to eq(:previous)
191
- fsm.go
192
- expect(called).to eq([
193
- 'before_init',
194
- 'exit_none',
195
- 'transition_none_previous',
196
- 'enter_previous',
197
- 'after_init',
198
- 'before_go',
199
- 'guard',
200
- 'exit_previous',
201
- 'transition_previous_next',
202
- 'enter_next',
203
- 'after_go'
204
- ])
205
- end
206
-
207
- it "allows multiple callbacks for the same state" do
208
- called = []
209
- fsm = FiniteMachine.define do
210
- initial :green, silent: false
211
-
212
- events {
213
- event :slow, :green => :yellow
214
- event :stop, :yellow => :red
215
- event :ready, :red => :yellow
216
- event :go, :yellow => :green
217
- }
218
-
219
- callbacks {
220
- # generic state callbacks
221
- on_enter do |event| called << 'on_enter' end
222
- on_transition do |event| called << 'on_transition' end
223
- on_exit do |event| called << 'on_exit' end
224
-
225
- # generic event callbacks
226
- on_before do |event| called << 'on_before' end
227
- on_after do |event| called << 'on_after' end
228
-
229
- # state callbacks
230
- on_exit :green do |event| called << 'on_exit_green_1' end
231
- on_exit :green do |event| called << 'on_exit_green_2' end
232
- on_enter :yellow do |event| called << 'on_enter_yellow_1' end
233
- on_enter :yellow do |event| called << 'on_enter_yellow_2' end
234
- on_transition :yellow do |event| called << 'on_transition_yellow_1' end
235
- on_transition :yellow do |event| called << 'on_transition_yellow_2' end
236
-
237
- # event callbacks
238
- on_before :slow do |event| called << 'on_before_slow_1' end
239
- on_before :slow do |event| called << 'on_before_slow_2' end
240
- on_after :slow do |event| called << 'on_after_slow_1' end
241
- on_after :slow do |event| called << 'on_after_slow_2' end
242
- }
243
- end
244
-
245
- expect(fsm.current).to eql(:green)
246
- expect(called).to eql([
247
- 'on_before',
248
- 'on_exit',
249
- 'on_transition',
250
- 'on_enter',
251
- 'on_after'
252
- ])
253
- called = []
254
- fsm.slow
255
- expect(fsm.current).to eql(:yellow)
256
- expect(called).to eql([
257
- 'on_before_slow_1',
258
- 'on_before_slow_2',
259
- 'on_before',
260
- 'on_exit_green_1',
261
- 'on_exit_green_2',
262
- 'on_exit',
263
- 'on_transition_yellow_1',
264
- 'on_transition_yellow_2',
265
- 'on_transition',
266
- 'on_enter_yellow_1',
267
- 'on_enter_yellow_2',
268
- 'on_enter',
269
- 'on_after_slow_1',
270
- 'on_after_slow_2',
271
- 'on_after'
272
- ])
273
- end
274
-
275
- it "allows for fluid callback definition" do
276
- called = []
277
- fsm = FiniteMachine.define do
278
- initial :green
279
-
280
- events {
281
- event :slow, :green => :yellow
282
- event :stop, :yellow => :red
283
- event :ready, :red => :yellow
284
- event :go, :yellow => :green
285
- }
286
-
287
- callbacks {
288
- # state callbacks
289
- on_exit_green do |event| called << 'on_exit_green' end
290
- on_enter_yellow do |event| called << 'on_enter_yellow' end
291
- on_transition_yellow do |event| called << 'on_transition_yellow' end
292
-
293
- # event callbacks
294
- on_before_slow do |event| called << 'on_before_slow' end
295
- on_after_slow do |event| called << 'on_after_slow' end
296
- }
297
- end
298
-
299
- called = []
300
- fsm.slow
301
- expect(fsm.current).to eql(:yellow)
302
- expect(called).to eql([
303
- 'on_before_slow',
304
- 'on_exit_green',
305
- 'on_transition_yellow',
306
- 'on_enter_yellow',
307
- 'on_after_slow'
308
- ])
309
- end
310
-
311
- it "passes event object to callback" do
312
- evt = nil
313
- fsm = FiniteMachine.define do
314
- initial :green
315
-
316
- events {
317
- event :slow, :green => :yellow
318
- }
319
-
320
- callbacks {
321
- on_enter(:yellow) { |e| evt = e }
322
- }
323
- end
324
-
325
- expect(fsm.current).to eql(:green)
326
- fsm.slow
327
- expect(fsm.current).to eql(:yellow)
328
-
329
- expect(evt.from).to eql(:green)
330
- expect(evt.to).to eql(:yellow)
331
- expect(evt.name).to eql(:slow)
332
- end
333
-
334
- it "identifies the from state for callback event parameter" do
335
- evt = nil
336
- fsm = FiniteMachine.define do
337
- initial :green
338
-
339
- events {
340
- event :slow, [:red, :blue, :green] => :yellow
341
- event :fast, :red => :purple
342
- }
343
-
344
- callbacks {
345
- on_enter(:yellow) { |e| evt = e }
346
- }
347
- end
348
-
349
- expect(fsm.current).to eql(:green)
350
- fsm.slow
351
- expect(fsm.current).to eql(:yellow)
352
-
353
- expect(evt.from).to eql(:green)
354
- expect(evt.to).to eql(:yellow)
355
- expect(evt.name).to eql(:slow)
356
- end
357
-
358
- it "passes extra parameters to callbacks" do
359
- expected = {name: :init, from: :none, to: :green, a: nil, b: nil, c: nil }
360
-
361
- callback = Proc.new { |event, a, b, c|
362
- target.expect(event.from).to target.eql(expected[:from])
363
- target.expect(event.to).to target.eql(expected[:to])
364
- target.expect(event.name).to target.eql(expected[:name])
365
- target.expect(a).to target.eql(expected[:a])
366
- target.expect(b).to target.eql(expected[:b])
367
- target.expect(c).to target.eql(expected[:c])
368
- }
369
- context = self
370
-
371
- fsm = FiniteMachine.define do
372
- initial :green
373
-
374
- target context
375
-
376
- events {
377
- event :slow, :green => :yellow
378
- event :stop, :yellow => :red
379
- event :ready, :red => :yellow
380
- event :go, :yellow => :green
381
- }
382
-
383
- callbacks {
384
- # generic state callbacks
385
- on_enter(&callback)
386
- on_transition(&callback)
387
- on_exit(&callback)
388
-
389
- # generic event callbacks
390
- on_before(&callback)
391
- on_after(&callback)
392
-
393
- # state callbacks
394
- on_enter :green, &callback
395
- on_enter :yellow, &callback
396
- on_enter :red, &callback
397
-
398
- on_transition :green , &callback
399
- on_transition :yellow, &callback
400
- on_transition :red , &callback
401
-
402
- on_exit :green , &callback
403
- on_exit :yellow, &callback
404
- on_exit :red , &callback
405
-
406
- # event callbacks
407
- on_before :slow , &callback
408
- on_before :stop , &callback
409
- on_before :ready, &callback
410
- on_before :go , &callback
411
-
412
- on_after :slow , &callback
413
- on_after :stop , &callback
414
- on_after :ready, &callback
415
- on_after :go , &callback
416
- }
417
- end
418
-
419
- expected = {name: :slow, from: :green, to: :yellow, a: 1, b: 2, c: 3}
420
- fsm.slow(1, 2, 3)
421
-
422
- expected = {name: :stop, from: :yellow, to: :red, a: 'foo', b: 'bar'}
423
- fsm.stop('foo', 'bar')
424
-
425
- expected = {name: :ready, from: :red, to: :yellow, a: :foo, b: :bar}
426
- fsm.ready(:foo, :bar)
427
-
428
- expected = {name: :go, from: :yellow, to: :green, a: nil, b: nil}
429
- fsm.go(nil, nil)
430
- end
431
-
432
- it "sets callback parameters correctly for transition from :any state" do
433
- expected = {name: :init, from: :none, to: :green, a: nil, b: nil, c: nil }
434
-
435
- callback = Proc.new { |event, a, b, c|
436
- target.expect(event.from).to target.eql(expected[:from])
437
- target.expect(event.to).to target.eql(expected[:to])
438
- target.expect(event.name).to target.eql(expected[:name])
439
- target.expect(a).to target.eql(expected[:a])
440
- target.expect(b).to target.eql(expected[:b])
441
- target.expect(c).to target.eql(expected[:c])
442
- }
443
-
444
- context = self
445
-
446
- fsm = FiniteMachine.define do
447
- initial :red
448
-
449
- target context
450
-
451
- events {
452
- event :power_on, :off => :red
453
- event :power_off, :any => :off
454
- event :go, :red => :green
455
- event :slow, :green => :yellow
456
- event :stop, :yellow => :red
457
- }
458
-
459
- callbacks {
460
- # generic state callbacks
461
- on_enter(&callback)
462
- on_transition(&callback)
463
- on_exit(&callback)
464
-
465
- # generic event callbacks
466
- on_before(&callback)
467
- on_after(&callback)
468
-
469
- # state callbacks
470
- on_enter :green, &callback
471
- on_enter :yellow, &callback
472
- on_enter :red, &callback
473
- on_enter :off, &callback
474
- on_enter :off, &callback
475
-
476
- on_transition :green, &callback
477
- on_transition :yellow, &callback
478
- on_transition :red, &callback
479
- on_transition :off, &callback
480
- on_transition :off, &callback
481
-
482
- on_exit :green, &callback
483
- on_exit :yellow, &callback
484
- on_exit :red, &callback
485
- on_exit :off, &callback
486
- on_exit :off, &callback
487
-
488
- # event callbacks
489
- on_before :power_on, &callback
490
- on_before :power_off, &callback
491
- on_before :go, &callback
492
- on_before :slow, &callback
493
- on_before :stop, &callback
494
-
495
- on_after :power_on, &callback
496
- on_after :power_off, &callback
497
- on_after :go, &callback
498
- on_after :slow, &callback
499
- on_after :stop, &callback
500
- }
501
- end
502
-
503
- expect(fsm.current).to eq(:red)
504
-
505
- expected = {name: :go, from: :red, to: :green, a: 1, b: 2, c: 3 }
506
- fsm.go(1, 2, 3)
507
-
508
- expected = {name: :slow, from: :green, to: :yellow, a: 4, b: 5, c: 6}
509
- fsm.slow(4, 5, 6)
510
-
511
- expected = {name: :stop, from: :yellow, to: :red, a: 7, b: 8, c: 9}
512
- fsm.stop(7, 8, 9)
513
-
514
- expected = {name: :power_off, from: :red, to: :off, a: 10, b: 11, c: 12}
515
- fsm.power_off(10, 11, 12)
516
- end
517
-
518
- it "raises an error with invalid callback name" do
519
- expect {
520
- FiniteMachine.define do
521
- initial :green
522
-
523
- events {
524
- event :slow, :green => :yellow
525
- }
526
-
527
- callbacks {
528
- on_enter(:magic) { |event| called << 'on_enter'}
529
- }
530
- end
531
- }.to raise_error(FiniteMachine::InvalidCallbackNameError, /\"magic\" is not a valid callback name/)
532
- end
533
-
534
- it "doesn't allow to mix state callback with event name" do
535
- expect {
536
- FiniteMachine.define do
537
- events { event :slow, :green => :yellow }
538
-
539
- callbacks { on_enter_slow do |event| end }
540
- end
541
- }.to raise_error(FiniteMachine::InvalidCallbackNameError, "\"on_enter\" callback is a state listener and cannot be used with \"slow\" event name. Please use on_before or on_after instead.")
542
- end
543
-
544
- it "doesn't allow to mix event callback with state name" do
545
- expect {
546
- FiniteMachine.define do
547
- events { event :slow, :green => :yellow }
548
-
549
- callbacks { on_before_green do |event| end }
550
- end
551
- }.to raise_error(FiniteMachine::InvalidCallbackNameError, '"on_before" callback is an event listener and cannot be used with "green" state name. Please use on_enter, on_transition or on_exit instead.')
552
- end
553
-
554
- it "propagates exceptions raised inside callback" do
555
- fsm = FiniteMachine.define do
556
- initial :green
557
-
558
- events { event :slow, :green => :yellow }
559
-
560
- callbacks { on_enter(:yellow) { raise RuntimeError } }
561
- end
562
-
563
- expect(fsm.current).to eql(:green)
564
- expect { fsm.slow }.to raise_error(RuntimeError)
565
- end
566
-
567
- it "executes callbacks with multiple 'from' transitions" do
568
- called = []
569
- fsm = FiniteMachine.define do
570
- initial :green
571
-
572
- events {
573
- event :stop, :green => :yellow
574
- event :stop, :yellow => :red
575
- }
576
-
577
- callbacks {
578
- on_before_stop do |event|
579
- called << 'on_before_stop'
580
- end
581
- }
582
- end
583
- expect(fsm.current).to eql(:green)
584
- fsm.stop
585
- expect(fsm.current).to eql(:yellow)
586
- fsm.stop
587
- expect(fsm.current).to eql(:red)
588
- expect(called).to eql([
589
- 'on_before_stop',
590
- 'on_before_stop'
591
- ])
592
- end
593
-
594
- it "allows to define callbacks on machine instance" do
595
- called = []
596
- fsm = FiniteMachine.define do
597
- initial :green
598
-
599
- events {
600
- event :slow, :green => :yellow
601
- event :stop, :yellow => :red
602
- event :ready, :red => :yellow
603
- event :go, :yellow => :green
604
- }
605
- end
606
-
607
- fsm.on_enter_yellow do |event|
608
- called << 'on_enter_yellow'
609
- end
610
-
611
- expect(fsm.current).to eql(:green)
612
- fsm.slow
613
- expect(called).to eql([
614
- 'on_enter_yellow'
615
- ])
616
- end
617
-
618
- it "raises error for unknown callback" do
619
- expect { FiniteMachine.define do
620
- initial :green
621
-
622
- events {
623
- event :slow, :green => :yellow
624
- event :stop, :yellow => :red
625
- event :ready, :red => :yellow
626
- event :go, :yellow => :green
627
- }
628
-
629
- callbacks {
630
- on_enter_unknown do |event| end
631
- }
632
- end }.to raise_error(NoMethodError)
633
- end
634
-
635
- it "triggers callbacks only once" do
636
- called = []
637
- fsm = FiniteMachine.define do
638
- initial :green, silent: false
639
-
640
- events {
641
- event :slow, :green => :yellow
642
- event :go, :yellow => :green
643
- }
644
-
645
- callbacks {
646
- # state callbacks
647
- once_on_enter_green do |event| called << 'once_on_enter_green' end
648
- once_on_enter_yellow do |event| called << 'once_on_enter_yellow' end
649
-
650
- once_on_transition_green do |event| called << 'once_on_transition_green' end
651
- once_on_transition_yellow do |event| called << 'once_on_transition_yellow' end
652
- once_on_exit_none do |event| called << 'once_on_exit_none' end
653
- once_on_exit_green do |event| called << 'once_on_exit_green' end
654
- once_on_exit_yellow do |event| called << 'once_on_exit_yellow' end
655
-
656
- # event callbacks
657
- once_on_before_init do |event| called << 'once_on_before_init' end
658
- once_on_before_slow do |event| called << 'once_on_before_slow' end
659
- once_on_before_go do |event| called << 'once_on_before_go' end
660
-
661
- once_on_after_init do |event| called << 'once_on_after_init' end
662
- once_on_after_slow do |event| called << 'once_on_after_slow' end
663
- once_on_after_go do |event| called << 'once_on_after_go' end
664
- }
665
- end
666
- expect(fsm.current).to eql(:green)
667
- fsm.slow
668
- expect(fsm.current).to eql(:yellow)
669
- fsm.go
670
- expect(fsm.current).to eql(:green)
671
- fsm.slow
672
- expect(fsm.current).to eql(:yellow)
673
- expect(called).to eql([
674
- 'once_on_before_init',
675
- 'once_on_exit_none',
676
- 'once_on_transition_green',
677
- 'once_on_enter_green',
678
- 'once_on_after_init',
679
- 'once_on_before_slow',
680
- 'once_on_exit_green',
681
- 'once_on_transition_yellow',
682
- 'once_on_enter_yellow',
683
- 'once_on_after_slow',
684
- 'once_on_before_go',
685
- 'once_on_exit_yellow',
686
- 'once_on_after_go'
687
- ])
688
- end
689
-
690
- it "cancels transition on event callback" do
691
- fsm = FiniteMachine.define do
692
- initial :green
693
-
694
- events {
695
- event :slow, :green => :yellow
696
- event :go, :yellow => :green
697
- }
698
-
699
- callbacks {
700
- on_exit :green do |event|
701
- FiniteMachine::CANCELLED
702
- end
703
- }
704
- end
705
-
706
- expect(fsm.current).to eql(:green)
707
- fsm.slow
708
- expect(fsm.current).to eql(:green)
709
- end
710
-
711
- it "stops executing callbacks when cancelled" do
712
- called = []
713
-
714
- fsm = FiniteMachine.define do
715
- initial :initial
716
-
717
- events { event :bump, initial: :low }
718
-
719
- callbacks {
720
- on_before do |event|
721
- called << "enter_#{event.name}_#{event.from}_#{event.to}"
722
-
723
- FiniteMachine::CANCELLED
724
- end
725
-
726
- on_exit :initial do |event| called << "exit_initial" end
727
- on_exit do |event| called << "exit_any" end
728
- on_enter :low do |event| called << "enter_low" end
729
- on_after :bump do |event| called << "after_#{event.name}" end
730
- on_after do |event| called << "after_any" end
731
- }
732
- end
733
-
734
- fsm.bump
735
-
736
- expect(called).to eq(['enter_bump_initial_low'])
737
- end
738
-
739
- xit "groups callbacks"
740
-
741
- it "groups states from separate events with the same name" do
742
- callbacks = []
743
- fsm = FiniteMachine.define do
744
- initial :initial, silent: false
745
-
746
- events {
747
- event :bump, :initial => :low
748
- event :bump, :low => :medium
749
- event :bump, :medium => :high
750
- }
751
-
752
- callbacks {
753
- on_enter do |event|
754
- callbacks << "enter_#{event.name}_#{event.from}_#{event.to}"
755
- end
756
- on_exit do |event|
757
- callbacks << "exit_#{event.name}_#{event.from}_#{event.to}"
758
- end
759
- on_before do |event|
760
- callbacks << "before_#{event.name}_#{event.from}_#{event.to}"
761
- end
762
- on_after do |event|
763
- callbacks << "after_#{event.name}_#{event.from}_#{event.to}"
764
- end
765
- }
766
- end
767
- expect(fsm.current).to eq(:initial)
768
- fsm.bump
769
- expect(callbacks).to eq([
770
- 'before_init_none_initial',
771
- 'exit_init_none_initial',
772
- 'enter_init_none_initial',
773
- 'after_init_none_initial',
774
- 'before_bump_initial_low',
775
- 'exit_bump_initial_low',
776
- 'enter_bump_initial_low',
777
- 'after_bump_initial_low'
778
- ])
779
- fsm.bump
780
- expect(callbacks).to eq([
781
- 'before_init_none_initial',
782
- 'exit_init_none_initial',
783
- 'enter_init_none_initial',
784
- 'after_init_none_initial',
785
- 'before_bump_initial_low',
786
- 'exit_bump_initial_low',
787
- 'enter_bump_initial_low',
788
- 'after_bump_initial_low',
789
- 'before_bump_low_medium',
790
- 'exit_bump_low_medium',
791
- 'enter_bump_low_medium',
792
- 'after_bump_low_medium'
793
- ])
794
- fsm.bump
795
- expect(callbacks).to eq([
796
- 'before_init_none_initial',
797
- 'exit_init_none_initial',
798
- 'enter_init_none_initial',
799
- 'after_init_none_initial',
800
- 'before_bump_initial_low',
801
- 'exit_bump_initial_low',
802
- 'enter_bump_initial_low',
803
- 'after_bump_initial_low',
804
- 'before_bump_low_medium',
805
- 'exit_bump_low_medium',
806
- 'enter_bump_low_medium',
807
- 'after_bump_low_medium',
808
- 'before_bump_medium_high',
809
- 'exit_bump_medium_high',
810
- 'enter_bump_medium_high',
811
- 'after_bump_medium_high'
812
- ])
813
- end
814
-
815
- it "groups states under event name" do
816
- callbacks = []
817
- fsm = FiniteMachine.define do
818
- initial :initial, silent: false
819
-
820
- events {
821
- event :bump, :initial => :low,
822
- :low => :medium,
823
- :medium => :high
824
- }
825
-
826
- callbacks {
827
- on_enter do |event|
828
- callbacks << "enter_#{event.name}_#{event.from}_#{event.to}"
829
- end
830
- on_before do |event|
831
- callbacks << "before_#{event.name}_#{event.from}_#{event.to}"
832
- end
833
- }
834
- end
835
- expect(fsm.current).to eq(:initial)
836
- fsm.bump
837
- expect(callbacks).to eq([
838
- 'before_init_none_initial',
839
- 'enter_init_none_initial',
840
- 'before_bump_initial_low',
841
- 'enter_bump_initial_low'
842
- ])
843
- fsm.bump
844
- expect(callbacks).to eq([
845
- 'before_init_none_initial',
846
- 'enter_init_none_initial',
847
- 'before_bump_initial_low',
848
- 'enter_bump_initial_low',
849
- 'before_bump_low_medium',
850
- 'enter_bump_low_medium'
851
- ])
852
- fsm.bump
853
- expect(callbacks).to eq([
854
- 'before_init_none_initial',
855
- 'enter_init_none_initial',
856
- 'before_bump_initial_low',
857
- 'enter_bump_initial_low',
858
- 'before_bump_low_medium',
859
- 'enter_bump_low_medium',
860
- 'before_bump_medium_high',
861
- 'enter_bump_medium_high'
862
- ])
863
- end
864
-
865
- it "permits state and event with the same name" do
866
- called = []
867
- fsm = FiniteMachine.define do
868
- initial :on_hook, silent: false
869
-
870
- events {
871
- event :off_hook, :on_hook => :off_hook
872
- event :on_hook, :off_hook => :on_hook
873
- }
874
-
875
- callbacks {
876
- on_before(:on_hook) { |event| called << "on_before_#{event.name}"}
877
- on_enter(:on_hook) { |event| called << "on_enter_#{event.to}"}
878
- }
879
- end
880
- expect(fsm.current).to eq(:on_hook)
881
- expect(called).to eq([
882
- 'on_enter_on_hook'
883
- ])
884
- fsm.off_hook
885
- expect(fsm.current).to eq(:off_hook)
886
- fsm.on_hook
887
- expect(called).to eq([
888
- 'on_enter_on_hook',
889
- 'on_before_on_hook',
890
- 'on_enter_on_hook'
891
- ]);
892
- end
893
-
894
- it "allows to selectively silence events" do
895
- called = []
896
- fsm = FiniteMachine.define do
897
- initial :yellow
898
-
899
- events {
900
- event :go, :yellow => :green, silent: true
901
- event :stop, :green => :red
902
- }
903
-
904
- callbacks {
905
- on_enter :green do |event| called << 'on_enter_yellow' end
906
- on_enter :red do |event| called << 'on_enter_red' end
907
- }
908
- end
909
- expect(fsm.current).to eq(:yellow)
910
- fsm.go
911
- fsm.stop
912
- expect(called).to eq(['on_enter_red'])
913
- end
914
-
915
- it "executes event-based callbacks even when state does not change" do
916
- called = []
917
- fsm = FiniteMachine.define do
918
- initial :active
919
-
920
- events {
921
- event :advance, :active => :inactive, if: -> { false }
922
- event :advance, :inactive => :active, if: -> { false }
923
- }
924
-
925
- callbacks {
926
- on_before do |event|
927
- called << "before_#{event.name}_#{event.from}_#{event.to}"
928
- end
929
- on_after do |event|
930
- called << "after_#{event.name}_#{event.from}_#{event.to}"
931
- end
932
- }
933
- end
934
- expect(fsm.current).to eq(:active)
935
- fsm.advance
936
- expect(fsm.current).to eq(:active)
937
- expect(called).to eq([
938
- 'before_advance_active_inactive',
939
- 'after_advance_active_inactive'
940
- ])
941
- end
942
- end