statemachine 0.0.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.
- data/CHANGES +12 -0
- data/Rakefile +170 -0
- data/lib/proc_calling.rb +54 -0
- data/lib/state.rb +69 -0
- data/lib/state_machine.rb +103 -0
- data/lib/statemachine.rb +1 -0
- data/lib/super_state.rb +68 -0
- data/lib/transition.rb +71 -0
- data/spec/sm_action_parameterization_spec.rb +114 -0
- data/spec/sm_entry_exit_actions_spec.rb +49 -0
- data/spec/sm_odds_n_ends_spec.rb +27 -0
- data/spec/sm_simple_spec.rb +69 -0
- data/spec/sm_super_state_spec.rb +80 -0
- data/spec/sm_turnstile_spec.rb +78 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/transition_spec.rb +97 -0
- metadata +66 -0
data/CHANGES
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
$:.unshift('lib')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/rubyforgepublisher'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'spec/rake/spectask'
|
8
|
+
|
9
|
+
# Some of the tasks are in separate files since they are also part of the website documentation
|
10
|
+
"load File.dirname(__FILE__) + '/tasks/examples.rake'
|
11
|
+
load File.dirname(__FILE__) + '/tasks/examples_specdoc.rake'
|
12
|
+
load File.dirname(__FILE__) + '/tasks/examples_with_rcov.rake'
|
13
|
+
load File.dirname(__FILE__) + '/tasks/failing_examples_with_html.rake'
|
14
|
+
load File.dirname(__FILE__) + '/tasks/verify_rcov.rake'"
|
15
|
+
|
16
|
+
PKG_NAME = "statemachine"
|
17
|
+
PKG_VERSION = "0.0.0"
|
18
|
+
PKG_TAG = "0_0_0"
|
19
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
20
|
+
PKG_FILES = FileList[
|
21
|
+
'[A-Z]*',
|
22
|
+
'lib/**/*.rb',
|
23
|
+
'spec/**/*.rb'
|
24
|
+
# 'examples/**/*',
|
25
|
+
]
|
26
|
+
|
27
|
+
task :default => :spec
|
28
|
+
|
29
|
+
desc "Run all specs"
|
30
|
+
Spec::Rake::SpecTask.new do |t|
|
31
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
t.spec_opts = ['--diff','--color']
|
33
|
+
# t.rcov = true
|
34
|
+
# t.rcov_dir = 'doc/output/coverage'
|
35
|
+
# t.rcov_opts = ['--exclude', 'spec\/spec,bin\/spec']"
|
36
|
+
end
|
37
|
+
|
38
|
+
#desc 'Generate HTML documentation for website'
|
39
|
+
#task :webgen do
|
40
|
+
# Dir.chdir 'doc' do
|
41
|
+
# output = nil
|
42
|
+
# IO.popen('webgen 2>&1') do |io|
|
43
|
+
# output = io.read
|
44
|
+
# end
|
45
|
+
# raise "ERROR while running webgen: #{output}" if output =~ /ERROR/n || $? != 0
|
46
|
+
# end
|
47
|
+
#end
|
48
|
+
|
49
|
+
#desc 'Generate RDoc'
|
50
|
+
#rd = Rake::RDocTask.new do |rdoc|
|
51
|
+
# rdoc.rdoc_dir = 'doc/output/rdoc'
|
52
|
+
# rdoc.options << '--title' << 'RSpec' << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
53
|
+
# rdoc.rdoc_files.include('README', 'CHANGES', 'EXAMPLES.rd', 'lib/**/*.rb')
|
54
|
+
#end
|
55
|
+
#task :rdoc => :examples_specdoc # We generate EXAMPLES.rd
|
56
|
+
|
57
|
+
spec = Gem::Specification.new do |s|
|
58
|
+
s.name = PKG_NAME
|
59
|
+
s.version = PKG_VERSION
|
60
|
+
s.summary = Spec::VERSION::DESCRIPTION
|
61
|
+
s.description = <<-EOF
|
62
|
+
StateMachine is a ruby library for building Finite State Machines (FSM), also known as Finite State Automata (FSA).
|
63
|
+
EOF
|
64
|
+
|
65
|
+
s.files = PKG_FILES.to_a
|
66
|
+
s.require_path = 'lib'
|
67
|
+
|
68
|
+
# s.has_rdoc = true
|
69
|
+
# s.rdoc_options = rd.options
|
70
|
+
# s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$|^EXAMPLES.rd$/ }.to_a
|
71
|
+
|
72
|
+
s.test_files = Dir.glob('spec/*_spec.rb')
|
73
|
+
s.require_path = 'lib'
|
74
|
+
s.autorequire = 'statemachine'
|
75
|
+
# s.bindir = "bin"
|
76
|
+
# s.executables = ["spec"]
|
77
|
+
# s.default_executable = "spec"
|
78
|
+
s.author = ["Micah Martin"]
|
79
|
+
s.email = "statemachine-devel@rubyforge.org"
|
80
|
+
s.homepage = "http://statemachine.rubyforge.org"
|
81
|
+
s.rubyforge_project = "statemachine"
|
82
|
+
end
|
83
|
+
|
84
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
85
|
+
pkg.need_zip = true
|
86
|
+
pkg.need_tar = true
|
87
|
+
end
|
88
|
+
|
89
|
+
def egrep(pattern)
|
90
|
+
Dir['**/*.rb'].each do |fn|
|
91
|
+
count = 0
|
92
|
+
open(fn) do |f|
|
93
|
+
while line = f.gets
|
94
|
+
count += 1
|
95
|
+
if line =~ pattern
|
96
|
+
puts "#{fn}:#{count}:#{line}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Look for TODO and FIXME tags in the code"
|
104
|
+
task :todo do
|
105
|
+
egrep /(FIXME|TODO|TBD)/
|
106
|
+
end
|
107
|
+
|
108
|
+
task :clobber do
|
109
|
+
rm_rf 'doc/output'
|
110
|
+
end
|
111
|
+
|
112
|
+
task :release => [:clobber, :verify_committed, :verify_user, :verify_password, :spec, :publish_packages, :tag, :publish_website, :publish_news]
|
113
|
+
|
114
|
+
desc "Verifies that there is no uncommitted code"
|
115
|
+
task :verify_committed do
|
116
|
+
IO.popen('svn stat') do |io|
|
117
|
+
io.each_line do |line|
|
118
|
+
raise "\n!!! Do a svn commit first !!!\n\n" if line =~ /^\s*M\s*/
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "Creates a tag in svn"
|
124
|
+
task :tag do
|
125
|
+
puts "Creating tag in SVN"
|
126
|
+
`svn cp svn+ssh://#{ENV['RUBYFORGE_USER']}@rubyforge.org/var/svn/statemachine/trunk svn+ssh://#{ENV['RUBYFORGE_USER']}@rubyforge.org/var/svn/statemachine/tags/#{PKG_VERSION} -m "Tag release #{PKG_TAG}"`
|
127
|
+
puts "Done!"
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
#desc "Build the website, but do not publish it"
|
132
|
+
#task :website => [:clobber, :verify_rcov, :webgen, :failing_examples_with_html, :spec, :examples_specdoc, :rdoc]
|
133
|
+
|
134
|
+
#desc "Upload Website to RubyForge"
|
135
|
+
#task :publish_website => [:verify_user, :website] do
|
136
|
+
# publisher = Rake::SshDirPublisher.new(
|
137
|
+
# "rspec-website@rubyforge.org",
|
138
|
+
# "/var/www/gforge-projects/#{PKG_NAME}",
|
139
|
+
# "doc/output"
|
140
|
+
# )
|
141
|
+
|
142
|
+
# publisher.upload
|
143
|
+
#end
|
144
|
+
|
145
|
+
task :verify_user do
|
146
|
+
raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER']
|
147
|
+
end
|
148
|
+
|
149
|
+
task :verify_password do
|
150
|
+
raise "RUBYFORGE_PASSWORD environment variable not set!" unless ENV['RUBYFORGE_PASSWORD']
|
151
|
+
end
|
152
|
+
|
153
|
+
desc "Publish gem+tgz+zip on RubyForge. You must make sure lib/version.rb is aligned with the CHANGELOG file"
|
154
|
+
task :publish_packages => [:verify_user, :verify_password, :package] do
|
155
|
+
require 'meta_project'
|
156
|
+
require 'rake/contrib/xforge'
|
157
|
+
release_files = FileList[
|
158
|
+
"pkg/#{PKG_FILE_NAME}.gem",
|
159
|
+
"pkg/#{PKG_FILE_NAME}.tgz",
|
160
|
+
"pkg/#{PKG_FILE_NAME}.zip"
|
161
|
+
]
|
162
|
+
|
163
|
+
Rake::XForge::Release.new(MetaProject::Project::XForge::RubyForge.new(PKG_NAME)) do |xf|
|
164
|
+
# Never hardcode user name and password in the Rakefile!
|
165
|
+
xf.user_name = ENV['RUBYFORGE_USER']
|
166
|
+
xf.password = ENV['RUBYFORGE_PASSWORD']
|
167
|
+
xf.files = release_files.to_a
|
168
|
+
xf.release_name = "StateMachine #{PKG_VERSION}"
|
169
|
+
end
|
170
|
+
end
|
data/lib/proc_calling.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module StateMachine
|
2
|
+
|
3
|
+
module ProcCalling
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def call_proc(proc, args, message)
|
8
|
+
arity = proc.arity
|
9
|
+
if should_call_with(arity, 0, args, message)
|
10
|
+
proc.call
|
11
|
+
elsif should_call_with(arity, 1, args, message)
|
12
|
+
proc.call args[0]
|
13
|
+
elsif should_call_with(arity, 2, args, message)
|
14
|
+
proc.call args[0], args[1]
|
15
|
+
elsif should_call_with(arity, 3, args, message)
|
16
|
+
proc.call args[0], args[1], args[2]
|
17
|
+
elsif should_call_with(arity, 4, args, message)
|
18
|
+
proc.call args[0], args[1], args[2], args[3]
|
19
|
+
elsif should_call_with(arity, 5, args, message)
|
20
|
+
proc.call args[0], args[1], args[2], args[3], args[4]
|
21
|
+
elsif should_call_with(arity, 6, args, message)
|
22
|
+
proc.call args[0], args[1], args[2], args[3], args[4], args[5]
|
23
|
+
elsif should_call_with(arity, 7, args, message)
|
24
|
+
proc.call args[0], args[1], args[2], args[3], args[4], args[5], args[6]
|
25
|
+
elsif should_call_with(arity, 8, args, message)
|
26
|
+
proc.call args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]
|
27
|
+
elsif arity < 0 and args and args.length > 8
|
28
|
+
proc.call args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]
|
29
|
+
else
|
30
|
+
raise StateMachineException.new("Too many arguments(#{args.length}). (#{message})")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def should_call_with(arity, n, args, message)
|
35
|
+
actual = args ? args.length : 0
|
36
|
+
if arity == n
|
37
|
+
return enough_args?(actual, arity, arity, message)
|
38
|
+
elsif arity < 0
|
39
|
+
required_args = (arity * -1) - 1
|
40
|
+
return (actual == n and enough_args?(actual, required_args, arity, message))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def enough_args?(actual, required, arity, message)
|
45
|
+
if actual >= required
|
46
|
+
return true
|
47
|
+
else
|
48
|
+
raise StateMachineException.new("Insufficient parameters. (#{message})")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/state.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/proc_calling'
|
2
|
+
|
3
|
+
module StateMachine
|
4
|
+
|
5
|
+
class State
|
6
|
+
|
7
|
+
include ProcCalling
|
8
|
+
|
9
|
+
attr_reader :id, :statemachine, :entry_action, :exit_action
|
10
|
+
attr_accessor :superstate
|
11
|
+
|
12
|
+
def initialize(id, state_machine)
|
13
|
+
@id = id
|
14
|
+
@statemachine = state_machine
|
15
|
+
@transitions = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(transition)
|
19
|
+
@transitions[transition.event] = transition
|
20
|
+
end
|
21
|
+
|
22
|
+
def transitions
|
23
|
+
return @superstate ? @transitions.merge(@superstate.transitions) : @transitions
|
24
|
+
end
|
25
|
+
|
26
|
+
def local_transitions
|
27
|
+
return @transitions
|
28
|
+
end
|
29
|
+
|
30
|
+
def [] (event)
|
31
|
+
return transitions[event]
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_entry action
|
35
|
+
@entry_action = action
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_exit action
|
39
|
+
@exit_action = action
|
40
|
+
end
|
41
|
+
|
42
|
+
def exit(args)
|
43
|
+
@statemachine.trace("\texiting #{self}")
|
44
|
+
call_proc(@exit_action, args, "exit action for #{self}") if @exit_action
|
45
|
+
@superstate.existing(self) if @superstate
|
46
|
+
end
|
47
|
+
|
48
|
+
def enter(args)
|
49
|
+
@statemachine.trace("\tentering #{self}")
|
50
|
+
call_proc(@entry_action, args, "entry action for #{self}") if @entry_action
|
51
|
+
end
|
52
|
+
|
53
|
+
def is_superstate?
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
return "'#{id}' state"
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_substates(*substate_ids)
|
62
|
+
raise StateMachineException.new("At least one parameter is required for add_substates.") if substate_ids.length == 0
|
63
|
+
replacement = Superstate.new(self, @transitions, substate_ids)
|
64
|
+
@statemachine.replace_state(@id, replacement)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/state'
|
2
|
+
require File.dirname(__FILE__) + '/super_state'
|
3
|
+
require File.dirname(__FILE__) + '/transition'
|
4
|
+
require File.dirname(__FILE__) + '/proc_calling'
|
5
|
+
|
6
|
+
module StateMachine
|
7
|
+
|
8
|
+
class StateMachineException < Exception
|
9
|
+
end
|
10
|
+
|
11
|
+
class MissingTransitionException < StateMachineException
|
12
|
+
end
|
13
|
+
|
14
|
+
class StateMachine
|
15
|
+
|
16
|
+
include ProcCalling
|
17
|
+
|
18
|
+
attr_reader :states, :state, :running
|
19
|
+
attr_accessor :start_state, :tracer
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@states = {}
|
23
|
+
@start_state = nil
|
24
|
+
@state = nil
|
25
|
+
@running = false
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(origin_id, event, destination_id, action = nil)
|
29
|
+
origin = acquire_state(origin_id)
|
30
|
+
@start_state = origin if @start_state == nil
|
31
|
+
destination = acquire_state(destination_id)
|
32
|
+
origin.add(Transition.new(origin, destination, event, action))
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
@state = @start_state
|
37
|
+
@running = true
|
38
|
+
end
|
39
|
+
alias :reset :run
|
40
|
+
|
41
|
+
def [] (state_id)
|
42
|
+
return @states[state_id]
|
43
|
+
end
|
44
|
+
|
45
|
+
def state= value
|
46
|
+
if @states[value]
|
47
|
+
@state = @states[value]
|
48
|
+
elsif value and @states[value.to_sym]
|
49
|
+
@state = @states[value.to_sym]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_event(event, *args)
|
54
|
+
trace "Event: #{event}"
|
55
|
+
if @state
|
56
|
+
transition = @state.transitions[event]
|
57
|
+
if transition
|
58
|
+
@state = transition.invoke(@state, args)
|
59
|
+
else
|
60
|
+
raise MissingTransitionException.new("#{@state} does not respond to the '#{event}' event.")
|
61
|
+
end
|
62
|
+
@running = @state != nil
|
63
|
+
else
|
64
|
+
raise StateMachineException.new("The state machine isn't in any state. Did you forget to call run?")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(message, *args)
|
69
|
+
if @state and @state[message]
|
70
|
+
method = self.method(:process_event)
|
71
|
+
params = [message.to_sym].concat(args)
|
72
|
+
call_proc(method, params, "method_missing")
|
73
|
+
else
|
74
|
+
super(message, args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def acquire_state(state_id)
|
79
|
+
return nil if state_id == nil
|
80
|
+
state = @states[state_id]
|
81
|
+
if not state
|
82
|
+
state = State.new(state_id, self)
|
83
|
+
@states[state_id] = state
|
84
|
+
end
|
85
|
+
return state
|
86
|
+
end
|
87
|
+
|
88
|
+
def replace_state(state_id, replacement_state)
|
89
|
+
@states[state_id] = replacement_state
|
90
|
+
@states.values.each do |state|
|
91
|
+
state.local_transitions.values.each do |transition|
|
92
|
+
transition.destination = replacement_state if transition.destination.id == state_id
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def trace(message)
|
98
|
+
@tracer.puts message if @tracer
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/statemachine.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/state_machine'
|
data/lib/super_state.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module StateMachine
|
2
|
+
|
3
|
+
class Superstate < State
|
4
|
+
|
5
|
+
attr_writer :start_state
|
6
|
+
|
7
|
+
def initialize(state, transitions, substate_ids)
|
8
|
+
@id = state.id
|
9
|
+
@statemachine = state.statemachine
|
10
|
+
@transitions = transitions
|
11
|
+
@entry_action = state.entry_action
|
12
|
+
@exit_action = state.exit_action
|
13
|
+
@superstate = state.superstate
|
14
|
+
do_substate_adding(substate_ids)
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_superstate?
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_state
|
22
|
+
if @use_history and @history_state
|
23
|
+
return @history_state
|
24
|
+
else
|
25
|
+
return @start_state
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def existing(substate)
|
30
|
+
@history_state = substate
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_substates(*substate_ids)
|
34
|
+
do_substate_adding(substate_ids)
|
35
|
+
end
|
36
|
+
|
37
|
+
def use_history
|
38
|
+
@use_history = true;
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
return "'#{id}' superstate"
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def do_substate_adding(substate_ids)
|
48
|
+
substate_ids.each do |substate_id|
|
49
|
+
substate = @statemachine.acquire_state(substate_id)
|
50
|
+
@start_state = substate if not @start_state
|
51
|
+
substate.superstate = self
|
52
|
+
check_for_substate_recursion
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_for_substate_recursion
|
57
|
+
tmp_state = @superstate
|
58
|
+
while tmp_state
|
59
|
+
if tmp_state == self
|
60
|
+
raise StateMachineException.new("Cyclic substates not allowed. (#{id})")
|
61
|
+
end
|
62
|
+
tmp_state = tmp_state.superstate
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/transition.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/proc_calling'
|
2
|
+
|
3
|
+
module StateMachine
|
4
|
+
|
5
|
+
class Transition
|
6
|
+
|
7
|
+
include ProcCalling
|
8
|
+
|
9
|
+
attr_reader :origin, :event, :action
|
10
|
+
attr_accessor :destination
|
11
|
+
|
12
|
+
def initialize(origin, destination, event, action)
|
13
|
+
@origin = origin
|
14
|
+
@destination = destination
|
15
|
+
@event = event
|
16
|
+
@action = action
|
17
|
+
end
|
18
|
+
|
19
|
+
def invoke(origin, args)
|
20
|
+
exits, entries = exits_and_entries(origin)
|
21
|
+
exits.each { |exited_state| exited_state.exit(args) }
|
22
|
+
|
23
|
+
call_proc(@action, args, "transition action from #{origin} invoked by '#{event}' event") if @action
|
24
|
+
|
25
|
+
entries.each { |entered_state| entered_state.enter(args) }
|
26
|
+
|
27
|
+
terminal_state = @destination
|
28
|
+
while terminal_state and terminal_state.is_superstate?
|
29
|
+
terminal_state = terminal_state.start_state
|
30
|
+
terminal_state.enter(args)
|
31
|
+
end
|
32
|
+
|
33
|
+
return terminal_state
|
34
|
+
end
|
35
|
+
|
36
|
+
def exits_and_entries(origin)
|
37
|
+
exits = []
|
38
|
+
entries = exits_and_entries_helper(exits, origin)
|
39
|
+
|
40
|
+
return exits, entries.reverse
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
return "#{origin.id} ---#{event}---> #{destination.id} : #{action}"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def exits_and_entries_helper(exits, exit_state)
|
50
|
+
entries = entries_to_destination(exit_state)
|
51
|
+
return entries if entries
|
52
|
+
return [] if exit_state == nil
|
53
|
+
|
54
|
+
exits << exit_state
|
55
|
+
exits_and_entries_helper(exits, exit_state.superstate)
|
56
|
+
end
|
57
|
+
|
58
|
+
def entries_to_destination(exit_state)
|
59
|
+
entries = []
|
60
|
+
state = @destination
|
61
|
+
while state
|
62
|
+
entries << state
|
63
|
+
return entries if exit_state == state.superstate
|
64
|
+
state = state.superstate
|
65
|
+
end
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "State Machine Odds And Ends" do
|
4
|
+
include SwitchStateMachine
|
5
|
+
|
6
|
+
setup do
|
7
|
+
create_switch
|
8
|
+
@sm.run
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "action with one parameter" do
|
12
|
+
@sm.add(:off, :set, :on, Proc.new { |value| @status = value } )
|
13
|
+
@sm.set "blue"
|
14
|
+
@status.should_equal "blue"
|
15
|
+
@sm.state.id.should_be :on
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "action with two parameters" do
|
19
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b| @status = [a, b].join(",") } )
|
20
|
+
@sm.set "blue", "green"
|
21
|
+
@status.should_equal "blue,green"
|
22
|
+
@sm.state.id.should_be :on
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "action with three parameters" do
|
26
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } )
|
27
|
+
@sm.set "blue", "green", "red"
|
28
|
+
@status.should_equal "blue,green,red"
|
29
|
+
@sm.state.id.should_be :on
|
30
|
+
end
|
31
|
+
|
32
|
+
specify "action with four parameters" do
|
33
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d| @status = [a, b, c, d].join(",") } )
|
34
|
+
@sm.set "blue", "green", "red", "orange"
|
35
|
+
@status.should_equal "blue,green,red,orange"
|
36
|
+
@sm.state.id.should_be :on
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "action with five parameters" do
|
40
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e| @status = [a, b, c, d, e].join(",") } )
|
41
|
+
@sm.set "blue", "green", "red", "orange", "yellow"
|
42
|
+
@status.should_equal "blue,green,red,orange,yellow"
|
43
|
+
@sm.state.id.should_be :on
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "action with six parameters" do
|
47
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f| @status = [a, b, c, d, e, f].join(",") } )
|
48
|
+
@sm.set "blue", "green", "red", "orange", "yellow", "indigo"
|
49
|
+
@status.should_equal "blue,green,red,orange,yellow,indigo"
|
50
|
+
@sm.state.id.should_be :on
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "action with seven parameters" do
|
54
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g| @status = [a, b, c, d, e, f, g].join(",") } )
|
55
|
+
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet"
|
56
|
+
@status.should_equal "blue,green,red,orange,yellow,indigo,violet"
|
57
|
+
@sm.state.id.should_be :on
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "action with eight parameters" do
|
61
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h| @status = [a, b, c, d, e, f, g, h].join(",") } )
|
62
|
+
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet"
|
63
|
+
@status.should_equal "blue,green,red,orange,yellow,indigo,violet,ultra-violet"
|
64
|
+
@sm.state.id.should_be :on
|
65
|
+
end
|
66
|
+
|
67
|
+
specify "To many parameters" do
|
68
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h, i| @status = [a, b, c, d, e, f, g, h, i].join(",") } )
|
69
|
+
begin
|
70
|
+
@sm.process_event(:set, "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet", "Yikes!")
|
71
|
+
rescue StateMachine::StateMachineException => e
|
72
|
+
e.message.should_equal "Too many arguments(9). (transition action from 'off' state invoked by 'set' event)"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
specify "calling process_event with parameters" do
|
77
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } )
|
78
|
+
@sm.process_event(:set, "blue", "green", "red")
|
79
|
+
@status.should_equal "blue,green,red"
|
80
|
+
@sm.state.id.should_be :on
|
81
|
+
end
|
82
|
+
|
83
|
+
specify "Insufficient params" do
|
84
|
+
@sm.add(:off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } )
|
85
|
+
begin
|
86
|
+
@sm.set "blue", "green"
|
87
|
+
rescue StateMachine::StateMachineException => e
|
88
|
+
e.message.should_equal "Insufficient parameters. (transition action from 'off' state invoked by 'set' event)"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
specify "infinate args" do
|
93
|
+
@sm.add(:off, :set, :on, Proc.new { |*a| @status = a.join(",") } )
|
94
|
+
@sm.set(1, 2, 3)
|
95
|
+
@status.should_equal "1,2,3"
|
96
|
+
|
97
|
+
@sm.state = :off
|
98
|
+
@sm.set(1, 2, 3, 4, 5, 6)
|
99
|
+
@status.should_equal "1,2,3,4,5,6"
|
100
|
+
end
|
101
|
+
|
102
|
+
specify "Insufficient params when params are infinate" do
|
103
|
+
@sm.add(:off, :set, :on, Proc.new { |a, *b| @status = a.to_s + ":" + b.join(",") } )
|
104
|
+
@sm.set(1, 2, 3)
|
105
|
+
@status.should_equal "1:2,3"
|
106
|
+
|
107
|
+
@sm.state = :off
|
108
|
+
begin
|
109
|
+
@sm.set
|
110
|
+
rescue StateMachine::StateMachineException => e
|
111
|
+
e.message.should_equal "Insufficient parameters. (transition action from 'off' state invoked by 'set' event)"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "State Machine Entry and Exit Actions" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@log = []
|
7
|
+
@sm = StateMachine::StateMachine.new
|
8
|
+
@sm.add(:off, :toggle, :on, Proc.new { @log << "on" } )
|
9
|
+
@sm.add(:on, :toggle, :off, Proc.new { @log << "off" } )
|
10
|
+
@sm.run
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "entry action" do
|
14
|
+
@sm[:on].on_entry Proc.new { @log << "entered_on" }
|
15
|
+
|
16
|
+
@sm.toggle
|
17
|
+
|
18
|
+
@log.join(",").should_equal "on,entered_on"
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "exit action" do
|
22
|
+
@sm[:off].on_exit Proc.new { @log << "exited_off" }
|
23
|
+
|
24
|
+
@sm.toggle
|
25
|
+
|
26
|
+
@log.join(",").should_equal "exited_off,on"
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "exit and entry" do
|
30
|
+
@sm[:off].on_exit Proc.new { @log << "exited_off" }
|
31
|
+
@sm[:on].on_entry Proc.new { @log << "entered_on" }
|
32
|
+
|
33
|
+
@sm.toggle
|
34
|
+
|
35
|
+
@log.join(",").should_equal "exited_off,on,entered_on"
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "entry and exit actions may be parameterized" do
|
39
|
+
@sm[:off].on_exit Proc.new { |a| @log << "exited_off(#{a})" }
|
40
|
+
@sm[:on].on_entry Proc.new { |a, b| @log << "entered_on(#{a},#{b})" }
|
41
|
+
|
42
|
+
@sm.toggle "one", "two"
|
43
|
+
|
44
|
+
@log.join(",").should_equal "exited_off(one),on,entered_on(one,two)"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "State Machine Odds And Ends" do
|
4
|
+
include SwitchStateMachine
|
5
|
+
|
6
|
+
setup do
|
7
|
+
create_switch
|
8
|
+
@sm.run
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "method missing delegates to super in case of no event" do
|
12
|
+
lambda { @sm.blah }.should_raise NoMethodError
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "set state with string" do
|
16
|
+
@sm.state.id.should_be :off
|
17
|
+
@sm.state = "on"
|
18
|
+
@sm.state.id.should_be :on
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "set state with symbol" do
|
22
|
+
@sm.state.id.should_be :off
|
23
|
+
@sm.state = :on
|
24
|
+
@sm.state.id.should_be :on
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "simple cases:" do
|
4
|
+
setup do
|
5
|
+
@sm = StateMachine::StateMachine.new
|
6
|
+
@count = 0
|
7
|
+
@proc = Proc.new {@count = @count + 1}
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "one transition has states" do
|
11
|
+
@sm.add(:on, :flip, :off, @proc)
|
12
|
+
|
13
|
+
@sm.states.length.should_be 2
|
14
|
+
@sm[:on].should_not_be nil
|
15
|
+
@sm[:off].should_not_be nil
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "one trasition create connects states with transition" do
|
19
|
+
@sm.add(:on, :flip, :off, @proc)
|
20
|
+
origin = @sm[:on]
|
21
|
+
destination = @sm[:off]
|
22
|
+
|
23
|
+
origin.transitions.length.should_be 1
|
24
|
+
destination.transitions.length.should_be 0
|
25
|
+
transition = origin[:flip]
|
26
|
+
check_transition(transition, :on, :off, :flip, @proc)
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "end state" do
|
30
|
+
@sm.add(:start, :blah, nil, @proc)
|
31
|
+
|
32
|
+
@sm.run
|
33
|
+
@sm.running.should.be true
|
34
|
+
@sm.process_event(:blah)
|
35
|
+
@sm.state.should.be nil
|
36
|
+
@sm.running.should.be false;
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "reset" do
|
40
|
+
@sm.add(:start, :blah, nil, @proc)
|
41
|
+
@sm.run
|
42
|
+
@sm.process_event(:blah)
|
43
|
+
|
44
|
+
@sm.reset
|
45
|
+
|
46
|
+
@sm.state.should.be @sm[:start]
|
47
|
+
@sm.running.should.be true
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "exception when state machine is not running" do
|
51
|
+
@sm.add(:on, :flip, :off)
|
52
|
+
|
53
|
+
begin
|
54
|
+
@sm.process_event(:flip)
|
55
|
+
rescue StateMachine::StateMachineException => e
|
56
|
+
e.message.should_equal "The state machine isn't in any state. Did you forget to call run?"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "no proc in transition" do
|
61
|
+
@sm.add(:on, :flip, :off)
|
62
|
+
@sm.run
|
63
|
+
|
64
|
+
@sm.flip
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "Turn Stile" do
|
4
|
+
include TurnstileStateMachine
|
5
|
+
|
6
|
+
setup do
|
7
|
+
create_turnstile
|
8
|
+
|
9
|
+
@out_out_order = false
|
10
|
+
@sm.add(:operative, :maintain, :maintenance, Proc.new { @out_of_order = true } )
|
11
|
+
@sm.add(:maintenance, :operate, :operative, Proc.new { @out_of_order = false } )
|
12
|
+
@sm[:operative].add_substates(:locked, :unlocked)
|
13
|
+
|
14
|
+
@sm.run
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "substates respond to superstate transitions" do
|
18
|
+
@sm.process_event(:maintain)
|
19
|
+
@sm.state.id.should_be :maintenance
|
20
|
+
@locked.should_be true
|
21
|
+
@out_of_order.should_be true
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "after transitions, substates respond to superstate transitions" do
|
25
|
+
@sm.coin
|
26
|
+
@sm.maintain
|
27
|
+
@sm.state.id.should_be :maintenance
|
28
|
+
@locked.should_be false
|
29
|
+
@out_of_order.should_be true
|
30
|
+
end
|
31
|
+
|
32
|
+
specify "transitions back to superstate go to history state" do
|
33
|
+
@sm[:operative].use_history
|
34
|
+
@sm.maintain
|
35
|
+
@sm.operate
|
36
|
+
@sm.state.id.should_be :locked
|
37
|
+
@out_of_order.should_be false
|
38
|
+
|
39
|
+
@sm.coin
|
40
|
+
@sm.maintain
|
41
|
+
@sm.operate
|
42
|
+
@sm.state.id.should_be :unlocked
|
43
|
+
end
|
44
|
+
|
45
|
+
specify "missing substates are added" do
|
46
|
+
@sm[:operative].add_substates(:blah)
|
47
|
+
@sm[:blah].should_not_be nil
|
48
|
+
@sm[:blah].superstate.id.should_be :operative
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "recursive superstates not allowed" do
|
52
|
+
begin
|
53
|
+
@sm[:operative].add_substates(:operative)
|
54
|
+
self.should_fail_with_message("exception expected")
|
55
|
+
rescue StateMachine::StateMachineException => e
|
56
|
+
e.message.should_equal "Cyclic substates not allowed. (operative)"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "recursive superstates (2 levels) not allowed" do
|
61
|
+
begin
|
62
|
+
@sm[:operative].add_substates(:blah)
|
63
|
+
@sm[:blah].add_substates(:operative)
|
64
|
+
self.should_fail_with_message("exception expected")
|
65
|
+
rescue StateMachine::StateMachineException => e
|
66
|
+
e.message.should_equal "Cyclic substates not allowed. (blah)"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
specify "exception when add_substates called without args" do
|
71
|
+
begin
|
72
|
+
@sm[:locked].add_substates()
|
73
|
+
self.should_fail_with_message("exception expected")
|
74
|
+
rescue StateMachine::StateMachineException => e
|
75
|
+
e.message.should_equal "At least one parameter is required for add_substates."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "Turn Stile" do
|
4
|
+
include TurnstileStateMachine
|
5
|
+
|
6
|
+
setup do
|
7
|
+
create_turnstile
|
8
|
+
@sm.run
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "connections" do
|
12
|
+
@sm.states.length.should_be 2
|
13
|
+
locked_state = @sm[:locked]
|
14
|
+
unlocked_state = @sm[:unlocked]
|
15
|
+
|
16
|
+
locked_state.transitions.length.should_be 2
|
17
|
+
unlocked_state.transitions.length.should_be 2
|
18
|
+
|
19
|
+
check_transition(locked_state[:coin], :locked, :unlocked, :coin, @unlock)
|
20
|
+
check_transition(locked_state[:pass], :locked, :locked, :pass, @alarm)
|
21
|
+
check_transition(unlocked_state[:pass], :unlocked, :locked, :pass, @lock)
|
22
|
+
check_transition(unlocked_state[:coin], :unlocked, :locked, :coin, @thankyou)
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "start state" do
|
26
|
+
@sm.run
|
27
|
+
@sm.start_state.should.be @sm[:locked]
|
28
|
+
@sm.state.should.be @sm[:locked]
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "bad event" do
|
32
|
+
begin
|
33
|
+
@sm.process_event(:blah)
|
34
|
+
self.should.fail_with_message("Exception expected")
|
35
|
+
rescue Exception => e
|
36
|
+
e.class.should.be StateMachine::MissingTransitionException
|
37
|
+
e.to_s.should_equal "'locked' state does not respond to the 'blah' event."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "locked state with a coin" do
|
42
|
+
@sm.process_event(:coin)
|
43
|
+
|
44
|
+
@sm.state.should.be @sm[:unlocked]
|
45
|
+
@locked.should.be false
|
46
|
+
end
|
47
|
+
|
48
|
+
specify "locked state with pass event" do
|
49
|
+
@sm.process_event(:pass)
|
50
|
+
|
51
|
+
@sm.state.should.be @sm[:locked]
|
52
|
+
@locked.should.be true
|
53
|
+
@alarm.should.be true
|
54
|
+
end
|
55
|
+
|
56
|
+
specify "unlocked state with coin" do
|
57
|
+
@sm.process_event(:coin)
|
58
|
+
@sm.process_event(:coin)
|
59
|
+
|
60
|
+
@sm.state.should.be @sm[:locked]
|
61
|
+
@thankyou_status.should.be true
|
62
|
+
end
|
63
|
+
|
64
|
+
specify "unlocked state with pass event" do
|
65
|
+
@sm.process_event(:coin)
|
66
|
+
@sm.process_event(:pass)
|
67
|
+
|
68
|
+
@sm.state.should.be @sm[:locked]
|
69
|
+
@locked.should.be true
|
70
|
+
end
|
71
|
+
|
72
|
+
specify "events invoked via method_missing" do
|
73
|
+
@sm.coin
|
74
|
+
@sm.state.should.be @sm[:unlocked]
|
75
|
+
@sm.pass
|
76
|
+
@sm.state.should.be @sm[:locked]
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/state_machine'
|
2
|
+
|
3
|
+
def check_transition(transition, origin_id, destination_id, event, action)
|
4
|
+
transition.should_not_be nil
|
5
|
+
transition.event.should_be event
|
6
|
+
transition.action.should_be action
|
7
|
+
transition.origin.id.should_be origin_id
|
8
|
+
transition.destination.id.should_be destination_id
|
9
|
+
end
|
10
|
+
|
11
|
+
module SwitchStateMachine
|
12
|
+
|
13
|
+
def create_switch
|
14
|
+
@status = "off"
|
15
|
+
@sm = StateMachine::StateMachine.new
|
16
|
+
@sm.add(:off, :toggle, :on, Proc.new { @status = "on" } )
|
17
|
+
@sm.add(:on, :toggle, :off, Proc.new { @status = "off" } )
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module TurnstileStateMachine
|
23
|
+
|
24
|
+
def create_turnstile
|
25
|
+
@locked = true
|
26
|
+
@alarm_status = false
|
27
|
+
@thankyou_status = false
|
28
|
+
@lock = Proc.new { @locked = true }
|
29
|
+
@unlock = Proc.new { @locked = false }
|
30
|
+
@alarm = Proc.new { @alarm_status = true }
|
31
|
+
@thankyou = Proc.new { @thankyou_status = true }
|
32
|
+
|
33
|
+
@sm = StateMachine::StateMachine.new
|
34
|
+
@sm.add(:locked, :coin, :unlocked, @unlock)
|
35
|
+
@sm.add(:unlocked, :pass, :locked, @lock)
|
36
|
+
@sm.add(:locked, :pass, :locked, @alarm)
|
37
|
+
@sm.add(:unlocked, :coin, :locked, @thankyou)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/state_machine'
|
2
|
+
|
3
|
+
context "Transition Calculating Exits and Entries" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@a = StateMachine::State.new("a", nil)
|
7
|
+
@b = StateMachine::State.new("b", nil)
|
8
|
+
@c = StateMachine::State.new("c", nil)
|
9
|
+
@d = StateMachine::State.new("d", nil)
|
10
|
+
@e = StateMachine::State.new("e", nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "to nil" do
|
14
|
+
transition = StateMachine::Transition.new(@a, nil, nil, nil)
|
15
|
+
exits, entries = transition.exits_and_entries(@a)
|
16
|
+
exits.to_s.should_equal [@a].to_s
|
17
|
+
entries.to_s.should_equal [].to_s
|
18
|
+
entries.length.should_be 0
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "to itself" do
|
22
|
+
transition = StateMachine::Transition.new(@a, @a, nil, nil)
|
23
|
+
exits, entries = transition.exits_and_entries(@a)
|
24
|
+
exits.to_s.should_equal [@a].to_s
|
25
|
+
entries.to_s.should_equal [@a].to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "to friend" do
|
29
|
+
transition = StateMachine::Transition.new(@a, @b, nil, nil)
|
30
|
+
exits, entries = transition.exits_and_entries(@a)
|
31
|
+
exits.to_s.should_equal [@a].to_s
|
32
|
+
entries.to_s.should_equal [@b].to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
specify "to parent" do
|
36
|
+
@a.superstate = @b
|
37
|
+
transition = StateMachine::Transition.new(@a, @b, nil, nil)
|
38
|
+
exits, entries = transition.exits_and_entries(@a)
|
39
|
+
exits.to_s.should_equal [@a, @b].to_s
|
40
|
+
entries.to_s.should_equal [@b].to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "to uncle" do
|
44
|
+
@a.superstate = @b
|
45
|
+
transition = StateMachine::Transition.new(@a, @c, nil, nil)
|
46
|
+
exits, entries = transition.exits_and_entries(@a)
|
47
|
+
exits.to_s.should_equal [@a, @b].to_s
|
48
|
+
entries.to_s.should_equal [@c].to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "to cousin" do
|
52
|
+
@a.superstate = @b
|
53
|
+
@c.superstate = @d
|
54
|
+
transition = StateMachine::Transition.new(@a, @c, nil, nil)
|
55
|
+
exits, entries = transition.exits_and_entries(@a)
|
56
|
+
exits.to_s.should_equal [@a, @b].to_s
|
57
|
+
entries.to_s.should_equal [@d, @c].to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "to nephew" do
|
61
|
+
@a.superstate = @b
|
62
|
+
transition = StateMachine::Transition.new(@c, @a, nil, nil)
|
63
|
+
exits, entries = transition.exits_and_entries(@c)
|
64
|
+
exits.to_s.should_equal [@c].to_s
|
65
|
+
entries.to_s.should_equal [@b,@a].to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
specify "to sister" do
|
69
|
+
@a.superstate = @c
|
70
|
+
@b.superstate = @c
|
71
|
+
transition = StateMachine::Transition.new(@a, @b, nil, nil)
|
72
|
+
exits, entries = transition.exits_and_entries(@a)
|
73
|
+
exits.to_s.should_equal [@a].to_s
|
74
|
+
entries.to_s.should_equal [@b].to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
specify "to second cousin" do
|
78
|
+
@a.superstate = @b
|
79
|
+
@b.superstate = @c
|
80
|
+
@d.superstate = @e
|
81
|
+
@e.superstate = @c
|
82
|
+
transition = StateMachine::Transition.new(@a, @d, nil, nil)
|
83
|
+
exits, entries = transition.exits_and_entries(@a)
|
84
|
+
exits.to_s.should_equal [@a, @b].to_s
|
85
|
+
entries.to_s.should_equal [@e, @d].to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
specify "to grandparent" do
|
89
|
+
@a.superstate = @b
|
90
|
+
@b.superstate = @c
|
91
|
+
transition = StateMachine::Transition.new(@a, @c, nil, nil)
|
92
|
+
exits, entries = transition.exits_and_entries(@a)
|
93
|
+
exits.to_s.should_equal [@a, @b, @c].to_s
|
94
|
+
entries.to_s.should_equal [@c].to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: statemachine
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.0
|
7
|
+
date: 2006-10-25 00:00:00 -05:00
|
8
|
+
summary: RSpec-0.6.4 - BDD for Ruby http://rspec.rubyforge.org/
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: statemachine-devel@rubyforge.org
|
12
|
+
homepage: http://statemachine.rubyforge.org
|
13
|
+
rubyforge_project: statemachine
|
14
|
+
description: StateMachine is a ruby library for building Finite State Machines (FSM), also known as Finite State Automata (FSA).
|
15
|
+
autorequire: statemachine
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- - Micah Martin
|
30
|
+
files:
|
31
|
+
- CHANGES
|
32
|
+
- Rakefile
|
33
|
+
- lib/proc_calling.rb
|
34
|
+
- lib/state.rb
|
35
|
+
- lib/state_machine.rb
|
36
|
+
- lib/statemachine.rb
|
37
|
+
- lib/super_state.rb
|
38
|
+
- lib/transition.rb
|
39
|
+
- spec/sm_action_parameterization_spec.rb
|
40
|
+
- spec/sm_entry_exit_actions_spec.rb
|
41
|
+
- spec/sm_odds_n_ends_spec.rb
|
42
|
+
- spec/sm_simple_spec.rb
|
43
|
+
- spec/sm_super_state_spec.rb
|
44
|
+
- spec/sm_turnstile_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
- spec/transition_spec.rb
|
47
|
+
test_files:
|
48
|
+
- spec/sm_action_parameterization_spec.rb
|
49
|
+
- spec/sm_entry_exit_actions_spec.rb
|
50
|
+
- spec/sm_odds_n_ends_spec.rb
|
51
|
+
- spec/sm_simple_spec.rb
|
52
|
+
- spec/sm_super_state_spec.rb
|
53
|
+
- spec/sm_turnstile_spec.rb
|
54
|
+
- spec/transition_spec.rb
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
executables: []
|
60
|
+
|
61
|
+
extensions: []
|
62
|
+
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
dependencies: []
|
66
|
+
|