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,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine, '#respond_to' do
6
4
 
@@ -14,9 +12,7 @@ RSpec.describe FiniteMachine, '#respond_to' do
14
12
  FiniteMachine.new target: Car.new do
15
13
  initial :green
16
14
 
17
- events {
18
- event :slow, :green => :yellow
19
- }
15
+ event :slow, :green => :yellow
20
16
  end
21
17
  }
22
18
 
@@ -1,19 +1,16 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine::StateParser, '#parse' do
6
4
  let(:object) { described_class }
7
5
 
8
- subject(:parser) { object.new(attrs) }
9
-
10
6
  context 'when no attributes' do
11
7
  let(:attrs) { { } }
12
8
 
13
9
  it "raises error for no transitions" do
14
10
  expect {
15
- parser.parse
16
- }.to raise_error(FiniteMachine::NotEnoughTransitionsError)
11
+ object.parse(attrs)
12
+ }.to raise_error(FiniteMachine::NotEnoughTransitionsError,
13
+ /please provide state transitions/)
17
14
  end
18
15
  end
19
16
 
@@ -21,7 +18,7 @@ RSpec.describe FiniteMachine::StateParser, '#parse' do
21
18
  let(:attrs) { { from: :green, to: :yellow }}
22
19
 
23
20
  it "removes :from and :to keys" do
24
- expect(parser.parse).to eq({green: :yellow})
21
+ expect(object.parse(attrs)).to eq({green: :yellow})
25
22
  end
26
23
  end
27
24
 
@@ -29,7 +26,7 @@ RSpec.describe FiniteMachine::StateParser, '#parse' do
29
26
  let(:attrs) { { from: :green }}
30
27
 
31
28
  it "adds to state as copy of from" do
32
- expect(parser.parse).to eq({green: :green})
29
+ expect(object.parse(attrs)).to eq({green: :green})
33
30
  end
34
31
  end
35
32
 
@@ -37,7 +34,7 @@ RSpec.describe FiniteMachine::StateParser, '#parse' do
37
34
  let(:attrs) { { to: :green }}
38
35
 
39
36
  it "inserts :any from state" do
40
- expect(parser.parse).to eq({any: :green})
37
+ expect(object.parse(attrs)).to eq({FiniteMachine::ANY_STATE => :green})
41
38
  end
42
39
  end
43
40
 
@@ -45,7 +42,7 @@ RSpec.describe FiniteMachine::StateParser, '#parse' do
45
42
  let(:attrs) { { green: :yellow } }
46
43
 
47
44
  it "copies attributes over" do
48
- expect(parser.parse).to eq({green: :yellow})
45
+ expect(object.parse(attrs)).to eq({green: :yellow})
49
46
  end
50
47
  end
51
48
 
@@ -53,7 +50,7 @@ RSpec.describe FiniteMachine::StateParser, '#parse' do
53
50
  let(:attrs) { { [:green, :red] => :yellow } }
54
51
 
55
52
  it "extracts states" do
56
- expect(parser.parse).to include({red: :yellow, green: :yellow})
53
+ expect(object.parse(attrs)).to include({red: :yellow, green: :yellow})
57
54
  end
58
55
  end
59
56
  end
@@ -1,33 +1,27 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'states' do
3
+ RSpec.describe FiniteMachine, '#states' do
6
4
  it "retrieves all available states" do
7
- fsm = FiniteMachine.define do
5
+ fsm = FiniteMachine.new do
8
6
  initial :green
9
7
 
10
- events {
11
- event :slow, :green => :yellow
12
- event :stop, :yellow => :red
13
- event :ready, :red => :yellow
14
- event :go, :yellow => :green
15
- }
8
+ event :slow, :green => :yellow
9
+ event :stop, :yellow => :red
10
+ event :ready, :red => :yellow
11
+ event :go, :yellow => :green
16
12
  end
17
13
 
18
14
  expect(fsm.states).to match_array([:none, :green, :yellow, :red])
19
15
  end
20
16
 
21
17
  it "retrieves all unique states for choice transition" do
22
- fsm = FiniteMachine.define do
18
+ fsm = FiniteMachine.new do
23
19
  initial :green
