finite_machine 0.11.3 → 0.12.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 (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +564 -569
  4. data/Rakefile +5 -1
  5. data/benchmarks/memory_profile.rb +11 -0
  6. data/benchmarks/memory_usage.rb +16 -9
  7. data/finite_machine.gemspec +10 -3
  8. data/lib/finite_machine.rb +34 -46
  9. data/lib/finite_machine/async_call.rb +5 -21
  10. data/lib/finite_machine/callable.rb +4 -4
  11. data/lib/finite_machine/catchable.rb +4 -2
  12. data/lib/finite_machine/choice_merger.rb +19 -19
  13. data/lib/finite_machine/const.rb +16 -0
  14. data/lib/finite_machine/definition.rb +2 -2
  15. data/lib/finite_machine/dsl.rb +66 -149
  16. data/lib/finite_machine/env.rb +4 -2
  17. data/lib/finite_machine/event_definition.rb +7 -15
  18. data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
  19. data/lib/finite_machine/hook_event.rb +60 -61
  20. data/lib/finite_machine/hooks.rb +44 -36
  21. data/lib/finite_machine/listener.rb +2 -2
  22. data/lib/finite_machine/logger.rb +5 -4
  23. data/lib/finite_machine/message_queue.rb +39 -30
  24. data/lib/finite_machine/observer.rb +55 -37
  25. data/lib/finite_machine/safety.rb +12 -10
  26. data/lib/finite_machine/state_definition.rb +3 -5
  27. data/lib/finite_machine/state_machine.rb +83 -64
  28. data/lib/finite_machine/state_parser.rb +51 -79
  29. data/lib/finite_machine/subscribers.rb +1 -1
  30. data/lib/finite_machine/threadable.rb +3 -1
  31. data/lib/finite_machine/transition.rb +30 -31
  32. data/lib/finite_machine/transition_builder.rb +23 -32
  33. data/lib/finite_machine/transition_event.rb +12 -11
  34. data/lib/finite_machine/two_phase_lock.rb +3 -1
  35. data/lib/finite_machine/undefined_transition.rb +5 -6
  36. data/lib/finite_machine/version.rb +2 -2
  37. data/spec/integration/system_spec.rb +36 -38
  38. data/spec/performance/benchmark_spec.rb +13 -21
  39. data/spec/unit/alias_target_spec.rb +22 -41
  40. data/spec/unit/async_callbacks_spec.rb +8 -13
  41. data/spec/unit/auto_methods_spec.rb +44 -0
  42. data/spec/unit/callable/call_spec.rb +1 -3
  43. data/spec/unit/callbacks_spec.rb +372 -463
  44. data/spec/unit/can_spec.rb +13 -23
  45. data/spec/unit/cancel_callbacks_spec.rb +46 -0
  46. data/spec/unit/choice_spec.rb +105 -141
  47. data/spec/unit/define_spec.rb +31 -31
  48. data/spec/unit/definition_spec.rb +24 -41
  49. data/spec/unit/event_names_spec.rb +6 -10
  50. data/spec/unit/events_map/add_spec.rb +23 -0
  51. data/spec/unit/events_map/choice_transition_spec.rb +25 -0
  52. data/spec/unit/events_map/clear_spec.rb +13 -0
  53. data/spec/unit/events_map/events_spec.rb +16 -0
  54. data/spec/unit/events_map/inspect_spec.rb +22 -0
  55. data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
  56. data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
  57. data/spec/unit/events_map/states_for_spec.rb +17 -0
  58. data/spec/unit/events_spec.rb +91 -160
  59. data/spec/unit/handlers_spec.rb +34 -66
  60. data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
  61. data/spec/unit/hook_event/build_spec.rb +1 -3
  62. data/spec/unit/hook_event/eql_spec.rb +1 -3
  63. data/spec/unit/hook_event/initialize_spec.rb +2 -4
  64. data/spec/unit/hook_event/notify_spec.rb +2 -4
  65. data/spec/unit/hooks/clear_spec.rb +1 -1
  66. data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
  67. data/spec/unit/hooks/inspect_spec.rb +16 -8
  68. data/spec/unit/hooks/register_spec.rb +4 -9
  69. data/spec/unit/if_unless_spec.rb +76 -115
  70. data/spec/unit/initial_spec.rb +50 -82
  71. data/spec/unit/inspect_spec.rb +14 -9
  72. data/spec/unit/is_spec.rb +12 -18
  73. data/spec/unit/log_transitions_spec.rb +4 -10
  74. data/spec/unit/logger_spec.rb +1 -3
  75. data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
  76. data/spec/unit/new_spec.rb +50 -0
  77. data/spec/unit/respond_to_spec.rb +2 -6
  78. data/spec/unit/state_parser/parse_spec.rb +9 -12
  79. data/spec/unit/states_spec.rb +12 -18
  80. data/spec/unit/subscribers_spec.rb +1 -3
  81. data/spec/unit/target_spec.rb +60 -93
  82. data/spec/unit/terminated_spec.rb +15 -25
  83. data/spec/unit/transition/check_conditions_spec.rb +16 -15
  84. data/spec/unit/transition/inspect_spec.rb +6 -6
  85. data/spec/unit/transition/matches_spec.rb +5 -7
  86. data/spec/unit/transition/states_spec.rb +5 -7
  87. data/spec/unit/transition/to_state_spec.rb +5 -13
  88. data/spec/unit/trigger_spec.rb +5 -9
  89. data/spec/unit/undefined_transition/eql_spec.rb +1 -3
  90. metadata +86 -49
  91. data/.gitignore +0 -18
  92. data/.rspec +0 -5
  93. data/.travis.yml +0 -27
  94. data/Gemfile +0 -16
  95. data/assets/finite_machine_logo.png +0 -0
  96. data/lib/finite_machine/async_proxy.rb +0 -55
  97. data/spec/unit/async_events_spec.rb +0 -107
  98. data/spec/unit/events_chain/add_spec.rb +0 -25
  99. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  100. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  101. data/spec/unit/events_chain/clear_spec.rb +0 -15
  102. data/spec/unit/events_chain/events_spec.rb +0 -18
  103. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  104. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  105. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  106. data/spec/unit/state_parser/inspect_spec.rb +0 -25
@@ -1,49 +1,49 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'define' do
1
+ # frozen_string_literal: true
6
2
 
3
+ RSpec.describe FiniteMachine, '.define' do
7
4
  context 'with block' do
8
5
  it "creates system state machine" do
9
- fsm = FiniteMachine.define do
6
+ stub_const("TrafficLights", FiniteMachine.define do
10
7
  initial :green
11
8
 
12
- events {
13
- event :slow, :green => :yellow
14
- event :stop, :yellow => :red
15
- event :ready, :red => :yellow
16
- event :go, :yellow => :green
17
- }
18
- end
9
+ event :slow, :green => :yellow
10
+ event :stop, :yellow => :red
11
+ event :ready, :red => :yellow
12
+ event :go, :yellow => :green
13
+ end)
19
14
 
20
- expect(fsm.current).to eql(:green)
15
+ lights_fsm_a = TrafficLights.new
16
+ lights_fsm_b = TrafficLights.new
21
17
 
22
- fsm.slow
23
- expect(fsm.current).to eql(:yellow)
24
- fsm.stop
25
- expect(fsm.current).to eql(:red)
26
- fsm.ready
27
- expect(fsm.current).to eql(:yellow)
28
- fsm.go
29
- expect(fsm.current).to eql(:green)
18
+ expect(lights_fsm_a.current).to eql(:green)
19
+ expect(lights_fsm_b.current).to eql(:green)
20
+
21
+ lights_fsm_a.slow
22
+ expect(lights_fsm_a.current).to eql(:yellow)
23
+ expect(lights_fsm_b.current).to eql(:green)
24
+
25
+ lights_fsm_a.stop
26
+ expect(lights_fsm_a.current).to eql(:red)
27
+ expect(lights_fsm_b.current).to eql(:green)
30
28
  end
31
29
  end
32
30
 
33
31
  context 'without block' do
34
32
  it "creates state machine" do
35
33
  called = []
36
- fsm = FiniteMachine.define
37
- fsm.initial(:green)
38
- fsm.event(:slow, :green => :yellow)
39
- fsm.event(:stop, :yellow => :red)
40
- fsm.event(:ready,:red => :yellow)
41
- fsm.event(:go, :yellow => :green)
42
- fsm.on_enter(:yellow) { |event| called << 'on_enter_yellow' }
43
- fsm.handle(FiniteMachine::InvalidStateError) { |exception|
34
+ stub_const("TrafficLights", FiniteMachine.define)
35
+ TrafficLights.initial(:green)
36
+ TrafficLights.event(:slow, :green => :yellow)
37
+ TrafficLights.event(:stop, :yellow => :red)
38
+ TrafficLights.event(:ready,:red => :yellow)
39
+ TrafficLights.event(:go, :yellow => :green)
40
+ TrafficLights.on_enter(:yellow) { |event| called << 'on_enter_yellow' }
41
+ TrafficLights.handle(FiniteMachine::InvalidStateError) { |exception|
44
42
  called << 'error_handler'
45
43
  }
46
- fsm.init
44
+
45
+ fsm = TrafficLights.new
46
+
47
47
  expect(fsm.current).to eql(:green)
48
48
  fsm.slow
49
49
  expect(fsm.current).to eql(:yellow)
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine::Definition, 'definition' do
6
4
 
@@ -8,26 +6,20 @@ RSpec.describe FiniteMachine::Definition, 'definition' do
8
6
  class Engine < FiniteMachine::Definition
9
7
  initial :neutral
10
8
 
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
- }
9
+ event :forward, [:reverse, :neutral] => :one
10
+ event :shift, :one => :two
11
+ event :shift, :two => :one
12
+ event :back, [:neutral, :one] => :reverse
13
+
14
+ on_enter :reverse do |event|
15
+ target.turn_reverse_lights_on
16
+ end
17
+
18
+ on_exit :reverse do |event|
19
+ target.turn_reverse_lights_off
20
+ end
21
+
22
+ handle FiniteMachine::InvalidStateError do |exception| end
31
23
  end
32
24
  end
33
25
 
@@ -36,6 +28,8 @@ RSpec.describe FiniteMachine::Definition, 'definition' do
36
28
  engine_b = Engine.new
37
29
  expect(engine_a).not_to be(engine_b)
38
30
 
31
+ expect(engine_a.current).to eq(:neutral)
32
+
39
33
  engine_a.forward
40
34
  expect(engine_a.current).to eq(:one)
41
35
  expect(engine_b.current).to eq(:neutral)
@@ -57,8 +51,7 @@ RSpec.describe FiniteMachine::Definition, 'definition' do
57
51
  end)
