state-fu 0.11.1
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/LICENSE +40 -0
- data/README.textile +293 -0
- data/Rakefile +114 -0
- data/lib/binding.rb +292 -0
- data/lib/event.rb +192 -0
- data/lib/executioner.rb +120 -0
- data/lib/hooks.rb +39 -0
- data/lib/interface.rb +132 -0
- data/lib/lathe.rb +538 -0
- data/lib/machine.rb +184 -0
- data/lib/method_factory.rb +243 -0
- data/lib/persistence.rb +116 -0
- data/lib/persistence/active_record.rb +34 -0
- data/lib/persistence/attribute.rb +47 -0
- data/lib/persistence/base.rb +100 -0
- data/lib/persistence/relaxdb.rb +23 -0
- data/lib/persistence/session.rb +7 -0
- data/lib/sprocket.rb +58 -0
- data/lib/state-fu.rb +56 -0
- data/lib/state.rb +48 -0
- data/lib/support/active_support_lite/array.rb +9 -0
- data/lib/support/active_support_lite/array/access.rb +60 -0
- data/lib/support/active_support_lite/array/conversions.rb +202 -0
- data/lib/support/active_support_lite/array/extract_options.rb +21 -0
- data/lib/support/active_support_lite/array/grouping.rb +109 -0
- data/lib/support/active_support_lite/array/random_access.rb +13 -0
- data/lib/support/active_support_lite/array/wrapper.rb +25 -0
- data/lib/support/active_support_lite/blank.rb +67 -0
- data/lib/support/active_support_lite/cattr_reader.rb +57 -0
- data/lib/support/active_support_lite/keys.rb +57 -0
- data/lib/support/active_support_lite/misc.rb +59 -0
- data/lib/support/active_support_lite/module.rb +1 -0
- data/lib/support/active_support_lite/module/delegation.rb +130 -0
- data/lib/support/active_support_lite/object.rb +9 -0
- data/lib/support/active_support_lite/string.rb +38 -0
- data/lib/support/active_support_lite/symbol.rb +16 -0
- data/lib/support/applicable.rb +41 -0
- data/lib/support/arrays.rb +197 -0
- data/lib/support/core_ext.rb +90 -0
- data/lib/support/exceptions.rb +106 -0
- data/lib/support/has_options.rb +16 -0
- data/lib/support/logger.rb +165 -0
- data/lib/support/methodical.rb +17 -0
- data/lib/support/no_stdout.rb +55 -0
- data/lib/support/plotter.rb +62 -0
- data/lib/support/vizier.rb +300 -0
- data/lib/tasks/spec_last.rake +55 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/transition.rb +338 -0
- data/lib/transition_query.rb +224 -0
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +118 -0
- data/spec/features/plotter_spec.rb +97 -0
- data/spec/features/shared_log_spec.rb +7 -0
- data/spec/features/singleton_machine_spec.rb +39 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison_spec.rb +101 -0
- data/spec/helper.rb +13 -0
- data/spec/integration/active_record_persistence_spec.rb +202 -0
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/class_accessor_spec.rb +117 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/example_01_document_spec.rb +133 -0
- data/spec/integration/example_02_string_spec.rb +88 -0
- data/spec/integration/instance_accessor_spec.rb +97 -0
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/machine_duplication_spec.rb +101 -0
- data/spec/integration/relaxdb_persistence_spec.rb +97 -0
- data/spec/integration/requirement_reflection_spec.rb +270 -0
- data/spec/integration/state_definition_spec.rb +163 -0
- data/spec/integration/transition_spec.rb +1033 -0
- data/spec/spec.opts +9 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +948 -0
- data/spec/units/binding_spec.rb +192 -0
- data/spec/units/event_spec.rb +214 -0
- data/spec/units/exceptions_spec.rb +82 -0
- data/spec/units/lathe_spec.rb +570 -0
- data/spec/units/machine_spec.rb +229 -0
- data/spec/units/method_factory_spec.rb +366 -0
- data/spec/units/sprocket_spec.rb +69 -0
- data/spec/units/state_spec.rb +59 -0
- metadata +171 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec/runner/formatter/progress_bar_formatter'
|
2
|
+
class CustomFormatter < Spec::Runner::Formatter::ProgressBarFormatter
|
3
|
+
def add_line(l)
|
4
|
+
(@lines||=[]) << l
|
5
|
+
end
|
6
|
+
|
7
|
+
def dump_pending
|
8
|
+
unless @pending_examples.empty?
|
9
|
+
lpad = @pending_examples.map{|e|e[2].length}.max
|
10
|
+
@output.puts
|
11
|
+
@output.puts "Pending: #{@pending_examples.length}"
|
12
|
+
@pending_examples.each do |pending_example|
|
13
|
+
@output.puts yellow("#{pending_example[2].strip.ljust(lpad)} # - #{pending_example[1]}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
@output.flush
|
17
|
+
end
|
18
|
+
|
19
|
+
# def example_failed(example, counter, failure)
|
20
|
+
# failure.instance_eval do
|
21
|
+
# (class<<self;self;end).class_eval { attr_accessor :location }
|
22
|
+
# end
|
23
|
+
# failure.location = example.location
|
24
|
+
# super(example,counter,failure)
|
25
|
+
# end
|
26
|
+
|
27
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
28
|
+
if @lines
|
29
|
+
@output.puts "="*72
|
30
|
+
@lines.each do |line|
|
31
|
+
@output.puts line
|
32
|
+
end
|
33
|
+
@output.puts "="*72
|
34
|
+
end
|
35
|
+
super(duration, example_count, failure_count, pending_count)
|
36
|
+
end
|
37
|
+
|
38
|
+
def dump_failure(counter, failure)
|
39
|
+
@output.puts
|
40
|
+
@output.puts "#{counter.to_s})"
|
41
|
+
# @output.puts failure.location
|
42
|
+
@output.puts colorize_failure("#{failure.header}\n#{failure.exception.message}", failure.inspect)
|
43
|
+
@output.puts format_backtrace(failure.exception.backtrace)
|
44
|
+
#failure.exception
|
45
|
+
line = failure.exception.backtrace.last rescue failure.exception.inspect
|
46
|
+
add_line line
|
47
|
+
@output.flush
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
module MySpecHelper
|
4
|
+
module BindingExampleHelper
|
5
|
+
|
6
|
+
attr_accessor :ok
|
7
|
+
|
8
|
+
def helper_method
|
9
|
+
end
|
10
|
+
|
11
|
+
def requirement_satisfier?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def requirement_satisfier_with_arg?( t )
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module OtherExampleHelper
|
20
|
+
def other_helper_method
|
21
|
+
end
|
22
|
+
|
23
|
+
def other_requirement_satisfier?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "extending bindings and transitions with Lathe#helper" do
|
30
|
+
|
31
|
+
include MySpecHelper
|
32
|
+
|
33
|
+
before(:each) do
|
34
|
+
reset!
|
35
|
+
make_pristine_class('Klass')
|
36
|
+
Klass.class_eval do
|
37
|
+
attr_accessor :ok
|
38
|
+
end
|
39
|
+
|
40
|
+
@machine = Klass.state_fu_machine do
|
41
|
+
helper MySpecHelper::BindingExampleHelper
|
42
|
+
helper 'my_spec_helper/other_example_helper'
|
43
|
+
|
44
|
+
chain "a -a2b-> b -b2c-> c"
|
45
|
+
|
46
|
+
events.each do |e|
|
47
|
+
e.requires :requirement_satisfier?
|
48
|
+
e.requires :requirement_satisfier_with_arg?
|
49
|
+
e.requires :other_requirement_satisfier?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@other_machine = Klass.state_fu_machine(:other) do
|
54
|
+
helper ::MySpecHelper::OtherExampleHelper
|
55
|
+
end
|
56
|
+
@obj = Klass.new
|
57
|
+
@binding = @obj.state_fu
|
58
|
+
@other_binding = @obj.other
|
59
|
+
@transition = @obj.state_fu.transition(:a2b)
|
60
|
+
end # before
|
61
|
+
|
62
|
+
#
|
63
|
+
#
|
64
|
+
|
65
|
+
describe "binding" do
|
66
|
+
describe "instance methods" do
|
67
|
+
|
68
|
+
it "should respond to helper_method" do
|
69
|
+
@binding.should respond_to( :helper_method)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
it "should respond to other_helper_method" do
|
74
|
+
@binding.should respond_to( :other_helper_method)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should respond to requirement_satisfier?" do
|
78
|
+
@binding.should respond_to( :requirement_satisfier?)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should respond to other_requirement_satisfier?" do
|
82
|
+
@binding.should respond_to( :other_requirement_satisfier?)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "transition" do
|
89
|
+
describe "instance methods" do
|
90
|
+
|
91
|
+
it "should respond to helper_method" do
|
92
|
+
@transition.should respond_to( :helper_method)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should respond to other_helper_method" do
|
96
|
+
@transition.should respond_to( :other_helper_method)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should respond to requirement_satisfier?" do
|
100
|
+
@transition.should respond_to( :requirement_satisfier?)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should respond to other_requirement_satisfier?" do
|
104
|
+
@transition.should respond_to( :other_requirement_satisfier?)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
describe "method_missing" do
|
4
|
+
include MySpecHelper
|
5
|
+
before do
|
6
|
+
make_pristine_class('Klass')
|
7
|
+
Klass.state_fu_machine() {}
|
8
|
+
@obj = Klass.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should revert to the original method_missing after it is called once" do
|
12
|
+
mock.proxy( @obj ).state_fu!.times(1)
|
13
|
+
mm1 = @obj.method(:method_missing)
|
14
|
+
call_snafu = lambda do
|
15
|
+
begin
|
16
|
+
@obj.snafu!
|
17
|
+
rescue NoMethodError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
call_snafu.call()
|
21
|
+
mm2 = @obj.method(:method_missing)
|
22
|
+
mm1.should_not == mm2
|
23
|
+
call_snafu.call()
|
24
|
+
mm3 = @obj.method(:method_missing)
|
25
|
+
mm3.should == mm2
|
26
|
+
# @obj.snafu
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
module RequirementFeatureHelper
|
4
|
+
def account_expired?
|
5
|
+
!! account_expired
|
6
|
+
end
|
7
|
+
|
8
|
+
def valid_password?
|
9
|
+
!! valid_password
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# it_should_behave_like "!" do
|
14
|
+
shared_examples_for "not requirements" do
|
15
|
+
describe "requirements with names beginning with no[t]_" do
|
16
|
+
|
17
|
+
it "should return the opposite of the requirement name without not_" do
|
18
|
+
# @obj.current_state.should == :guest
|
19
|
+
@obj.stfu.teleport! :anonymous
|
20
|
+
@obj.valid_password = false
|
21
|
+
@binding.can_has_valid_password?.should == false
|
22
|
+
@binding.can_has_not_valid_password?.should == true
|
23
|
+
@binding.can_has_no_valid_password?.should == true
|
24
|
+
@obj.valid_password = true
|
25
|
+
@binding.can_has_valid_password?.should == true
|
26
|
+
@binding.can_has_not_valid_password?.should == false
|
27
|
+
@binding.can_has_no_valid_password?.should == false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should call the method directly if one exists" do
|
31
|
+
@obj.valid_password = true
|
32
|
+
(class << @obj; self; end).class_eval do
|
33
|
+
define_method( :no_valid_password? ) { true }
|
34
|
+
end
|
35
|
+
@binding.can_has_valid_password?.should == true
|
36
|
+
@binding.can_has_not_valid_password?.should == false
|
37
|
+
@binding.can_has_no_valid_password?.should == true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "requirements" do
|
45
|
+
before(:all) do
|
46
|
+
reset!
|
47
|
+
make_pristine_class('Klass')
|
48
|
+
Klass.class_eval do
|
49
|
+
attr_accessor :valid_password
|
50
|
+
attr_accessor :account_expired
|
51
|
+
end
|
52
|
+
@machine = StateFu::Machine.new do
|
53
|
+
initial_state :guest
|
54
|
+
|
55
|
+
event :has_valid_password, :from => :anonymous, :to => :logged_in do
|
56
|
+
requires :valid_password?
|
57
|
+
end
|
58
|
+
|
59
|
+
event :has_not_valid_password, :from => :anonymous, :to => :suspect do
|
60
|
+
requires :not_valid_password?
|
61
|
+
end
|
62
|
+
|
63
|
+
event :has_no_valid_password, :from => :anonymous, :to => :suspect do
|
64
|
+
requires :no_valid_password?
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
before :each do
|
71
|
+
@obj.valid_password = true
|
72
|
+
@obj.account_expired = false
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "requirements defined with a machine helper" do
|
76
|
+
before :all do
|
77
|
+
@machine.lathe { helper RequirementFeatureHelper }
|
78
|
+
@machine.bind!(Klass, :default)
|
79
|
+
@obj = Klass.new
|
80
|
+
@binding = @obj.state_fu
|
81
|
+
end
|
82
|
+
|
83
|
+
it_should_behave_like "not requirements"
|
84
|
+
|
85
|
+
it "should not have methods on the object" do
|
86
|
+
@obj.respond_to?(:valid_password?).should == false
|
87
|
+
@obj.respond_to?(:account_expired?).should == false
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should have methods on the binding" do
|
91
|
+
# this is a little misleading because theyre not evaluated on the binding ..
|
92
|
+
@binding.respond_to?(:valid_password?).should == true
|
93
|
+
@binding.respond_to?(:account_expired?).should == true
|
94
|
+
@binding.respond_to?(:not_valid_password?).should == false
|
95
|
+
@binding.respond_to?(:not_account_expired?).should == false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "requirements defined on the object" do
|
100
|
+
before :all do
|
101
|
+
@machine.bind!(Klass, :default)
|
102
|
+
@obj = Klass.new
|
103
|
+
@binding = @obj.state_fu
|
104
|
+
Klass.class_eval do
|
105
|
+
include RequirementFeatureHelper
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it_should_behave_like "not requirements"
|
110
|
+
|
111
|
+
it "should have methods on the object" do
|
112
|
+
@obj.respond_to?(:valid_password?).should == true
|
113
|
+
@obj.respond_to?(:not_valid_password?).should == false
|
114
|
+
@obj.respond_to?(:account_expired?).should == true
|
115
|
+
@obj.respond_to?(:not_account_expired?).should == false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
##
|
4
|
+
##
|
5
|
+
##
|
6
|
+
|
7
|
+
describe StateFu::Plotter do
|
8
|
+
include MySpecHelper
|
9
|
+
before do
|
10
|
+
reset!
|
11
|
+
make_pristine_class('Klass')
|
12
|
+
@machine = Klass.state_fu_machine(:drawme) do
|
13
|
+
chain 'clean -tarnish-> dirty -fester-> putrid'
|
14
|
+
end
|
15
|
+
@machine = Klass.state_fu_machine(:drawme)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
describe "class methods" do
|
20
|
+
describe ".new" do
|
21
|
+
it "should expect a StateFu::Machine and return a Plotter" do
|
22
|
+
@plotter = StateFu::Plotter.new( @machine )
|
23
|
+
@plotter.should be_kind_of(StateFu::Plotter)
|
24
|
+
@plotter.machine.should == @machine
|
25
|
+
lambda { StateFu::Plotter.new( "abracadabra" ) }.should raise_error(RuntimeError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "a new plotter" do
|
30
|
+
before do
|
31
|
+
@plotter = StateFu::Plotter.new( @machine )
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have an empty hash of states" do
|
35
|
+
@plotter = StateFu::Plotter.new( @machine )
|
36
|
+
@plotter.states.should == {}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end # class methods
|
41
|
+
|
42
|
+
describe "instance methods" do
|
43
|
+
before do
|
44
|
+
@plotter = StateFu::Plotter.new( @machine )
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".generate" do
|
48
|
+
|
49
|
+
it "should call generate_dot!" do
|
50
|
+
mock( @plotter ).generate_dot!() { "dot" }
|
51
|
+
@plotter.generate
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should store the result in the dot attribute" do
|
55
|
+
mock( @plotter).generate_dot!() { "dot" }
|
56
|
+
@plotter.generate
|
57
|
+
@plotter.dot.should == "dot"
|
58
|
+
end
|
59
|
+
|
60
|
+
describe ".save_as(filename)" do
|
61
|
+
it "should save the string to a file" do
|
62
|
+
mock( File).open( 'filename', 'w' ).yields( @fh = Object.new() )
|
63
|
+
mock( @fh ).write( @plotter.output )
|
64
|
+
@plotter.output.save_as( 'filename' )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe ".save!" do
|
69
|
+
it "should save the string in a tempfile and return the path" do
|
70
|
+
mock(@tempfile = Object.new).path {"path"}.subject
|
71
|
+
mock(Tempfile).new(['state_fu_graph','.dot']).yields( @fh = Object.new() ) { @tempfile }
|
72
|
+
mock( @fh ).write( @plotter.output )
|
73
|
+
@plotter.output.save!.should == 'path'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end # instance methods
|
77
|
+
|
78
|
+
describe "output" do
|
79
|
+
it "should return the result of .generate" do
|
80
|
+
@plotter.output.should == @plotter.generate
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "generate_dot!" do
|
85
|
+
it "should return a string" do
|
86
|
+
@plotter.generate_dot!.should be_kind_of(String)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should extend the string to respond_to save_as" do
|
90
|
+
@plotter.output.should respond_to(:save_as)
|
91
|
+
end
|
92
|
+
end # output
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
describe "singleton machines" do
|
4
|
+
before do
|
5
|
+
make_pristine_class('Klass')
|
6
|
+
@m = StateFu::Machine.new do
|
7
|
+
state :a do
|
8
|
+
event :beatify, :transitions_to => :b
|
9
|
+
end
|
10
|
+
end
|
11
|
+
@m.events.length.should == 1
|
12
|
+
@obj = Klass.new
|
13
|
+
@m.bind!( @obj, :my_binding)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return a binding to the machine when calling the binding's name" do
|
17
|
+
@obj.should respond_to(:my_binding)
|
18
|
+
@obj.my_binding.should be_kind_of(StateFu::Binding)
|
19
|
+
@obj.my_binding.machine.should == @m
|
20
|
+
@obj.my_binding.object.should == @obj
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have event methods defined" do
|
24
|
+
%w/beatify can_beatify? beatify!/.each do |method_name|
|
25
|
+
@obj.my_binding.should respond_to(method_name)
|
26
|
+
@obj.should respond_to(method_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should transition" do
|
31
|
+
@b = @obj.my_binding
|
32
|
+
@b.current_state.should == :a
|
33
|
+
t = @obj.beatify!
|
34
|
+
t.should be_kind_of(StateFu::Transition)
|
35
|
+
t.should be_accepted
|
36
|
+
@b.current_state.should == :b
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|