24
20
 
25
- events {
26
- event :next, from: :green do
27
- choice :yellow, if: -> { false }
28
- choice :red, if: -> { true }
29
- end
30
- }
21
+ event :next, from: :green do
22
+ choice :yellow, if: -> { false }
23
+ choice :red, if: -> { true }
24
+ end
31
25
  end
32
26
  expect(fsm.states).to match_array([:none, :green, :yellow, :red])
33
27
  end
@@ -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::Subscribers do
6
4
  let(:listener) { double }
@@ -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, '#target' do
6
4
  it "allows to target external object" do
@@ -20,28 +18,21 @@ RSpec.describe FiniteMachine, '#target' do
20
18
  end
21
19
 
22
20
  def engine
23
- context = self
24
- @engine ||= FiniteMachine.define do
21
+ @engine ||= FiniteMachine.new(self) do
25
22
  initial :neutral
26
23
 
27
- target context
28
-
29
- events {
30
- event :forward, [:reverse, :neutral] => :one
31
- event :shift, :one => :two
32
- event :shift, :two => :one
33
- event :back, [:neutral, :one] => :reverse
34
- }
24
+ event :forward, [:reverse, :neutral] => :one
25
+ event :shift, :one => :two
26
+ event :shift, :two => :one
27
+ event :back, [:neutral, :one] => :reverse
35
28
 
36
- callbacks {
37
- on_enter :reverse do |event|
38
- target.turn_reverse_lights_on
39
- end
29
+ on_enter :reverse do |event|
30
+ target.turn_reverse_lights_on
31
+ end
40
32
 
41
- on_exit :reverse do |event|
42
- target.turn_reverse_lights_off
43
- end
44
- }
33
+ on_exit :reverse do |event|
34
+ target.turn_reverse_lights_off
35
+ end
45
36
  end
46
37
  end
47
38
  end)
@@ -57,17 +48,14 @@ RSpec.describe FiniteMachine, '#target' do
57
48
  end
58
49
 
59
50
  it "propagates method call" do
60
- fsm = FiniteMachine.define do
51
+ fsm = FiniteMachine.new do
61
52
  initial :green
62
- events {
63
- event :slow, :green => :yellow
64
- }
65
53
 
66
- callbacks {
67
- on_enter_yellow do |event|
68
- uknown_method
69
- end
70
- }
54
+ event :slow, :green => :yellow
55
+
56
+ on_enter_yellow do |event|
57
+ uknown_method
58
+ end
71
59
  end
72
60
  expect(fsm.current).to eql(:green)
73
61
  expect { fsm.slow }.to raise_error(StandardError)
@@ -75,25 +63,21 @@ RSpec.describe FiniteMachine, '#target' do
75
63
 
76
64
  it "references machine methods inside callback" do
77
65
  called = []
78
- fsm = FiniteMachine.define do
66
+ fsm = FiniteMachine.new do
79
67
  initial :green
80
68
 
81
- events {
82
- event :slow, :green => :yellow
83
- event :stop, :yellow => :red
84
- event :ready, :red => :yellow
85
- event :go, :yellow => :green
86
- }
69
+ event :slow, :green => :yellow
70
+ event :stop, :yellow => :red
71
+ event :ready, :red => :yellow
72
+ event :go, :yellow => :green
87
73
 
88
- callbacks {
89
- on_enter_yellow do |event|
90
- stop(:now)
91
- end
74
+ on_enter_yellow do |event|
75
+ stop(:now)
76
+ end
92
77
 
93
- on_enter_red do |event, param|
94
- called << "#{event.from} #{param}"
95
- end
96
- }
78
+ on_enter_red do |event, param|
79
+ called << "#{event.from} #{param}"
80
+ end
97
81
  end
98
82
 
99
83
  expect(fsm.current).to eql(:green)
@@ -121,29 +105,23 @@ RSpec.describe FiniteMachine, '#target' do
121
105
 
122
106
  def engine
123
107
  self.called ||= []
124
- context ||= self
125
- @engine ||= FiniteMachine.define do
108
+
109
+ @engine ||= FiniteMachine.new(self) do
126
110
  initial :neutral
127
111
 