58
52
 
59
53
  car = Car.new
60
- engine = Engine.new
61
- engine.target car
54
+ engine = Engine.new(car)
62
55
  expect(engine.current).to eq(:neutral)
63
56
 
64
57
  engine.forward
@@ -74,30 +67,20 @@ RSpec.describe FiniteMachine::Definition, 'definition' do
74
67
  class GenericStateMachine < FiniteMachine::Definition
75
68
  initial :red
76
69
 
77
- events {
78
- event :start, :red => :green
79
- }
70
+ event :start, :red => :green
80
71
 
81
- callbacks {
82
- on_enter { |event| target << 'generic' }
83
- }
72
+ on_enter { |event| target << 'generic' }
84
73
  end
85
74
 
86
75
  class SpecificStateMachine < GenericStateMachine
87
- events {
88
- event :stop, :green => :yellow
89
- }
76
+ event :stop, :green => :yellow
90
77
 
91
- callbacks {
92
- on_enter(:yellow) { |event| target << 'specific' }
93
- }
78
+ on_enter(:yellow) { |event| target << 'specific' }
94
79
  end
95
80
 
96
- generic_fsm = GenericStateMachine.new
97
- specific_fsm = SpecificStateMachine.new
98
81
  called = []
99
- generic_fsm.target called
100
- specific_fsm.target called
82
+ generic_fsm = GenericStateMachine.new(called)
83
+ specific_fsm = SpecificStateMachine.new(called)
101
84
 
