finite_machine 0.11.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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