128
- target context
129
-
130
- events {
131
- event :forward, [:reverse, :neutral] => :one
132
- event :shift, :one => :two
133
- event :shift, :two => :one
134
- event :back, [:neutral, :one] => :reverse
135
- }
136
-
137
- callbacks {
138
- on_enter :reverse do |event|
139
- target.called << 'on_enter_reverse'
140
- target.turn_reverse_lights_on
141
- forward('Piotr!')
142
- end
143
- on_before :forward do |event, name|
144
- target.called << "on_enter_forward with #{name}"
145
- end
146
- }
112
+ event :forward, [:reverse, :neutral] => :one
113
+ event :shift, :one => :two
114
+ event :shift, :two => :one
115
+ event :back, [:neutral, :one] => :reverse
116
+
117
+ on_enter :reverse do |event|
118
+ target.called << 'on_enter_reverse'
119
+ target.turn_reverse_lights_on
120
+ forward('Piotr!')
121
+ end
122
+ on_before :forward do |event, name|
123
+ target.called << "on_enter_forward with #{name}"
124
+ end
147
125
  end
148
126
  end
149
127
  end)
@@ -162,20 +140,15 @@ RSpec.describe FiniteMachine, '#target' do
162
140
  it "allows to access target inside the callback" do
163
141
  context = double(:context)
164
142
  called = nil
165
- fsm = FiniteMachine.define do
143
+ fsm = FiniteMachine.new(context) do
166
144
  initial :green
167
145
 
168
- target context
146
+ event :slow, :green => :yellow
147
+ event :stop, :yellow => :red
169
148
 
170
- events {
171
- event :slow, :green => :yellow
172
- event :stop, :yellow => :red
173
- }
174
- callbacks {
175
- on_enter_yellow do |event|
176
- called = target
177
- end
178
- }
149
+ on_enter_yellow do |event|
150
+ called = target
151
+ end
179
152
  end
180
153
  expect(fsm.current).to eql(:green)
181
154
  fsm.slow
@@ -194,25 +167,19 @@ RSpec.describe FiniteMachine, '#target' do
194
167
  end)
195
168
 
196
169
  car = Car.new(called)
197
- fsm = FiniteMachine.define do
170
+ fsm = FiniteMachine.new(car) do
198
171
  initial :unsaved
199
172
 
200
- target car
201
-
202
- events {
203
- event :validate, :unsaved => :valid
204
- event :save, :valid => :saved
205
- }
173
+ event :validate, :unsaved => :valid
174
+ event :save, :valid => :saved
206
175
 
207
- callbacks {
208
- on_enter :valid do |event|
209
- target.save
210
- save
211
- end
212
- on_after :save do |event|
213
- called << 'event save called'
214
- end
215
- }
176
+ on_enter :valid do |event|
177
+ target.save
178
+ save
179
+ end
180
+ on_after :save do |event|
181
+ called << 'event save called'
182
+ end
216
183
  end
217
184
  expect(fsm.current).to eql(:unsaved)
218
185
  fsm.validate
@@ -1,18 +1,14 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'terminated?' do
3
+ RSpec.describe FiniteMachine, '#terminated?' do
6
4
 
7
5
  it "allows to specify terminal state" do
8
- fsm = FiniteMachine.define do
6
+ fsm = FiniteMachine.new do
9
7
  initial :green
10
8
  terminal :red
11
9
 
12
- events {
13
- event :slow, :green => :yellow
14
- event :stop, :yellow => :red
15
- }
10
+ event :slow, :green => :yellow
11
+ event :stop, :yellow => :red
16
12
  end
17
13
 
18
14
  expect(fsm.current).to eql(:green)
@@ -28,13 +24,11 @@ RSpec.describe FiniteMachine, 'terminated?' do
28
24
  end
29
25
 
30
26
  it "allows to specify terminal state as parameter" do
31
- fsm = FiniteMachine.define terminal: :red do
27
+ fsm = FiniteMachine.new terminal: :red do
32
28
  initial :green
33
29
 
34
- events {
35
- event :slow, :green => :yellow
36
- event :stop, :yellow => :red
37
- }
30
+ event :slow, :green => :yellow
31
+ event :stop, :yellow => :red
38
32
  end