102
85
  expect(generic_fsm.states).to match_array([:none, :red, :green])
103
86
  expect(specific_fsm.states).to match_array([:none, :red, :green, :yellow])
@@ -1,19 +1,15 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, '.event_names' do
3
+ RSpec.describe FiniteMachine, '#events' do
6
4
  it "retrieves all event names" do
7
- fsm = FiniteMachine.define do
5
+ fsm = FiniteMachine.new do
8
6
  initial :green
9
7
 
10
- events {
11
- event :start, :red => :green
12
- event :stop, :green => :red
13
- }
8
+ event :start, :red => :green
9
+ event :stop, :green => :red
14
10
  end
15
11
 
16
12
  expect(fsm.current).to eql(:green)
17
- expect(fsm.event_names).to eql([:init, :start, :stop])
13
+ expect(fsm.events).to match_array([:init, :start, :stop])
18
14
  end
19
15
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine::EventsMap, '#add' do
4
+ it "adds transitions" do
5
+ transition = double(:transition)
6
+ events_map = described_class.new
7
+
8
+ events_map.add(:validated, transition)
9
+ expect(events_map[:validated]).to eq([transition])
10
+
11
+ events_map.add(:validated, transition)
12
+ expect(events_map[:validated]).to eq([transition, transition])
13
+ end
14
+
15
+ it "allows to map add operations" do
16
+ events_map = described_class.new
17
+ transition = double(:transition)
18
+
19
+ events_map.add(:go, transition).add(:start, transition)
20
+
21
+ expect(events_map.size).to eq(2)
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine::EventsMap, '#choice_transition?' do
4
+ it "checks if transition has many branches" do
5
+ transition_a = double(:transition_a, matches?: true)
6
+ transition_b = double(:transition_b, matches?: true)
7
+
8
+ events_map = described_class.new
9
+ events_map.add(:go, transition_a)
10
+ events_map.add(:go, transition_b)
11
+
12
+ expect(events_map.choice_transition?(:go, :green)).to eq(true)
13
+ end
14
+
15
+ it "checks that transition has no branches" do
16
+ transition_a = double(:transition_a, matches?: false)
17
+ transition_b = double(:transition_b, matches?: true)
18
+
19
+ events_map = described_class.new
20
+ events_map.add(:go, transition_a)
21
+ events_map.add(:go, transition_b)
22
+
23
+ expect(events_map.choice_transition?(:go, :green)).to eq(false)
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine::EventsMap, '#clear' do
4
+ it "clears map events" do
5
+ event = double(:event)
6
+ events_map = described_class.new
7
+ events_map.add(:validated, event)
8
+ expect(events_map.empty?).to be(false)
9
+
10
+ events_map.clear
11
+ expect(events_map.empty?).to be(true)
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine::EventsMap, '#events' do
4
+ it "has no event names" do
5
+ events_map = described_class.new
6
+ expect(events_map.events).to eq([])
7
+ end
8
+
9
+ it "returns all event names" do
10
+ events_map = described_class.new
11
+ transition = double(:transition)
12
+ events_map.add(:ready, transition)
13
+ events_map.add(:go, transition)
14
+ expect(events_map.events).to match_array([:ready, :go])
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine::EventsMap, '#inspect' do
4
+ it "inspects empty events map" do
5
+ events_map = described_class.new
6
+ expect(events_map.inspect).to eq("<#FiniteMachine::EventsMap @events_map={}>")
7
+ end
8
+
9
+ it "inspect events map" do
10
+ transition = double(:transition)
11
+ events_map = described_class.new
12
+ events_map.add(:validated, transition)
13
+ expect(events_map.inspect).to eq("<#FiniteMachine::EventsMap @events_map=#{{validated: [transition]}}>")
14
+ end
15
+
16
+ it "prints events map" do
17
+ transition = double(:transition)
18
+ events_map = described_class.new
19
+ events_map.add(:validated, transition)
20
+ expect(events_map.to_s).to eq("#{{validated: [transition]}}")
21
+ end
22
+ end
@@ -1,37 +1,35 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.match_transition' do
3
+ RSpec.describe FiniteMachine::EventsMap, '#match_transition' do
6
4
  it "matches transition without conditions" do
