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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +34 -0
- data/README.md +564 -569
- data/Rakefile +5 -1
- data/benchmarks/memory_profile.rb +11 -0
- data/benchmarks/memory_usage.rb +16 -9
- data/finite_machine.gemspec +10 -3
- data/lib/finite_machine.rb +34 -46
- data/lib/finite_machine/async_call.rb +5 -21
- data/lib/finite_machine/callable.rb +4 -4
- data/lib/finite_machine/catchable.rb +4 -2
- data/lib/finite_machine/choice_merger.rb +19 -19
- data/lib/finite_machine/const.rb +16 -0
- data/lib/finite_machine/definition.rb +2 -2
- data/lib/finite_machine/dsl.rb +66 -149
- data/lib/finite_machine/env.rb +4 -2
- data/lib/finite_machine/event_definition.rb +7 -15
- data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
- data/lib/finite_machine/hook_event.rb +60 -61
- data/lib/finite_machine/hooks.rb +44 -36
- data/lib/finite_machine/listener.rb +2 -2
- data/lib/finite_machine/logger.rb +5 -4
- data/lib/finite_machine/message_queue.rb +39 -30
- data/lib/finite_machine/observer.rb +55 -37
- data/lib/finite_machine/safety.rb +12 -10
- data/lib/finite_machine/state_definition.rb +3 -5
- data/lib/finite_machine/state_machine.rb +83 -64
- data/lib/finite_machine/state_parser.rb +51 -79
- data/lib/finite_machine/subscribers.rb +1 -1
- data/lib/finite_machine/threadable.rb +3 -1
- data/lib/finite_machine/transition.rb +30 -31
- data/lib/finite_machine/transition_builder.rb +23 -32
- data/lib/finite_machine/transition_event.rb +12 -11
- data/lib/finite_machine/two_phase_lock.rb +3 -1
- data/lib/finite_machine/undefined_transition.rb +5 -6
- data/lib/finite_machine/version.rb +2 -2
- data/spec/integration/system_spec.rb +36 -38
- data/spec/performance/benchmark_spec.rb +13 -21
- data/spec/unit/alias_target_spec.rb +22 -41
- data/spec/unit/async_callbacks_spec.rb +8 -13
- data/spec/unit/auto_methods_spec.rb +44 -0
- data/spec/unit/callable/call_spec.rb +1 -3
- data/spec/unit/callbacks_spec.rb +372 -463
- data/spec/unit/can_spec.rb +13 -23
- data/spec/unit/cancel_callbacks_spec.rb +46 -0
- data/spec/unit/choice_spec.rb +105 -141
- data/spec/unit/define_spec.rb +31 -31
- data/spec/unit/definition_spec.rb +24 -41
- data/spec/unit/event_names_spec.rb +6 -10
- data/spec/unit/events_map/add_spec.rb +23 -0
- data/spec/unit/events_map/choice_transition_spec.rb +25 -0
- data/spec/unit/events_map/clear_spec.rb +13 -0
- data/spec/unit/events_map/events_spec.rb +16 -0
- data/spec/unit/events_map/inspect_spec.rb +22 -0
- data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
- data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
- data/spec/unit/events_map/states_for_spec.rb +17 -0
- data/spec/unit/events_spec.rb +91 -160
- data/spec/unit/handlers_spec.rb +34 -66
- data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
- data/spec/unit/hook_event/build_spec.rb +1 -3
- data/spec/unit/hook_event/eql_spec.rb +1 -3
- data/spec/unit/hook_event/initialize_spec.rb +2 -4
- data/spec/unit/hook_event/notify_spec.rb +2 -4
- data/spec/unit/hooks/clear_spec.rb +1 -1
- data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
- data/spec/unit/hooks/inspect_spec.rb +16 -8
- data/spec/unit/hooks/register_spec.rb +4 -9
- data/spec/unit/if_unless_spec.rb +76 -115
- data/spec/unit/initial_spec.rb +50 -82
- data/spec/unit/inspect_spec.rb +14 -9
- data/spec/unit/is_spec.rb +12 -18
- data/spec/unit/log_transitions_spec.rb +4 -10
- data/spec/unit/logger_spec.rb +1 -3
- data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
- data/spec/unit/new_spec.rb +50 -0
- data/spec/unit/respond_to_spec.rb +2 -6
- data/spec/unit/state_parser/parse_spec.rb +9 -12
- data/spec/unit/states_spec.rb +12 -18
- data/spec/unit/subscribers_spec.rb +1 -3
- data/spec/unit/target_spec.rb +60 -93
- data/spec/unit/terminated_spec.rb +15 -25
- data/spec/unit/transition/check_conditions_spec.rb +16 -15
- data/spec/unit/transition/inspect_spec.rb +6 -6
- data/spec/unit/transition/matches_spec.rb +5 -7
- data/spec/unit/transition/states_spec.rb +5 -7
- data/spec/unit/transition/to_state_spec.rb +5 -13
- data/spec/unit/trigger_spec.rb +5 -9
- data/spec/unit/undefined_transition/eql_spec.rb +1 -3
- metadata +86 -49
- data/.gitignore +0 -18
- data/.rspec +0 -5
- data/.travis.yml +0 -27
- data/Gemfile +0 -16
- data/assets/finite_machine_logo.png +0 -0
- data/lib/finite_machine/async_proxy.rb +0 -55
- data/spec/unit/async_events_spec.rb +0 -107
- data/spec/unit/events_chain/add_spec.rb +0 -25
- data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
- data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
- data/spec/unit/events_chain/clear_spec.rb +0 -15
- data/spec/unit/events_chain/events_spec.rb +0 -18
- data/spec/unit/events_chain/inspect_spec.rb +0 -24
- data/spec/unit/events_chain/states_for_spec.rb +0 -17
- data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
- data/spec/unit/state_parser/inspect_spec.rb +0 -25
data/.rspec
DELETED
data/.travis.yml
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
---
|
2
|
-
language: ruby
|
3
|
-
sudo: false
|
4
|
-
cache: bundler
|
5
|
-
bundler_args: --without yard benchmarks
|
6
|
-
script: "bundle exec rake ci"
|
7
|
-
rvm:
|
8
|
-
- 1.9.3
|
9
|
-
- 2.0
|
10
|
-
- 2.1
|
11
|
-
- 2.2
|
12
|
-
- 2.3.0
|
13
|
-
- ruby-head
|
14
|
-
- rbx-2
|
15
|
-
matrix:
|
16
|
-
include:
|
17
|
-
- rvm: jruby-19mode
|
18
|
-
- rvm: jruby-20mode
|
19
|
-
- rvm: jruby-21mode
|
20
|
-
- rvm: jruby-head
|
21
|
-
allow_failures:
|
22
|
-
- rvm: ruby-head
|
23
|
-
- rvm: jruby-head
|
24
|
-
- rvm: rbx-2
|
25
|
-
fast_finish: true
|
26
|
-
branches:
|
27
|
-
only: master
|
data/Gemfile
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
gemspec
|
4
|
-
|
5
|
-
group :development do
|
6
|
-
gem 'pry', '~> 0.10.1'
|
7
|
-
gem 'rspec', '~> 3.4.0'
|
8
|
-
gem 'rspec-benchmark', '~> 0.1.0'
|
9
|
-
gem 'yard', '~> 0.8.7'
|
10
|
-
end
|
11
|
-
|
12
|
-
group :metrics do
|
13
|
-
gem 'coveralls', '~> 0.8.1'
|
14
|
-
gem 'simplecov', '~> 0.10.0'
|
15
|
-
gem 'yardstick', '~> 0.9.9'
|
16
|
-
end
|
Binary file
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module FiniteMachine
|
4
|
-
# An asynchronous messages proxy
|
5
|
-
#
|
6
|
-
# @api private
|
7
|
-
class AsyncProxy
|
8
|
-
include Threadable
|
9
|
-
|
10
|
-
attr_threadsafe :context
|
11
|
-
|
12
|
-
# The queue for asynchronoous events
|
13
|
-
#
|
14
|
-
# @return [EventQueue]
|
15
|
-
#
|
16
|
-
# @api private
|
17
|
-
attr_threadsafe :event_queue
|
18
|
-
|
19
|
-
# Initialize an AsynxProxy
|
20
|
-
#
|
21
|
-
# @param [Object] context
|
22
|
-
# the context this proxy is associated with
|
23
|
-
#
|
24
|
-
# @api private
|
25
|
-
def initialize(context)
|
26
|
-
self.context = context
|
27
|
-
self.event_queue = MessageQueue.new
|
28
|
-
|
29
|
-
ObjectSpace.define_finalizer(self, self.class.cleanup(event_queue))
|
30
|
-
end
|
31
|
-
|
32
|
-
# Delegate asynchronous event to event queue
|
33
|
-
#
|
34
|
-
# @api private
|
35
|
-
def method_missing(method_name, *args, &block)
|
36
|
-
callable = Callable.new(method_name)
|
37
|
-
async_call = AsyncCall.new(context, callable, *args, &block)
|
38
|
-
|
39
|
-
event_queue.start unless event_queue.running?
|
40
|
-
context.event_queue << async_call
|
41
|
-
end
|
42
|
-
|
43
|
-
# Clean up event queue
|
44
|
-
#
|
45
|
-
# @api private
|
46
|
-
def self.cleanup(queue)
|
47
|
-
proc do
|
48
|
-
begin
|
49
|
-
queue && queue.shutdown
|
50
|
-
rescue MessageQueueDeadError
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end # AsyncProxy
|
55
|
-
end # FiniteMachine
|
@@ -1,107 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
RSpec.describe FiniteMachine, 'async events' do
|
4
|
-
|
5
|
-
it 'runs events asynchronously' do
|
6
|
-
called = []
|
7
|
-
fsm = FiniteMachine.define do
|
8
|
-
initial :green
|
9
|
-
|
10
|
-
events {
|
11
|
-
event :slow, :green => :yellow
|
12
|
-
event :stop, :yellow => :red
|
13
|
-
event :ready, :red => :yellow
|
14
|
-
event :go, :yellow => :green
|
15
|
-
}
|
16
|
-
|
17
|
-
callbacks {
|
18
|
-
on_enter :yellow do |event, a| called << "on_enter_yellow_#{a}" end
|
19
|
-
on_enter :red do |event, a| called << "on_enter_red_#{a}" end
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
expect(fsm.current).to eql(:green)
|
24
|
-
fsm.async.slow(:foo)
|
25
|
-
fsm.event_queue.join 0.01
|
26
|
-
expect(fsm.current).to eql(:yellow)
|
27
|
-
expect(called).to eql([
|
28
|
-
'on_enter_yellow_foo'
|
29
|
-
])
|
30
|
-
fsm.async(:stop, :bar) # execute directly
|
31
|
-
fsm.event_queue.join 0.01
|
32
|
-
expect(fsm.current).to eql(:red)
|
33
|
-
expect(called).to match_array([
|
34
|
-
'on_enter_yellow_foo',
|
35
|
-
'on_enter_red_bar'
|
36
|
-
])
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'correctly passes parameters to conditionals' do
|
40
|
-
called = []
|
41
|
-
fsm = FiniteMachine.define do
|
42
|
-
events {
|
43
|
-
event :go, :none => :green,
|
44
|
-
if: proc { |context, arg|
|
45
|
-
called << "cond_none_green(#{context},#{arg})"; true
|
46
|
-
}
|
47
|
-
|
48
|
-
event :stop, from: :any do
|
49
|
-
choice :red, if: proc { |context, arg|
|
50
|
-
called << "cond_any_red(#{context},#{arg})"; true
|
51
|
-
}
|
52
|
-
end
|
53
|
-
}
|
54
|
-
end
|
55
|
-
expect(fsm.current).to eql(:none)
|
56
|
-
fsm.async.go(:foo)
|
57
|
-
fsm.event_queue.join 0.02
|
58
|
-
expect(fsm.current).to eql(:green)
|
59
|
-
expect(called).to eql(["cond_none_green(#{fsm},foo)"])
|
60
|
-
|
61
|
-
expect(fsm.current).to eql(:green)
|
62
|
-
fsm.async.stop(:bar)
|
63
|
-
fsm.event_queue.join 0.02
|
64
|
-
expect(fsm.current).to eql(:red)
|
65
|
-
expect(called).to match_array([
|
66
|
-
"cond_none_green(#{fsm},foo)",
|
67
|
-
"cond_any_red(#{fsm},bar)"
|
68
|
-
])
|
69
|
-
end
|
70
|
-
|
71
|
-
it "ensure queue per thread" do
|
72
|
-
called = []
|
73
|
-
fsmFoo = nil
|
74
|
-
fsmBar = nil
|
75
|
-
foo_thread = Thread.new {
|
76
|
-
fsmFoo = FiniteMachine.define do
|
77
|
-
initial :green
|
78
|
-
events { event :slow, :green => :yellow }
|
79
|
-
|
80
|
-
callbacks {
|
81
|
-
on_enter :yellow do |event, a| called << "(foo)on_enter_yellow_#{a}" end
|
82
|
-
}
|
83
|
-
end
|
84
|
-
fsmFoo.async.slow(:foo)
|
85
|
-
}
|
86
|
-
bar_thread = Thread.new {
|
87
|
-
fsmBar = FiniteMachine.define do
|
88
|
-
initial :green
|
89
|
-
events { event :slow, :green => :yellow }
|
90
|
-
|
91
|
-
callbacks {
|
92
|
-
on_enter :yellow do |event, a| called << "(bar)on_enter_yellow_#{a}" end
|
93
|
-
}
|
94
|
-
end
|
95
|
-
fsmBar.async.slow(:bar)
|
96
|
-
}
|
97
|
-
ThreadsWait.all_waits(foo_thread, bar_thread)
|
98
|
-
fsmFoo.event_queue.join(0.01)
|
99
|
-
fsmBar.event_queue.join(0.01)
|
100
|
-
expect(called).to match_array([
|
101
|
-
'(foo)on_enter_yellow_foo',
|
102
|
-
'(bar)on_enter_yellow_bar'
|
103
|
-
])
|
104
|
-
expect(fsmFoo.current).to eql(:yellow)
|
105
|
-
expect(fsmBar.current).to eql(:yellow)
|
106
|
-
end
|
107
|
-
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,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,13 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
RSpec.describe FiniteMachine::HookEvent, '#infer_default_name' do
|
4
|
-
it "infers default name for state" do
|
5
|
-
hook_event = described_class::Enter
|
6
|
-
expect(described_class.infer_default_name(hook_event)).to eq(:any)
|
7
|
-
end
|
8
|
-
|
9
|
-
it "infers default name for event" do
|
10
|
-
hook_event = described_class::Before
|
11
|
-
expect(described_class.infer_default_name(hook_event)).to eq(:any_event)
|
12
|
-
end
|
13
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
RSpec.describe FiniteMachine::StateParser, "#inspect" do
|
6
|
-
let(:object) { described_class }
|
7
|
-
|
8
|
-
subject(:parser) { object.new(attrs) }
|
9
|
-
|
10
|
-
describe '#inspect' do
|
11
|
-
let(:attrs) { { green: :yellow } }
|
12
|
-
|
13
|
-
it "inspects parser" do
|
14
|
-
expect(parser.inspect).to eq("<#FiniteMachine::StateParser @attrs=green:yellow>")
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
describe '#to_s' do
|
19
|
-
let(:attrs) { { green: :yellow } }
|
20
|
-
|
21
|
-
it "prints parser attributes" do
|
22
|
-
expect(parser.to_s).to eq(attrs.to_s)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|