finite_machine 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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