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