7
5
  transition_a = double(:transition_a, matches?: false)
8
6
  transition_b = double(:transition_b, matches?: true)
9
- events_chain = described_class.new
7
+ events_map = described_class.new
10
8
 
11
- events_chain.add(:a, transition_a)
12
- events_chain.add(:a, transition_b)
9
+ events_map.add(:a, transition_a)
10
+ events_map.add(:a, transition_b)
13
11
 
14
- expect(events_chain.match_transition(:a, :green)).to eq(transition_b)
12
+ expect(events_map.match_transition(:a, :green)).to eq(transition_b)
15
13
  end
16
14
 
17
15
  it "fails to match any transition" do
18
- events_chain = described_class.new
16
+ events_map = described_class.new
19
17
 
20
- expect(events_chain.match_transition(:a, :green)).to eq(nil)
18
+ expect(events_map.match_transition(:a, :green)).to eq(nil)
21
19
  end
22
20
 
23
21
  it "matches transition with conditions" do
24
22
  transition_a = double(:transition_a, matches?: true)
25
23
  transition_b = double(:transition_b, matches?: true)
26
- events_chain = described_class.new
24
+ events_map = described_class.new
27
25
 
28
- events_chain.add(:a, transition_a)
29
- events_chain.add(:a, transition_b)
26
+ events_map.add(:a, transition_a)
27
+ events_map.add(:a, transition_b)
30
28
 
