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,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