39
33
  fsm.slow
40
34
  fsm.stop
@@ -42,13 +36,11 @@ RSpec.describe FiniteMachine, 'terminated?' do
42
36
  end
43
37
 
44
38
  it "checks without terminal state" do
45
- fsm = FiniteMachine.define do
39
+ fsm = FiniteMachine.new do
46
40
  initial :green
47
41
 
48
- events {
49
- event :slow, :green => :yellow
50
- event :stop, :yellow => :red
51
- }
42
+ event :slow, :green => :yellow
43
+ event :stop, :yellow => :red
52
44
  end
53
45
 
54
46
  expect(fsm.current).to eql(:green)
@@ -64,16 +56,14 @@ RSpec.describe FiniteMachine, 'terminated?' do
64
56
  end
65
57
 
66
58
  it "allows for multiple terminal states" do
67
- fsm = FiniteMachine.define do
59
+ fsm = FiniteMachine.new do
68
60
  initial :open
69
61
 
70
62
  terminal :close, :canceled, :faulty
71
63
 
72
- events {
73
- event :resolve, :open => :close
74
- event :decline, :open => :canceled
75
- event :error, :open => :faulty
76
- }
64
+ event :resolve, :open => :close
65
+ event :decline, :open => :canceled
66
+ event :error, :open => :faulty
77
67
  end
78
68
  expect(fsm.current).to eql(:open)
79
69
  expect(fsm.terminated?).to be(false)
@@ -1,37 +1,39 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine::Transition, '.check_conditions' do
3
+ RSpec.describe FiniteMachine::Transition, '#check_conditions' do
6
4
  it "verifies all conditions pass" do
5
+ context = double(:context)
7
6
  exec_conditions = 0
8
7
  ok_condition = -> { exec_conditions += 1; return true }
9
8
  fail_condition = -> { exec_conditions += 1; return false }
10
- machine = double(:machine, target: Object.new)
11
9
 
12
- transition = described_class.new(machine, if: [ok_condition, fail_condition])
10
+ transition = described_class.new(context, :event_name,
11
+ if: [ok_condition, fail_condition])
13
12
 
14
13
  expect(transition.check_conditions).to eql(false)
15
14
  expect(exec_conditions).to eq(2)
16
15
  end
17
16
 
18
17
  it "verifies 'if' and 'unless' conditions" do
19
- machine = double(:machine, target: Object.new)
18
+ context = double(:context)
20
19
  exec_conditions = 0
21
20
  ok_condition = -> { exec_conditions += 1; return true }
22
21
  fail_condition = -> { exec_conditions += 1; return false }
23
22
 
24
- transition = described_class.new(machine, if: [ok_condition], unless: [fail_condition])
23
+ transition = described_class.new(context, :event_name,
24
+ if: [ok_condition],
25
+ unless: [fail_condition])
25
26
 
26
27
  expect(transition.check_conditions).to eql(true)
27
28
  expect(exec_conditions).to eq(2)
28
29
  end
29
30
 
30
31
  it "verifies condition with arguments" do
31
- machine = double(:machine, target: Object.new)
32
- condition = lambda { |target, arg| arg == 1 }
32
+ context = double(:context)
33
+ condition = -> (_, arg) { arg == 1 }
33
34
 
34
- transition = described_class.new(machine, if: [condition])
35
+ transition = described_class.new(context, :event_name,
36
+ if: [condition])
35
37
 
36
38
  expect(transition.check_conditions(2)).to eql(false)
37
39
  expect(transition.check_conditions(1)).to eql(true)
@@ -43,11 +45,10 @@ RSpec.describe FiniteMachine::Transition, '.check_conditions' do
43
45
  true
44
46
  end
45
47
  end)
46
- target = Car.new
47
- machine = double(:machine, target: target)
48
- condition = lambda { |car| car.engine_on? }
48
+ context = Car.new
49
+ condition = -> (car) { car.engine_on? }
49
50
 
50
- transition = described_class.new(machine, if: condition)
51
+ transition = described_class.new(context, :event_name, if: condition)
51
52
 
52
53
  expect(transition.check_conditions).to eql(true)
53
54
  end