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,115 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::Definition, 'definition' do
6
-
7
- before do
8
- class Engine < FiniteMachine::Definition
9
- initial :neutral
10
-
11
- events {
12
- event :forward, [:reverse, :neutral] => :one
13
- event :shift, :one => :two
14
- event :shift, :two => :one
15
- event :back, [:neutral, :one] => :reverse
16
- }
17
-
18
- callbacks {
19
- on_enter :reverse do |event|
20
- target.turn_reverse_lights_on
21
- end
22
-
23
- on_exit :reverse do |event|
24
- target.turn_reverse_lights_off
25
- end
26
- }
27
-
28
- handlers {
29
- handle FiniteMachine::InvalidStateError do |exception| end
30
- }
31
- end
32
- end
33
-
34
- it "creates unique instances" do
35
- engine_a = Engine.new
36
- engine_b = Engine.new
37
- expect(engine_a).not_to be(engine_b)
38
-
39
- engine_a.forward
40
- expect(engine_a.current).to eq(:one)
41
- expect(engine_b.current).to eq(:neutral)
42
- end
43
-
44
- it "allows to create standalone machine" do
45
- stub_const("Car", Class.new do
46
- def turn_reverse_lights_off
47
- @reverse_lights = false
48
- end
49
-
50
- def turn_reverse_lights_on
51
- @reverse_lights = true
52
- end
53
-
54
- def reverse_lights?
55
- @reverse_lights ||= false
56
- end
57
- end)
58
-
59
- car = Car.new
60
- engine = Engine.new
61
- engine.target car
62
- expect(engine.current).to eq(:neutral)
63
-
64
- engine.forward
65
- expect(engine.current).to eq(:one)
66
- expect(car.reverse_lights?).to eq(false)
67
-
68
- engine.back
69
- expect(engine.current).to eq(:reverse)
70
- expect(car.reverse_lights?).to eq(true)
71
- end
72
-
73
- it "supports inheritance of definitions" do
74
- class GenericStateMachine < FiniteMachine::Definition
75
- initial :red
76
-
77
- events {
78
- event :start, :red => :green
79
- }
80
-
81
- callbacks {
82
- on_enter { |event| target << 'generic' }
83
- }
84
- end
85
-
86
- class SpecificStateMachine < GenericStateMachine
87
- events {
88
- event :stop, :green => :yellow
89
- }
90
-
91
- callbacks {
92
- on_enter(:yellow) { |event| target << 'specific' }
93
- }
94
- end
95
-
96
- generic_fsm = GenericStateMachine.new
97
- specific_fsm = SpecificStateMachine.new
98
- called = []
99
- generic_fsm.target called
100
- specific_fsm.target called
101
-
102
- expect(generic_fsm.states).to match_array([:none, :red, :green])
103
- expect(specific_fsm.states).to match_array([:none, :red, :green, :yellow])
104
-
105
- expect(specific_fsm.current).to eq(:red)
106
-
107
- specific_fsm.start
108
- expect(specific_fsm.current).to eq(:green)
109
- expect(called).to match_array(['generic'])
110
-
111
- specific_fsm.stop
112
- expect(specific_fsm.current).to eq(:yellow)
113
- expect(called).to match_array(['generic', 'generic', 'specific'])
114
- end
115
- end
@@ -1,19 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, '.event_names' do
6
- it "retrieves all event names" do
7
- fsm = FiniteMachine.define do
8
- initial :green
9
-
10
- events {
11
- event :start, :red => :green
12
- event :stop, :green => :red
13
- }
14
- end
15
-
16
- expect(fsm.current).to eql(:green)
17
- expect(fsm.event_names).to eql([:init, :start, :stop])
18
- end
19
- end
@@ -1,52 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventQueue do
6
-
7
- subject(:event_queue) { described_class.new }
8
-
9
- it "dispatches all events" do
10
- called = []
11
- event1 = double(:event1, dispatch: called << 'event1_dispatched')
12
- event2 = double(:event2, dispatch: called << 'event2_dispatched')
13
- expect(event_queue.size).to be_zero
14
- event_queue << event1
15
- event_queue << event2
16
- event_queue.join(0.001)
17
- expect(called).to match_array(['event1_dispatched', 'event2_dispatched'])
18
- end
19
-
20
- it "logs error" do
21
- event = double(:event)
22
- expect(FiniteMachine::Logger).to receive(:error)
23
- event_queue << event
24
- event_queue.join(0.01)
25
- expect(event_queue).to be_empty
26
- end
27
-
28
- it "notifies listeners" do
29
- called = []
30
- event1 = double(:event1, dispatch: true)
31
- event2 = double(:event2, dispatch: true)
32
- event3 = double(:event3, dispatch: true)
33
- event_queue.subscribe(:listener1) { |event| called << event }
34
- event_queue << event1 << event2 << event3
35
- event_queue.join(0.02)
36
- event_queue.shutdown
37
- expect(called).to match_array([event1, event2, event3])
38
- end
39
-
40
- it "allows to shutdown event queue" do
41
- event1 = double(:event1, dispatch: true)
42
- event2 = double(:event2, dispatch: true)
43
- event3 = double(:event3, dispatch: true)
44
- expect(event_queue.alive?).to be(true)
45
- event_queue << event1
46
- event_queue << event2
47
- event_queue.shutdown
48
- event_queue << event3
49
- event_queue.join(0.001)
50
- expect(event_queue.alive?).to be(false)
51
- end
52
- end
@@ -1,25 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.add' do
6
- it "adds transitions" do
7
- transition = double(:transition)
8
- events_chain = described_class.new
9
-
10
- events_chain.add(:validated, transition)
11
- expect(events_chain[:validated]).to eq([transition])
12
-
13
- events_chain.add(:validated, transition)
14
- expect(events_chain[:validated]).to eq([transition, transition])
15
- end
16
-
17
- it "allows to chain add operations" do
18
- events_chain = described_class.new
19
- transition = double(:transition)
20
-
21
- events_chain.add(:go, transition).add(:start, transition)
22
-
23
- expect(events_chain.size).to eq(2)
24
- end
25
- end
@@ -1,22 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.cancel_transitions' do
6
- it "sets cancel status for chosen transitions" do
7
- events_chain = described_class.new
8
- transition_a = spy(:transition_a, cancelled: false)
9
- transition_b = spy(:transition_b, cancelled: false)
10
- transition_c = spy(:transition_c, cancelled: false)
11
-
12
- events_chain.add(:start, transition_a)
13
- events_chain.add(:start, transition_b)
14
- events_chain.add(:finish, transition_c)
15
-
16
- events_chain.cancel_transitions(:start)
17
-
18
- expect(transition_a).to have_received(:cancelled=).with(true)
19
- expect(transition_b).to have_received(:cancelled=).with(true)
20
- expect(transition_c).not_to have_received(:cancelled=)
21
- end
22
- end
@@ -1,28 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.choice_transition?' do
6
-
7
- it "checks if transition has many branches" do
8
- transition_a = double(:transition_a, matches?: true)
9
- transition_b = double(:transition_b, matches?: true)
10
-
11
- events_chain = described_class.new
12
- events_chain.add(:go, transition_a)
13
- events_chain.add(:go, transition_b)
14
-
15
- expect(events_chain.choice_transition?(:go, :green)).to eq(true)
16
- end
17
-
18
- it "checks that transition has no branches" do
19
- transition_a = double(:transition_a, matches?: false)
20
- transition_b = double(:transition_b, matches?: true)
21
-
22
- events_chain = described_class.new
23
- events_chain.add(:go, transition_a)
24
- events_chain.add(:go, transition_b)
25
-
26
- expect(events_chain.choice_transition?(:go, :green)).to eq(false)
27
- end
28
- end
@@ -1,15 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '#clear' do
6
- it "clears chain events" do
7
- event = double(:event)
8
- events_chain = described_class.new
9
- events_chain.add(:validated, event)
10
- expect(events_chain.empty?).to be(false)
11
-
12
- events_chain.clear
13
- expect(events_chain.empty?).to be(true)
14
- end
15
- end
@@ -1,18 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.events' do
6
- it "has no event names" do
7
- events_chain = described_class.new
8
- expect(events_chain.events).to eq([])
9
- end
10
-
11
- it "returns all event names" do
12
- events_chain = described_class.new
13
- transition = double(:transition)
14
- events_chain.add(:ready, transition)
15
- events_chain.add(:go, transition)
16
- expect(events_chain.events).to match_array([:ready, :go])
17
- end
18
- end
@@ -1,24 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '#inspect' do
6
- it "inspects empty events chain" do
7
- events_chain = described_class.new
8
- expect(events_chain.inspect).to eq("<#FiniteMachine::EventsChain @chain={}>")
9
- end
10
-
11
- it "inspect events chain" do
12
- transition = double(:transition)
13
- events_chain = described_class.new
14
- events_chain.add(:validated, transition)
15
- expect(events_chain.inspect).to eq("<#FiniteMachine::EventsChain @chain=#{{validated: [transition]}}>")
16
- end
17
-
18
- it "prints events chain" do
19
- transition = double(:transition)
20
- events_chain = described_class.new
21
- events_chain.add(:validated, transition)
22
- expect(events_chain.to_s).to eq("#{{validated: [transition]}}")
23
- end
24
- end
@@ -1,37 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.match_transition' do
6
- it "matches transition without conditions" do
7
- transition_a = double(:transition_a, matches?: false)
8
- transition_b = double(:transition_b, matches?: true)
9
- events_chain = described_class.new
10
-
11
- events_chain.add(:a, transition_a)
12
- events_chain.add(:a, transition_b)
13
-
14
- expect(events_chain.match_transition(:a, :green)).to eq(transition_b)
15
- end
16
-
17
- it "fails to match any transition" do
18
- events_chain = described_class.new
19
-
20
- expect(events_chain.match_transition(:a, :green)).to eq(nil)
21
- end
22
-
23
- it "matches transition with conditions" do
24
- transition_a = double(:transition_a, matches?: true)
25
- transition_b = double(:transition_b, matches?: true)
26
- events_chain = described_class.new
27
-
28
- events_chain.add(:a, transition_a)
29
- events_chain.add(:a, transition_b)
30
-
31
- allow(transition_a).to receive(:check_conditions).and_return(false)
32
- allow(transition_b).to receive(:check_conditions).and_return(true)
33
-
34
- expect(events_chain.match_transition_with(:a, :green, 'Piotr')).to eq(transition_b)
35
- expect(transition_a).to have_received(:check_conditions).with('Piotr')
36
- end
37
- end
@@ -1,48 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.move_to' do
6
-
7
- it "moves to state by matching individual transition" do
8
- transition_a = double(:transition_a, matches?: false)
9
- transition_b = double(:transition_b, matches?: true)
10
-
11
- events_chain = described_class.new
12
- events_chain.add(:go, transition_a)
13
- events_chain.add(:go, transition_b)
14
-
15
- allow(transition_b).to receive(:to_state).with(:yellow).and_return(:red)
16
-
17
- expect(events_chain.move_to(:go, :yellow)).to eq(:red)
18
- expect(transition_b).to have_received(:to_state).with(:yellow)
19
- end
20
-
21
- it "moves to state by matching choice transition" do
22
- transition_a = double(:transition_a, matches?: true)
23
- transition_b = double(:transition_b, matches?: true)
24
-
25
- events_chain = described_class.new
26
- events_chain.add(:go, transition_a)
27
- events_chain.add(:go, transition_b)
28
-
29
- allow(transition_a).to receive(:check_conditions).and_return(false)
30
- allow(transition_b).to receive(:check_conditions).and_return(true)
31
-
32
- allow(transition_b).to receive(:to_state).with(:green).and_return(:red)
33
-
34
- expect(events_chain.move_to(:go, :green)).to eq(:red)
35
- expect(transition_b).to have_received(:to_state).with(:green)
36
- end
37
-
38
- it "moves to from state if no transition available" do
39
- transition_a = double(:transition_a, matches?: false)
40
- transition_b = double(:transition_b, matches?: false)
41
-
42
- events_chain = described_class.new
43
- events_chain.add(:go, transition_a)
44
- events_chain.add(:go, transition_b)
45
-
46
- expect(events_chain.move_to(:go, :green)).to eq(:green)
47
- end
48
- end
@@ -1,17 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe FiniteMachine::EventsChain do
4
- it "finds current states for event name" do
5
- transition = spy(:transition, states: {:red => :yellow, :yellow => :green})
6
- events_chain = described_class.new
7
- events_chain.add(:start, transition)
8
-
9
- expect(events_chain.states_for(:start)).to eq([:red, :yellow])
10
- end
11
-
12
- it "fails to find any states for event name" do
13
- events_chain = described_class.new
14
-
15
- expect(events_chain.states_for(:start)).to eq([])
16
- end
17
- end
@@ -1,459 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'events' do
6
-
7
- it "allows for hash rocket syntax to describe transition" do
8
- fsm = FiniteMachine.define do
9
- initial :green
10
-
11
- events {
12
- event :slow, :green => :yellow
13
- event :stop, :yellow => :red
14
- }
15
- end
16
-
17
- expect(fsm.current).to eql(:green)
18
- fsm.slow
19
- expect(fsm.current).to eql(:yellow)
20
- fsm.stop
21
- expect(fsm.current).to eql(:red)
22
- end
23
-
24
- it "allows to add event without events scope" do
25
- fsm = FiniteMachine.define do
26
- initial :green
27
-
28
- event :slow, :green => :yellow
29
- event :stop, :yellow => :red
30
- end
31
-
32
- expect(fsm.current).to eql(:green)
33
- end
34
-
35
- it "allows for (:from | :to) key pairs to describe transition" do
36
- fsm = FiniteMachine.define do
37
- initial :green
38
-
39
- events {
40
- event :slow, from: :green, to: :yellow
41
- event :stop, from: :yellow, to: :red
42
- }
43
- end
44
-
45
- expect(fsm.current).to eql(:green)
46
- fsm.slow
47
- expect(fsm.current).to eql(:yellow)
48
- fsm.stop
49
- expect(fsm.current).to eql(:red)
50
- end
51
-
52
- it "permits no-op event without 'to' transition" do
53
- fsm = FiniteMachine.define do
54
- initial :green
55
-
56
- events {
57
- event :noop, from: :green
58
- event :slow, from: :green, to: :yellow
59
- event :stop, from: :yellow, to: :red
60
- event :ready, from: :red, to: :yellow
61
- event :go, from: :yellow, to: :green
62
- }
63
- end
64
-
65
- expect(fsm.current).to eql(:green)
66
-
67
- expect(fsm.can?(:noop)).to be true
68
- expect(fsm.can?(:slow)).to be true
69
-
70
- fsm.noop
71
- expect(fsm.current).to eql(:green)
72
- fsm.slow
73
- expect(fsm.current).to eql(:yellow)
74
-
75
- expect(fsm.cannot?(:noop)).to be true
76
- expect(fsm.cannot?(:slow)).to be true
77
- end
78
-
79
- it "permits event from any state with :any 'from'" do
80
- fsm = FiniteMachine.define do
81
- initial :green
82
-
83
- events {
84
- event :slow, from: :green, to: :yellow
85
- event :stop, from: :yellow, to: :red
86
- event :ready, from: :red, to: :yellow
87
- event :go, from: :yellow, to: :green
88
- event :run, from: :any, to: :green
89
- }
90
- end
91
-
92
- expect(fsm.current).to eql(:green)
93
-
94
- fsm.slow
95
- expect(fsm.current).to eql(:yellow)
96
- fsm.run
97
- expect(fsm.current).to eql(:green)
98
-
99
- fsm.slow
100
- expect(fsm.current).to eql(:yellow)
101
- fsm.stop
102
- expect(fsm.current).to eql(:red)
103
- fsm.run
104
- expect(fsm.current).to eql(:green)
105
-
106
- fsm.slow
107
- expect(fsm.current).to eql(:yellow)
108
- fsm.go
109
- expect(fsm.current).to eql(:green)
110
- fsm.run
111
- expect(fsm.current).to eql(:green)
112
- end
113
-
114
- it "permits event from any state for hash syntax" do
115
- fsm = FiniteMachine.define do
116
- initial :red
117
-
118
- events {
119
- event :start, :red => :yellow
120
- event :run, :yellow => :green
121
- event :stop, :green => :red
122
- event :go, :any => :green
123
- }
124
- end
125
-
126
- expect(fsm.current).to eql(:red)
127
-
128
- fsm.go
129
- expect(fsm.current).to eql(:green)
130
- fsm.stop
131
- fsm.start
132
- expect(fsm.current).to eql(:yellow)
133
- fsm.go
134
- expect(fsm.current).to eql(:green)
135
- end
136
-
137
- it "permits event from any state without 'from'" do
138
- fsm = FiniteMachine.define do
139
- initial :green
140
-
141
- events {
142
- event :slow, from: :green, to: :yellow
143
- event :stop, from: :yellow, to: :red
144
- event :ready, from: :red, to: :yellow
145
- event :go, from: :yellow, to: :green
146
- event :run, to: :green
147
- }
148
- end
149
-
150
- expect(fsm.current).to eql(:green)
151
-
152
- fsm.slow
153
- expect(fsm.current).to eql(:yellow)
154
- fsm.run
155
- expect(fsm.current).to eql(:green)
156
-
157
- fsm.slow
158
- expect(fsm.current).to eql(:yellow)
159
- fsm.stop
160
- expect(fsm.current).to eql(:red)
161
- fsm.run
162
- expect(fsm.current).to eql(:green)
163
-
164
- fsm.slow
165
- expect(fsm.current).to eql(:yellow)
166
- fsm.go
167
- expect(fsm.current).to eql(:green)
168
- fsm.run
169
- expect(fsm.current).to eql(:green)
170
- end
171
-
172
- it "doesn't raise error on invalid transition for non-dangerous version" do
173
- called = []
174
- fsm = FiniteMachine.define do
175
- initial :green
176
-
177
- events {
178
- event :stop, from: :yellow, to: :red
179
- }
180
- callbacks {
181
- on_before :stop do |event| called << 'on_before_stop' end
182
- on_after :stop do |event| called << 'on_before_stop' end
183
- }
184
- end
185
-
186
- expect(fsm.current).to eq(:green)
187
- expect(fsm.stop).to eq(false)
188
- expect(fsm.current).to eq(:green)
189
- expect(called).to match_array(['on_before_stop'])
190
- end
191
-
192
- context 'for non-dangerous version' do
193
- it "doesn't raise error on invalid transition and fires callbacks" do
194
- called = []
195
- fsm = FiniteMachine.define do
196
- initial :green
197
-
198
- events {
199
- event :stop, from: :yellow, to: :red
200
- }
201
- callbacks {
202
- on_before :stop do |event| called << 'on_before_stop' end
203
- on_after :stop do |event| called << 'on_before_stop' end
204
- }
205
- end
206
-
207
- expect(fsm.current).to eq(:green)
208
- expect(fsm.stop).to eq(false)
209
- expect(fsm.current).to eq(:green)
210
- expect(called).to match_array(['on_before_stop'])
211
- end
212
-
213
- it "raises error on invalid transition for dangerous version" do
214
- called = []
215
- fsm = FiniteMachine.define do
216
- initial :green
217
-
218
- events {
219
- event :slow, from: :green, to: :yellow
220
- event :stop, from: :yellow, to: :red, silent: true
221
- }
222
- callbacks {
223
- on_before :stop do |event| called << 'on_before_stop' end
224
- on_after :stop do |event| called << 'on_before_stop' end
225
- }
226
- end
227
-
228
- expect(fsm.current).to eql(:green)
229
- expect(fsm.stop).to eq(false)
230
- expect(called).to match_array([])
231
- end
232
- end
233
-
234
- context 'for dangerous version' do
235
- it "raises error on invalid transition without callbacks" do
236
- called = []
237
- fsm = FiniteMachine.define do
238
- initial :green
239
-
240
- events {
241
- event :start, :red => :yellow, silent: true
242
- }
243
- callbacks {
244
- on_before :start do |event| called << 'on_before_start' end
245
- on_after :start do |event| called << 'on_after_start' end
246
- }
247
- end
248
-
249
- expect(fsm.current).to eq(:green)
250
- expect { fsm.start! }.to raise_error(FiniteMachine::InvalidStateError)
251
- expect(called).to eq([])
252
- expect(fsm.current).to eq(:green)
253
- end
254
-
255
- it "raises error on invalid transition with callbacks fired" do
256
- called = []
257
- fsm = FiniteMachine.define do
258
- initial :green
259
-
260
- events {
261
- event :start, :red => :yellow
262
- }
263
- callbacks {
264
- on_before :start do |event| called << 'on_before_start' end
265
- on_after :start do |event| called << 'on_after_start' end
266
- }
267
- end
268
-
269
- expect(fsm.current).to eq(:green)
270
- expect { fsm.start! }.to raise_error(FiniteMachine::InvalidStateError,
271
- /inappropriate current state 'green'/)
272
- expect(called).to eq(['on_before_start'])
273
- expect(fsm.current).to eq(:green)
274
- end
275
- end
276
-
277
- context 'when multiple from states' do
278
- it "allows for array from key" do
279
- fsm = FiniteMachine.define do
280
- initial :green
281
-
282
- events {
283
- event :slow, :green => :yellow
284
- event :stop, [:green, :yellow] => :red
285
- event :ready, :red => :yellow
286
- event :go, [:yellow, :red] => :green
287
- }
288
- end
289
-
290
- expect(fsm.current).to eql(:green)
291
-
292
- expect(fsm.can?(:slow)).to be true
293
- expect(fsm.can?(:stop)).to be true
294
- expect(fsm.cannot?(:ready)).to be true
295
- expect(fsm.cannot?(:go)).to be true
296
-
297
- fsm.slow; expect(fsm.current).to eql(:yellow)
298
- fsm.stop; expect(fsm.current).to eql(:red)
299
- fsm.ready; expect(fsm.current).to eql(:yellow)
300
- fsm.go; expect(fsm.current).to eql(:green)
301
-
302
- fsm.stop; expect(fsm.current).to eql(:red)
303
- fsm.go; expect(fsm.current).to eql(:green)
304
- end
305
-
306
- it "allows for hash of states" do
307
- fsm = FiniteMachine.define do
308
- initial :green
309
-
310
- events {
311
- event :slow, :green => :yellow
312
- event :stop, :green => :red, :yellow => :red
313
- event :ready, :red => :yellow
314
- event :go, :yellow => :green, :red => :green
315
- }
316
- end
317
-
318
- expect(fsm.current).to eql(:green)
319
-
320
- expect(fsm.can?(:slow)).to be true
321
- expect(fsm.can?(:stop)).to be true
322
- expect(fsm.cannot?(:ready)).to be true
323
- expect(fsm.cannot?(:go)).to be true
324
-
325
- fsm.slow; expect(fsm.current).to eql(:yellow)
326
- fsm.stop; expect(fsm.current).to eql(:red)
327
- fsm.ready; expect(fsm.current).to eql(:yellow)
328
- fsm.go; expect(fsm.current).to eql(:green)
329
-
330
- fsm.stop; expect(fsm.current).to eql(:red)
331
- fsm.go; expect(fsm.current).to eql(:green)
332
- end
333
- end
334
-
335
- it "groups events with the same name" do
336
- fsm = FiniteMachine.define do
337
- initial :green
338
-
339
- events {
340
- event :stop, :green => :yellow
341
- event :stop, :yellow => :red
342
- event :stop, :red => :pink
343
- event :cycle, [:yellow, :red, :pink] => :green
344
- }
345
- end
346
-
347
- expect(fsm.current).to eql(:green)
348
- expect(fsm.can?(:stop)).to be true
349
- fsm.stop
350
- expect(fsm.current).to eql(:yellow)
351
- fsm.stop
352
- expect(fsm.current).to eql(:red)
353
- fsm.stop
354
- expect(fsm.current).to eql(:pink)
355
- fsm.cycle
356
- expect(fsm.current).to eql(:green)
357
- fsm.stop
358
- expect(fsm.current).to eql(:yellow)
359
- end
360
-
361
- it "groups transitions under one event name" do
362
- fsm = FiniteMachine.define do
363
- initial :initial
364
-
365
- events {
366
- event :bump, :initial => :low,
367
- :low => :medium,
368
- :medium => :high
369
- }
370
- end
371
-
372
- expect(fsm.current).to eq(:initial)
373
- fsm.bump; expect(fsm.current).to eq(:low)
374
- fsm.bump; expect(fsm.current).to eq(:medium)
375
- fsm.bump; expect(fsm.current).to eq(:high)
376
- end
377
-
378
- it "returns values for events" do
379
- fsm = FiniteMachine.define do
380
- initial :neutral
381
-
382
- events {
383
- event :start, :neutral => :engine_on
384
- event :drive, :engine_on => :running, if: -> { return false }
385
- event :stop, :any => :neutral
386
- }
387
-
388
- callbacks {
389
- on_before(:drive) { FiniteMachine::CANCELLED }
390
- on_after(:stop) { }
391
- }
392
- end
393
-
394
- expect(fsm.current).to eql(:neutral)
395
- expect(fsm.start).to eql(true)
396
- expect(fsm.drive).to eql(false)
397
- expect(fsm.stop).to eql(true)
398
- expect(fsm.stop).to eql(true)
399
- end
400
-
401
- it "allows for self transition events" do
402
- digits = []
403
- callbacks = []
404
- phone = FiniteMachine.define do
405
- initial :on_hook
406
-
407
- events {
408
- event :digit, :on_hook => :dialing
409
- event :digit, :dialing => :dialing
410
- event :off_hook, :dialing => :alerting
411
- }
412
-
413
- callbacks {
414
- on_before_digit { |event, digit| digits << digit}
415
- on_before_off_hook { |event| callbacks << "dialing #{digits.join}" }
416
- }
417
- end
418
-
419
- expect(phone.current).to eq(:on_hook)
420
- phone.digit(9)
421
- expect(phone.current).to eq(:dialing)
422
- phone.digit(1)
423
- expect(phone.current).to eq(:dialing)
424
- phone.digit(1)
425
- expect(phone.current).to eq(:dialing)
426
- phone.off_hook
427
- expect(phone.current).to eq(:alerting)
428
- expect(digits).to match_array(digits)
429
- expect(callbacks).to match_array(["dialing 911"])
430
- end
431
-
432
- it "detects dangerous event names" do
433
- expect {
434
- FiniteMachine.define do
435
- events {
436
- event :trigger, :a => :b
437
- }
438
- end
439
- }.to raise_error(FiniteMachine::AlreadyDefinedError)
440
- end
441
-
442
- it "executes event block" do
443
- fsm = FiniteMachine.define do
444
- initial :red
445
-
446
- events {
447
- event :start, :red => :green
448
- event :stop, :green => :red
449
- }
450
- end
451
-
452
- expect(fsm.current).to eq(:red)
453
- called = []
454
- fsm.start do |from, to|
455
- called << "execute_start_#{from}_#{to}"
456
- end
457
- expect(called).to eq(['execute_start_red_green'])
458
- end
459
- end