31
29
  allow(transition_a).to receive(:check_conditions).and_return(false)
32
30
  allow(transition_b).to receive(:check_conditions).and_return(true)
33
31
 
34
- expect(events_chain.match_transition_with(:a, :green, 'Piotr')).to eq(transition_b)
32
+ expect(events_map.match_transition_with(:a, :green, 'Piotr')).to eq(transition_b)
35
33
  expect(transition_a).to have_received(:check_conditions).with('Piotr')
36
34
  end
37
35
  end
@@ -1,20 +1,17 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::EventsChain, '.move_to' do
1
+ # frozen_string_literal: true
6
2
 
3
+ RSpec.describe FiniteMachine::EventsMap, '#move_to' do
7
4
  it "moves to state by matching individual transition" do
8
5
  transition_a = double(:transition_a, matches?: false)
9
6
  transition_b = double(:transition_b, matches?: true)
10
7
 
11
- events_chain = described_class.new
12
- events_chain.add(:go, transition_a)
13
- events_chain.add(:go, transition_b)
8
+ events_map = described_class.new
9
+ events_map.add(:go, transition_a)
10
+ events_map.add(:go, transition_b)
14
11
 
15
12
  allow(transition_b).to receive(:to_state).with(:yellow).and_return(:red)
16
13
 
17
- expect(events_chain.move_to(:go, :yellow)).to eq(:red)
14
+ expect(events_map.move_to(:go, :yellow)).to eq(:red)
18
15
  expect(transition_b).to have_received(:to_state).with(:yellow)
19
16
  end
20
17
 
@@ -22,16 +19,16 @@ RSpec.describe FiniteMachine::EventsChain, '.move_to' do
22
19
  transition_a = double(:transition_a, matches?: true)
23
20
  transition_b = double(:transition_b, matches?: true)
24
21
 
25
- events_chain = described_class.new
26
- events_chain.add(:go, transition_a)
27
- events_chain.add(:go, transition_b)
22
+ events_map = described_class.new
23
+ events_map.add(:go, transition_a)
24
+ events_map.add(:go, transition_b)
28
25
 
29
26
  allow(transition_a).to receive(:check_conditions).and_return(false)
30
27
  allow(transition_b).to receive(:check_conditions).and_return(true)
31
28
 
32
29
  allow(transition_b).to receive(:to_state).with(:green).and_return(:red)
33
30
 
34
- expect(events_chain.move_to(:go, :green)).to eq(:red)
31
+ expect(events_map.move_to(:go, :green)).to eq(:red)
35
32
  expect(transition_b).to have_received(:to_state).with(:green)
36
33
  end
37
34
 
@@ -39,10 +36,10 @@ RSpec.describe FiniteMachine::EventsChain, '.move_to' do
39
36
  transition_a = double(:transition_a, matches?: false)
40
37
  transition_b = double(:transition_b, matches?: false)
41
38
 
42
- events_chain = described_class.new
43
- events_chain.add(:go, transition_a)
44
- events_chain.add(:go, transition_b)
39
+ events_map = described_class.new
40
+ events_map.add(:go, transition_a)
41
+ events_map.add(:go, transition_b)
45
42
 
46
- expect(events_chain.move_to(:go, :green)).to eq(:green)
43
+ expect(events_map.move_to(:go, :green)).to eq(:green)
47
44
  end
48
45
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine::EventsMap do
4
+ it "finds current states for event name" do
5
+ transition = spy(:transition, states: {:red => :yellow, :yellow => :green})
6
+ events_map = described_class.new
7
+ events_map.add(:start, transition)
8
+
9
+ expect(events_map.states_for(:start)).to eq([:red, :yellow])
10
+ end
11
+
12
+ it "fails to find any states for event name" do
13
+ events_map = described_class.new
14
+
15
+ expect(events_map.states_for(:start)).to eq([])
16
+ end
17
+ end