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