state-fu 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|