reduxco 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +24 -0
- data/README.rdoc +126 -0
- data/Rakefile +19 -0
- data/lib/reduxco.rb +10 -0
- data/lib/reduxco/callable_ref.rb +142 -0
- data/lib/reduxco/context.rb +243 -0
- data/lib/reduxco/context/callable_table.rb +81 -0
- data/lib/reduxco/context/callstack.rb +74 -0
- data/lib/reduxco/reduxer.rb +41 -0
- data/lib/reduxco/version.rb +4 -0
- data/spec/callable_ref_spec.rb +273 -0
- data/spec/callable_table_spec.rb +174 -0
- data/spec/callstack_spec.rb +128 -0
- data/spec/context_spec.rb +619 -0
- data/spec/rdoc_examples_spec.rb +46 -0
- data/spec/reduxer_spec.rb +88 -0
- data/spec/spec_helper.rb +17 -0
- metadata +69 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reduxco::Context::CallableTable do
|
4
|
+
|
5
|
+
let(:identity_dynref) { Reduxco::CallableRef.new(:identity) }
|
6
|
+
let(:identity_callable) { ->(c){c} }
|
7
|
+
let(:app_callable) { ->(c){c[:identity]} }
|
8
|
+
|
9
|
+
it 'should instantiate with an array of maps' do
|
10
|
+
table = Reduxco::Context::CallableTable.new( [{:identity => identity_callable}, {:app => app_callable}] )
|
11
|
+
table.should include(identity_dynref)
|
12
|
+
table.resolve(identity_dynref).should_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should instantiate with an array of arrays of arrays' do
|
16
|
+
table = Reduxco::Context::CallableTable.new( [[[:identity, identity_callable]], [[:app, app_callable]]] )
|
17
|
+
table.should include(identity_dynref)
|
18
|
+
table.resolve(identity_dynref).should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'introspection' do
|
22
|
+
|
23
|
+
let(:example_callable) { ->(c){c} }
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
map1 = {:moho => example_callable, :eve => example_callable}
|
27
|
+
map2 = {:eve => example_callable, :kerbin => example_callable}
|
28
|
+
@table = Reduxco::Context::CallableTable.new([map1,map2])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should introspect existence of static refs' do
|
32
|
+
@table.should include(Reduxco::CallableRef.new(:moho,1))
|
33
|
+
@table.should include(Reduxco::CallableRef.new(:eve,1))
|
34
|
+
@table.should include(Reduxco::CallableRef.new(:eve,2))
|
35
|
+
@table.should include(Reduxco::CallableRef.new(:kerbin,2))
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should introspect existence of dynamic refs' do
|
39
|
+
@table.should include(Reduxco::CallableRef.new(:moho))
|
40
|
+
@table.should include(Reduxco::CallableRef.new(:eve))
|
41
|
+
@table.should include(Reduxco::CallableRef.new(:kerbin))
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should introspect the non-existance of static refs' do
|
45
|
+
@table.should_not include(Reduxco::CallableRef.new(:duna,1))
|
46
|
+
@table.should_not include(Reduxco::CallableRef.new(:moho,2))
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should introspect the non-existance of dynamic refs' do
|
50
|
+
@table.should_not include(Reduxco::CallableRef.new(:duna))
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'resolution' do
|
56
|
+
|
57
|
+
before(:each) do
|
58
|
+
@map1 = {moho: ->(c){1}, eve: ->(c){2}, jool: ->(c){5}}
|
59
|
+
@map2 = {eve: ->(c){'second'}, kerbin: ->(c){3}}
|
60
|
+
@map3 = {jool: ->(c){'5th'}}
|
61
|
+
@table = Reduxco::Context::CallableTable.new([@map1,@map2,@map3])
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:context) {double('context')}
|
65
|
+
|
66
|
+
it 'should resolve top level static ref' do
|
67
|
+
@table.resolve(Reduxco::CallableRef.new(:moho,1)).tap do |callref, callable|
|
68
|
+
callref.should == Reduxco::CallableRef.new(:moho,1)
|
69
|
+
callable.call(context).should == 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should resolve a deeper level static ref' do
|
74
|
+
@table.resolve(Reduxco::CallableRef.new(:kerbin,2)).tap do |callref, callable|
|
75
|
+
callref.should == Reduxco::CallableRef.new(:kerbin,2)
|
76
|
+
callable.call(context).should == 3
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should resolve a static ref that shadows higher level one' do
|
81
|
+
@table.resolve(Reduxco::CallableRef.new(:eve,2)).tap do |callref, callable|
|
82
|
+
callref.should == Reduxco::CallableRef.new(:eve,2)
|
83
|
+
callable.call(context).should == 'second'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should resolve a shadowed static ref' do
|
88
|
+
@table.resolve(Reduxco::CallableRef.new(:eve,1)).tap do |callref, callable|
|
89
|
+
callref.should == Reduxco::CallableRef.new(:eve,1)
|
90
|
+
callable.call(context).should == 2
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should resolve simple (non-shadowed) dynamic refs to correct level' do
|
95
|
+
@table.resolve(Reduxco::CallableRef.new(:moho)).tap do |callref, callable|
|
96
|
+
callref.should == Reduxco::CallableRef.new(:moho,1)
|
97
|
+
callable.call(context).should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
@table.resolve(Reduxco::CallableRef.new(:kerbin)).tap do |callref, callable|
|
101
|
+
callref.should == Reduxco::CallableRef.new(:kerbin,2)
|
102
|
+
callable.call(context).should == 3
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should resolve a shadowed dynamic ref to the lowest level' do
|
107
|
+
@table.resolve(Reduxco::CallableRef.new(:eve)).tap do |callref, callable|
|
108
|
+
callref.should == Reduxco::CallableRef.new(:eve,2)
|
109
|
+
callable.call(context).should == 'second'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
it 'should return a callable value of nil if a static ref cannot be resolved' do
|
115
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo, 1)).tap do |callref, callable|
|
116
|
+
callref.should be_nil
|
117
|
+
callable.should be_nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should return a callable value of nil if a dynamic ref cannot be resolved' do
|
122
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo)).tap do |callref, callable|
|
123
|
+
callref.should be_nil
|
124
|
+
callable.should be_nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should return RESOLUTION_FAILURE on resolution failure' do
|
129
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo, 1)).should == Reduxco::Context::CallableTable::RESOLUTION_FAILURE
|
130
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo)).should == Reduxco::Context::CallableTable::RESOLUTION_FAILURE
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should give nil values for multiple assignment of RESOLUTION_FAILURE' do
|
134
|
+
callref, callable = Reduxco::Context::CallableTable::RESOLUTION_FAILURE
|
135
|
+
callref.should be_nil
|
136
|
+
callable.should be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should use :[] as an alias to :resolve' do
|
140
|
+
@table.method(:[]).should == @table.method(:resolve)
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'super' do
|
144
|
+
|
145
|
+
it 'should resolve super for a static ref' do
|
146
|
+
@table.resolve_super(Reduxco::CallableRef.new(:eve,2)).tap do |callref, callable|
|
147
|
+
callref.should == Reduxco::CallableRef.new(:eve,1)
|
148
|
+
callable.call(context).should == 2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should resolve super across depth gaps' do
|
153
|
+
@table.resolve_super(Reduxco::CallableRef.new(:jool,3)).tap do |callref, callable|
|
154
|
+
callref.should == Reduxco::CallableRef.new(:jool, 1)
|
155
|
+
callable.call(context).should == 5
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should return a callable value of nil if a super ref cannot be resolved' do
|
160
|
+
@table.resolve_super(Reduxco::CallableRef.new(:kerbin,2)).tap do |callref, callable|
|
161
|
+
callref.should be_nil
|
162
|
+
callable.should be_nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should raise an exception with a dynamic ref' do
|
167
|
+
->{ @table.resolve_super(Reduxco::CallableRef.new(:moho)) }.should raise_error(ArgumentError)
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reduxco::Context::Callstack do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@stack = Reduxco::Context::Callstack.new([:top, :middle, :bottom])
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should return the depth' do
|
10
|
+
@stack.depth.should == 3
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should get the top frame' do
|
14
|
+
@stack.top.should == :top
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should initialize as empty' do
|
18
|
+
stack = Reduxco::Context::Callstack.new
|
19
|
+
stack.depth.should == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be able to initialize with an array, top elem first' do
|
23
|
+
@stack.depth.should == 3
|
24
|
+
@stack.top.should == :top
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should pop' do
|
28
|
+
frame = @stack.pop
|
29
|
+
|
30
|
+
@stack.depth.should == 2
|
31
|
+
frame.should == :top
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should push' do
|
35
|
+
s = @stack.push(:new_top)
|
36
|
+
|
37
|
+
@stack.depth.should == 4
|
38
|
+
@stack.top.should == :new_top
|
39
|
+
|
40
|
+
s.object_id.should == @stack.object_id
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should check for frame inclusion' do
|
44
|
+
@stack.should include(:middle)
|
45
|
+
|
46
|
+
@stack.should_not include(:eeloo)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should return the rest of the stack' do
|
50
|
+
rest = @stack.rest
|
51
|
+
|
52
|
+
rest.depth.should == 2
|
53
|
+
rest.top.should == :middle
|
54
|
+
|
55
|
+
rest.should be_kind_of(@stack.class)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should return a properly structured copy of the stack' do
|
59
|
+
dup = @stack.dup
|
60
|
+
|
61
|
+
dup.should be_kind_of(@stack.class)
|
62
|
+
dup.object_id.should_not == @stack.object_id
|
63
|
+
|
64
|
+
dup.top.should == @stack.top
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should not mutate the copy of the stack out from under it' do
|
68
|
+
dup = @stack.dup
|
69
|
+
|
70
|
+
@stack.push(:moho)
|
71
|
+
dup.should_not include(:moho)
|
72
|
+
|
73
|
+
dup.push(:eve)
|
74
|
+
@stack.should_not include(:eve)
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'conversion and formatting' do
|
78
|
+
|
79
|
+
let(:frame) do
|
80
|
+
double('frame').tap do |f|
|
81
|
+
f.stub(:to_s) {'frame-string'}
|
82
|
+
f.stub(:inspect) {'frame-inspect'}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should output to_s with the top element first' do
|
87
|
+
str = @stack.to_s
|
88
|
+
|
89
|
+
str.index('top').should < str.index('bottom')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should inspect with the top element first' do
|
93
|
+
str = @stack.inspect
|
94
|
+
str.index('top').should < str.index('bottom')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should invoke inspect of frames when inspecting' do
|
98
|
+
@stack.push(frame)
|
99
|
+
|
100
|
+
frame.should_receive(:inspect)
|
101
|
+
str = @stack.inspect
|
102
|
+
|
103
|
+
str.should include('frame-inspect')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should convert to a caller-style callstack' do
|
107
|
+
cs = @stack.to_caller
|
108
|
+
|
109
|
+
cs.each {|tr| tr.should be_kind_of(String)}
|
110
|
+
cs.first.should include('top')
|
111
|
+
cs.last.should include('bottom')
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should prepend a callstack line into the caller-style callstack, conterting it to string' do
|
115
|
+
top = Object.new
|
116
|
+
cs = @stack.to_caller(top)
|
117
|
+
|
118
|
+
cs.each {|tr| tr.should be_kind_of(String)}
|
119
|
+
|
120
|
+
head, *tail = cs
|
121
|
+
|
122
|
+
head.should == top.to_s
|
123
|
+
tail.should == @stack.to_caller
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,619 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reduxco::Context do
|
4
|
+
|
5
|
+
let(:add_map) do
|
6
|
+
{
|
7
|
+
sum: ->(c){ c[:a]+c[:b] },
|
8
|
+
a: ->(c){3},
|
9
|
+
b: ->(c){5}
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:cyclical_map) do
|
14
|
+
{
|
15
|
+
a: ->(c){c[:b]},
|
16
|
+
b: ->(c){c[:c]},
|
17
|
+
c: ->(c){c[:a]}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:non_callable_map){ {moho:'foo'} }
|
22
|
+
|
23
|
+
let(:handler) do
|
24
|
+
{
|
25
|
+
handler: Proc.new do |c|
|
26
|
+
begin
|
27
|
+
c[:a]
|
28
|
+
rescue => e
|
29
|
+
[e, c.callstack]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'call' do
|
36
|
+
|
37
|
+
it 'should alias :[] to call' do
|
38
|
+
context = Reduxco::Context.new
|
39
|
+
context.method(:[]).should == context.method(:call)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should invoke the call method of the object it resolves to' do
|
43
|
+
callable = double('callable')
|
44
|
+
context = Reduxco::Context.new(:moho => callable)
|
45
|
+
|
46
|
+
callable.should_receive(:call).with(context)
|
47
|
+
|
48
|
+
context.call(:moho)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should resolve a leaf refname to a value' do
|
52
|
+
context = Reduxco::Context.new(add_map)
|
53
|
+
context.call(:a).should == 3
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should resolve a refname with dependencies to a value via cascading calculation' do
|
57
|
+
context = Reduxco::Context.new(add_map)
|
58
|
+
context.call(:sum).should == 8
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should resolve a callref' do
|
62
|
+
context = Reduxco::Context.new(add_map)
|
63
|
+
context.call( Reduxco::CallableRef.new(:sum) ).should == 8
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should resolve a callref to a shadowed value' do
|
67
|
+
context = Reduxco::Context.new(add_map, {sum: ->(c){-101}})
|
68
|
+
context.call( Reduxco::CallableRef.new(:sum, 1) ).should == 8
|
69
|
+
context.call( Reduxco::CallableRef.new(:sum, 2) ).should == -101
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should cache the results' do
|
73
|
+
context = Reduxco::Context.new(app: ->(c){ Object.new })
|
74
|
+
|
75
|
+
generated_object = context.call(:app)
|
76
|
+
context.call(:app).should == generated_object
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should allow yielding to a block.' do
|
80
|
+
context = Reduxco::Context.new(
|
81
|
+
app: ->(c){ c.call(:outter){|x| x*x} },
|
82
|
+
outter: ->(c){ c.yield(3) + c.yield(4) }
|
83
|
+
)
|
84
|
+
|
85
|
+
context.call(:app).should == 25
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'errors' do
|
89
|
+
|
90
|
+
it 'should error if refname resolves to a non-callable' do
|
91
|
+
context = Reduxco::Context.new(non_callable_map)
|
92
|
+
|
93
|
+
->{ context.call(:moho) }.should raise_error(Reduxco::Context::NotCallableError)
|
94
|
+
|
95
|
+
begin
|
96
|
+
context.call(:moho)
|
97
|
+
rescue => e
|
98
|
+
e.message.should include('moho')
|
99
|
+
e.backtrace.select {|tr| tr.include?('context.rb')}.should == []
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should error if refname is non-resolvable' do
|
104
|
+
context = Reduxco::Context.new(add_map)
|
105
|
+
|
106
|
+
->{ context.call(:eeloo) }.should raise_error(Reduxco::Context::NameError)
|
107
|
+
|
108
|
+
begin
|
109
|
+
context.call(:eeloo)
|
110
|
+
rescue => e
|
111
|
+
e.message.should include('eeloo')
|
112
|
+
e.backtrace.select {|tr| tr =~ /context.rb.+`call'$/}.should == []
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should error on cyclical dependencies' do
|
118
|
+
context = Reduxco::Context.new(cyclical_map)
|
119
|
+
|
120
|
+
->{ context.call(:a) }.should raise_error(Reduxco::Context::CyclicalError)
|
121
|
+
|
122
|
+
begin
|
123
|
+
context.call(:a)
|
124
|
+
rescue => e
|
125
|
+
e.message.should include('a:1')
|
126
|
+
e.message.should include('c:1')
|
127
|
+
e.backtrace.select {|tr| tr =~ /context.rb.+`call'$/}.should == []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should not corrupt the callstack when catching a non-resolvalbe name error' do
|
132
|
+
context = Reduxco::Context.new({a:->(c){c[:eeloo]}}, handler)
|
133
|
+
|
134
|
+
error, stack = context.call(:handler)
|
135
|
+
|
136
|
+
stack.top.should == Reduxco::CallableRef.new(:handler, 2)
|
137
|
+
stack.depth.should == 1
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should not corrupt the callstack when catching a cyclical dependency' do
|
141
|
+
context = Reduxco::Context.new(cyclical_map, handler)
|
142
|
+
|
143
|
+
error, stack = context.call(:handler)
|
144
|
+
|
145
|
+
stack.top.should == Reduxco::CallableRef.new(:handler, 2)
|
146
|
+
stack.depth.should == 1
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'super' do
|
154
|
+
|
155
|
+
it 'should resolve to the next previous ref of the same name' do
|
156
|
+
context = Reduxco::Context.new({
|
157
|
+
moho: ->(c){ 'top' }
|
158
|
+
},
|
159
|
+
{
|
160
|
+
moho: ->(c){ c.super }
|
161
|
+
})
|
162
|
+
|
163
|
+
context.call(:moho).should == 'top'
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should chain calls and their results' do
|
167
|
+
context = Reduxco::Context.new({
|
168
|
+
moho: ->(c){ 1 }
|
169
|
+
},
|
170
|
+
{
|
171
|
+
moho: ->(c){ c.super + 2 }
|
172
|
+
},
|
173
|
+
{
|
174
|
+
eve: ->(c){ 1024 }
|
175
|
+
},
|
176
|
+
{
|
177
|
+
moho: ->(c){ c.super + 4 }
|
178
|
+
})
|
179
|
+
|
180
|
+
context.call(:moho).should == 7
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should throw a NameError if super cannot be resolved' do
|
184
|
+
context = Reduxco::Context.new({
|
185
|
+
eve: ->(c){ 'eve' }
|
186
|
+
},
|
187
|
+
{
|
188
|
+
moho: ->(c){ c.super }
|
189
|
+
})
|
190
|
+
|
191
|
+
->{ context.call(:moho) }.should raise_error(Reduxco::Context::NameError)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should allow super to recieve a block to yield to' do
|
195
|
+
|
196
|
+
context = Reduxco::Context.new({
|
197
|
+
moho: ->(c){ c.yield(3) }
|
198
|
+
},
|
199
|
+
{
|
200
|
+
moho: ->(c){ c.super {|x| x*x} }
|
201
|
+
})
|
202
|
+
|
203
|
+
context.call(:moho).should == 9
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
it 'should forward the yield block to super' do
|
208
|
+
context = Reduxco::Context.new({
|
209
|
+
moho: ->(c){ c.yield(3) }
|
210
|
+
},
|
211
|
+
{
|
212
|
+
moho: ->(c){ c.super },
|
213
|
+
app: ->(c){ c.call(:moho){|x| x*x } }
|
214
|
+
})
|
215
|
+
|
216
|
+
context.call(:app).should == 9
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
describe 'callstack interface' do
|
222
|
+
|
223
|
+
it 'should return a copy of the callstack' do
|
224
|
+
context = Reduxco::Context.new
|
225
|
+
|
226
|
+
context.callstack.should be_kind_of(Reduxco::Context::Callstack)
|
227
|
+
context.callstack.should.object_id.should_not == context.callstack
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should return a copy of the callstack from inside of a call' do
|
231
|
+
context = Reduxco::Context.new({
|
232
|
+
app: ->(c){ c.callstack }
|
233
|
+
})
|
234
|
+
|
235
|
+
stack = context.call(:app)
|
236
|
+
|
237
|
+
stack.depth.should == 1
|
238
|
+
stack.top.name.should == :app
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'should return the current frame' do
|
242
|
+
context = Reduxco::Context.new({
|
243
|
+
app: ->(c){ c.current_frame.should == c.callstac.top }
|
244
|
+
})
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
describe 'introspection interface' do
|
250
|
+
|
251
|
+
let(:map1) do
|
252
|
+
{moho: ->(c){'moho-value'}, eve: ->(c){'eve-value'}}
|
253
|
+
end
|
254
|
+
|
255
|
+
let(:map2) do
|
256
|
+
{kerbin: ->(c){'kerbin-value'}, duna: ->(c){'duna-value'}, app: ->(c){c[:kerbin] + c[:moho]}}
|
257
|
+
end
|
258
|
+
|
259
|
+
let(:context) do
|
260
|
+
Reduxco::Context.new(map1, map2)
|
261
|
+
end
|
262
|
+
|
263
|
+
describe 'include' do
|
264
|
+
|
265
|
+
it 'should introspect which refnames are valid.' do
|
266
|
+
context.include?(:moho).should be_true
|
267
|
+
context.include?(:kerbin).should be_true
|
268
|
+
|
269
|
+
context.include?(:eeloo).should be_false
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should introspect which dynamic callrefs are valid.' do
|
273
|
+
context.include?( Reduxco::CallableRef.new(:moho) ).should be_true
|
274
|
+
context.include?( Reduxco::CallableRef.new(:kerbin) ).should be_true
|
275
|
+
context.include?( Reduxco::CallableRef.new(:eeloo) ).should be_false
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should introspect which static callrefs are valid.' do
|
279
|
+
context.include?( Reduxco::CallableRef.new(:moho,1) ).should be_true
|
280
|
+
context.include?( Reduxco::CallableRef.new(:kerbin,2) ).should be_true
|
281
|
+
context.include?( Reduxco::CallableRef.new(:moho,2) ).should be_false
|
282
|
+
context.include?( Reduxco::CallableRef.new(:kerbin,1) ).should be_false
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
describe 'completed' do
|
288
|
+
|
289
|
+
it 'should introspect which refnames have been computed.' do
|
290
|
+
[:moho, :eve, :kerbin, :duna, :app].each do |refname|
|
291
|
+
context.completed?(refname).should be_false
|
292
|
+
end
|
293
|
+
|
294
|
+
context.call(:app)
|
295
|
+
|
296
|
+
[:moho, :kerbin, :app].each do |refname|
|
297
|
+
context.completed?(refname).should be_true
|
298
|
+
end
|
299
|
+
|
300
|
+
[:eve, :duna].each do |refname|
|
301
|
+
context.completed?(refname).should be_false
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'should introspect which callrefs have been computed.' do
|
306
|
+
all_ref_args = [[:kerbin,1], [:kerbin,2], [:moho,1], [:moho,2], [:duna,1], [:duna,2]]
|
307
|
+
|
308
|
+
all_ref_args.each do |name, depth|
|
309
|
+
callref = Reduxco::CallableRef.new(name, depth)
|
310
|
+
context.completed?(callref).should be_false
|
311
|
+
end
|
312
|
+
|
313
|
+
context.call(:app)
|
314
|
+
|
315
|
+
completed_ref_args = [[:kerbin,2], [:moho,1]]
|
316
|
+
incomplete_ref_args = all_ref_args - completed_ref_args
|
317
|
+
|
318
|
+
completed_ref_args.each do |name, depth|
|
319
|
+
callref = Reduxco::CallableRef.new(name, depth)
|
320
|
+
context.completed?(callref).should be_true
|
321
|
+
end
|
322
|
+
|
323
|
+
incomplete_ref_args.each do |name, depth|
|
324
|
+
callref = Reduxco::CallableRef.new(name, depth)
|
325
|
+
context.completed?(callref).should be_false
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should report not-found refs as uncomputed.' do
|
330
|
+
context.completed?(:eeloo).should be_false
|
331
|
+
context.completed?( Reduxco::CallableRef.new(:eloo,1) ).should be_false
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
describe 'assert_completed' do
|
337
|
+
|
338
|
+
it 'should raise an exception if failed' do
|
339
|
+
->{ context.assert_completed(:app) }.should raise_error(Reduxco::Context::AssertError)
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'should return nil if success' do
|
343
|
+
context[:app]
|
344
|
+
|
345
|
+
context.assert_completed(:app).should be_nil
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
describe 'initialization' do
|
353
|
+
|
354
|
+
let(:map1) { {moho: ->(c){'moho1'}} }
|
355
|
+
let(:map2) { {eve: ->(c){'eve2'}} }
|
356
|
+
|
357
|
+
it 'should initialize with a single callable map' do
|
358
|
+
context = Reduxco::Context.new(map1)
|
359
|
+
context.should include(:moho)
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'should initialize with multiple callable maps' do
|
363
|
+
context = Reduxco::Context.new(map1, map2)
|
364
|
+
context.should include(:moho)
|
365
|
+
context.should include(:eve)
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'should only invoke :each on the map (returning name/callable pairs)' do
|
369
|
+
map = double('map')
|
370
|
+
map.should_receive(:each)
|
371
|
+
|
372
|
+
Reduxco::Context.new(map)
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
|
377
|
+
describe 'duplication' do
|
378
|
+
|
379
|
+
it 'should instantiate a new Context with the same callables on dup' do
|
380
|
+
map1 = double('map1')
|
381
|
+
map1.stub(:each)
|
382
|
+
map2 = double('map2')
|
383
|
+
map2.stub(:each)
|
384
|
+
context = Reduxco::Context.new(map1, map2)
|
385
|
+
|
386
|
+
dup = context.dup
|
387
|
+
|
388
|
+
dup.instance_variables.each do |ivar|
|
389
|
+
dup.instance_variable_get(ivar).object_id.should_not == context.instance_variable_get(ivar)
|
390
|
+
end
|
391
|
+
|
392
|
+
dup.instance_variable_get(:@callable_maps).tap do |maps|
|
393
|
+
maps.should == [map1, map2]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
end
|
398
|
+
|
399
|
+
describe 'reduce' do
|
400
|
+
|
401
|
+
it 'should reduce with a refname and return the result' do
|
402
|
+
context = Reduxco::Context.new(app: ->(c){'app-result'})
|
403
|
+
|
404
|
+
context.reduce(:app).should == 'app-result'
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'should reduce with :app by default' do
|
408
|
+
context = Reduxco::Context.new(app: ->(c){'app-result'})
|
409
|
+
|
410
|
+
context.reduce(:app).should == 'app-result'
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
|
415
|
+
describe 'flow helpers' do
|
416
|
+
|
417
|
+
describe 'before' do
|
418
|
+
|
419
|
+
it 'should invoke the before helper block before calling the passed refname' do
|
420
|
+
call_order = []
|
421
|
+
context = Reduxco::Context.new({
|
422
|
+
app: ->(c){ c.before(:a) {c[:b]} },
|
423
|
+
a: ->(c){ call_order << :a },
|
424
|
+
b: ->(c){ call_order << :b }
|
425
|
+
})
|
426
|
+
|
427
|
+
context.call(:app)
|
428
|
+
|
429
|
+
call_order.should == [:b, :a]
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'should return the value of the passed refname' do
|
433
|
+
context = Reduxco::Context.new({
|
434
|
+
app: ->(c){ c.before(:a) {c[:b]} },
|
435
|
+
a: ->(c){ 'a-result' },
|
436
|
+
b: ->(c){ 'b-result' }
|
437
|
+
})
|
438
|
+
|
439
|
+
context.call(:app).should == 'a-result'
|
440
|
+
end
|
441
|
+
|
442
|
+
it 'should not yield anything to the block' do
|
443
|
+
block_args = nil
|
444
|
+
context = Reduxco::Context.new({
|
445
|
+
app: Proc.new do |c|
|
446
|
+
c.before(:a) do |*args|
|
447
|
+
block_args = args
|
448
|
+
c[:b]
|
449
|
+
end
|
450
|
+
end,
|
451
|
+
a: ->(c){ 'a-result' },
|
452
|
+
b: ->(c){ 'b-result' }
|
453
|
+
})
|
454
|
+
|
455
|
+
context.call(:app)
|
456
|
+
|
457
|
+
block_args.should == []
|
458
|
+
end
|
459
|
+
|
460
|
+
end
|
461
|
+
|
462
|
+
describe 'after' do
|
463
|
+
|
464
|
+
it 'should invoke the after helper block after calling the passed refname' do
|
465
|
+
call_order = []
|
466
|
+
context = Reduxco::Context.new({
|
467
|
+
app: ->(c){ c.after(:a) {c[:b]} },
|
468
|
+
a: ->(c){ call_order << :a },
|
469
|
+
b: ->(c){ call_order << :b }
|
470
|
+
})
|
471
|
+
|
472
|
+
context.call(:app)
|
473
|
+
|
474
|
+
call_order.should == [:a, :b]
|
475
|
+
end
|
476
|
+
|
477
|
+
it 'should return the value of the passed refname' do
|
478
|
+
context = Reduxco::Context.new({
|
479
|
+
app: ->(c){ c.after(:a) {c[:b]} },
|
480
|
+
a: ->(c){ 'a-result' },
|
481
|
+
b: ->(c){ 'b-result' }
|
482
|
+
})
|
483
|
+
|
484
|
+
context.call(:app).should == 'a-result'
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'should not yield anything' do
|
488
|
+
block_args = nil
|
489
|
+
context = Reduxco::Context.new({
|
490
|
+
app: Proc.new do |c|
|
491
|
+
c.after(:a) do |*args|
|
492
|
+
block_args = args
|
493
|
+
c[:b]
|
494
|
+
end
|
495
|
+
end,
|
496
|
+
a: ->(c){ 'a-result' },
|
497
|
+
b: ->(c){ 'b-result' }
|
498
|
+
})
|
499
|
+
|
500
|
+
context.call(:app)
|
501
|
+
|
502
|
+
block_args.should == []
|
503
|
+
end
|
504
|
+
|
505
|
+
end
|
506
|
+
|
507
|
+
describe 'inside' do
|
508
|
+
|
509
|
+
describe 'client behavior' do
|
510
|
+
|
511
|
+
let(:table){ {} }
|
512
|
+
|
513
|
+
let(:outter) do
|
514
|
+
Proc.new do |c|
|
515
|
+
table[:yeild_result] = c.yield('arg1', 'arg2')
|
516
|
+
'outter_result'
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
let(:app) do
|
521
|
+
Proc.new do |c|
|
522
|
+
table[:inside_method_result] = c.inside(:outter) do |*args|
|
523
|
+
table[:args] = args
|
524
|
+
'yield_result'
|
525
|
+
end
|
526
|
+
'app_result'
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
let(:map) do
|
531
|
+
{app:app, outter:outter}
|
532
|
+
end
|
533
|
+
|
534
|
+
let(:context) do
|
535
|
+
context = Reduxco::Context.new(map)
|
536
|
+
end
|
537
|
+
|
538
|
+
it 'should be an alias to call, as it is just call with a block provided.' do
|
539
|
+
context.method(:inside).should == context.method(:call)
|
540
|
+
end
|
541
|
+
|
542
|
+
it 'should return the block\'s value from inside the passed refname' do
|
543
|
+
context.call(:app)
|
544
|
+
|
545
|
+
table[:yeild_result].should == 'yield_result'
|
546
|
+
end
|
547
|
+
|
548
|
+
it 'should return the value of the passed refname' do
|
549
|
+
context.call(:app)
|
550
|
+
|
551
|
+
|
552
|
+
table[:inside_method_result].should == 'outter_result'
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'should allow yielding of args to the block' do
|
556
|
+
context.call(:app)
|
557
|
+
|
558
|
+
table[:args].should == ['arg1', 'arg2']
|
559
|
+
end
|
560
|
+
|
561
|
+
it 'should allow taking no block' do
|
562
|
+
context = Reduxco::Context.new(app: ->(c){ c.inside(:moho) }, moho: ->(c){'moho-result'})
|
563
|
+
|
564
|
+
context.call(:app).should == 'moho-result'
|
565
|
+
end
|
566
|
+
|
567
|
+
end
|
568
|
+
|
569
|
+
describe 'yield failure modes' do
|
570
|
+
|
571
|
+
it 'should give nested insides their correct block handles' do
|
572
|
+
context = Reduxco::Context.new(
|
573
|
+
app: ->(c){ c.inside(:outter){ c.inside(:middle){ c[:inner] } } },
|
574
|
+
outter: ->(c){ c.yield << 'outter' },
|
575
|
+
middle: ->(c){ c.yield << 'middle' },
|
576
|
+
inner: ->(c){ ['inner'] }
|
577
|
+
)
|
578
|
+
|
579
|
+
context.call(:app).should == ['inner', 'middle', 'outter']
|
580
|
+
end
|
581
|
+
|
582
|
+
it 'should not corrupt the handle stack when an exception is thrown and then caught inside of nested insides' do
|
583
|
+
context = Reduxco::Context.new(
|
584
|
+
app: ->(c){ c.inside(:outter){ c.inside(:middle){ c.inside(:inner) {c[:leaf]} } } },
|
585
|
+
outter: ->(c){ c.yield << 'outter' },
|
586
|
+
middle: ->(c){ begin c.yield << 'middle' rescue ['middle-caught'] end },
|
587
|
+
inner: ->(c){ c.yield << 'inner' },
|
588
|
+
leaf: ->(c){ raise RuntimeError }
|
589
|
+
)
|
590
|
+
|
591
|
+
context.call(:app).should == ['middle-caught', 'outter']
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'should not allow yielding from a nested call.' do
|
595
|
+
context = Reduxco::Context.new(
|
596
|
+
app: ->(c){ c.inside(:outter){ 2 } },
|
597
|
+
outter: ->(c){ c[:middle] },
|
598
|
+
middle: ->(c){ c.yield }
|
599
|
+
)
|
600
|
+
|
601
|
+
->{ context.call(:app) }.should raise_error(Reduxco::Context::LocalJumpError)
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'should allow a double yield.' do
|
605
|
+
context = Reduxco::Context.new(
|
606
|
+
app: ->(c){ c.inside(:outter){|x| x*x} },
|
607
|
+
outter: ->(c){ c.yield(3) + c.yield(4) }
|
608
|
+
)
|
609
|
+
|
610
|
+
context.call(:app).should == 25
|
611
|
+
end
|
612
|
+
|
613
|
+
end
|
614
|
+
|
615
|
+
end
|
616
|
+
|
617
|
+
end
|
618
|
+
|
619
|
+
end
|