concurrent-ruby 0.2.0 → 0.2.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 +21 -21
- data/README.md +275 -275
- data/lib/concurrent.rb +28 -28
- data/lib/concurrent/agent.rb +114 -114
- data/lib/concurrent/cached_thread_pool.rb +131 -129
- data/lib/concurrent/defer.rb +65 -65
- data/lib/concurrent/event.rb +60 -60
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
- data/lib/concurrent/executor.rb +96 -95
- data/lib/concurrent/fixed_thread_pool.rb +99 -95
- data/lib/concurrent/functions.rb +120 -120
- data/lib/concurrent/future.rb +42 -42
- data/lib/concurrent/global_thread_pool.rb +16 -16
- data/lib/concurrent/goroutine.rb +29 -29
- data/lib/concurrent/null_thread_pool.rb +22 -22
- data/lib/concurrent/obligation.rb +67 -67
- data/lib/concurrent/promise.rb +174 -174
- data/lib/concurrent/reactor.rb +166 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
- data/lib/concurrent/supervisor.rb +105 -100
- data/lib/concurrent/thread_pool.rb +76 -76
- data/lib/concurrent/utilities.rb +32 -32
- data/lib/concurrent/version.rb +3 -3
- data/lib/concurrent_ruby.rb +1 -1
- data/md/agent.md +123 -123
- data/md/defer.md +174 -174
- data/md/event.md +32 -32
- data/md/executor.md +187 -187
- data/md/future.md +83 -83
- data/md/goroutine.md +52 -52
- data/md/obligation.md +32 -32
- data/md/promise.md +227 -227
- data/md/thread_pool.md +224 -224
- data/spec/concurrent/agent_spec.rb +386 -386
- data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
- data/spec/concurrent/defer_spec.rb +195 -195
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
- data/spec/concurrent/event_spec.rb +134 -134
- data/spec/concurrent/executor_spec.rb +200 -200
- data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
- data/spec/concurrent/functions_spec.rb +217 -217
- data/spec/concurrent/future_spec.rb +108 -108
- data/spec/concurrent/global_thread_pool_spec.rb +38 -38
- data/spec/concurrent/goroutine_spec.rb +67 -67
- data/spec/concurrent/null_thread_pool_spec.rb +57 -54
- data/spec/concurrent/obligation_shared.rb +132 -132
- data/spec/concurrent/promise_spec.rb +312 -312
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
- data/spec/concurrent/reactor_spec.rb +364 -364
- data/spec/concurrent/supervisor_spec.rb +269 -258
- data/spec/concurrent/thread_pool_shared.rb +204 -204
- data/spec/concurrent/utilities_spec.rb +74 -74
- data/spec/spec_helper.rb +32 -32
- metadata +20 -16
- checksums.yaml +0 -7
@@ -1,132 +1,132 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
share_examples_for Concurrent::Obligation do
|
4
|
-
|
5
|
-
context '#state' do
|
6
|
-
|
7
|
-
it 'is :pending when first created' do
|
8
|
-
f = pending_subject
|
9
|
-
f.state.should == :pending
|
10
|
-
f.should be_pending
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'is :fulfilled when the handler completes' do
|
14
|
-
f = fulfilled_subject
|
15
|
-
f.state.should == :fulfilled
|
16
|
-
f.should be_fulfilled
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'is :rejected when the handler raises an exception' do
|
20
|
-
f = rejected_subject
|
21
|
-
f.state.should == :rejected
|
22
|
-
f.should be_rejected
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
context '#value' do
|
27
|
-
|
28
|
-
it 'blocks the caller when :pending and timeout is nil' do
|
29
|
-
f = pending_subject
|
30
|
-
sleep(0.1)
|
31
|
-
f.value.should be_true
|
32
|
-
f.should be_fulfilled
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'returns nil when reaching the optional timeout value' do
|
36
|
-
f = pending_subject
|
37
|
-
sleep(0.1)
|
38
|
-
f.value(0).should be_nil
|
39
|
-
f.should be_pending
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'returns immediately when timeout is zero' do
|
43
|
-
Timeout.should_not_receive(:timeout).with(any_args())
|
44
|
-
f = pending_subject
|
45
|
-
sleep(0.1)
|
46
|
-
f.value(0).should be_nil
|
47
|
-
f.should be_pending
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'returns the value when fulfilled before timeout' do
|
51
|
-
f = pending_subject
|
52
|
-
sleep(0.1)
|
53
|
-
f.value(10).should be_true
|
54
|
-
f.should be_fulfilled
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'returns nil when timeout reached' do
|
58
|
-
f = pending_subject
|
59
|
-
sleep(0.1)
|
60
|
-
f.value(0.1).should be_nil
|
61
|
-
f.should be_pending
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'is nil when :pending' do
|
65
|
-
expected = pending_subject.value(0)
|
66
|
-
expected.should be_nil
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'is nil when :rejected' do
|
70
|
-
expected = rejected_subject.value
|
71
|
-
expected.should be_nil
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'is set to the return value of the block when :fulfilled' do
|
75
|
-
expected = fulfilled_subject.value
|
76
|
-
expected.should eq fulfilled_value
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
context '#reason' do
|
81
|
-
|
82
|
-
it 'is nil when :pending' do
|
83
|
-
pending_subject.reason.should be_nil
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'is nil when :fulfilled' do
|
87
|
-
fulfilled_subject.reason.should be_nil
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'is set to error object of the exception when :rejected' do
|
91
|
-
rejected_subject.reason.should be_a(Exception)
|
92
|
-
rejected_subject.reason.to_s.should =~ /#{rejected_reason}/
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
context 'Kernel aliases' do
|
97
|
-
|
98
|
-
it 'aliases Kernel#deref for #deref' do
|
99
|
-
deref(fulfilled_subject).should eq fulfilled_value
|
100
|
-
deref(fulfilled_subject, 0).should eq fulfilled_value
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'aliases Kernel#pending? for #pending?' do
|
104
|
-
#NOTE: was structured like others but was incorrectly failing
|
105
|
-
# on fulfilled_subject
|
106
|
-
fulfilled_subject.should_receive(:pending?).once
|
107
|
-
pending?(fulfilled_subject)
|
108
|
-
pending_subject.should_receive(:pending?).once
|
109
|
-
pending?(pending_subject)
|
110
|
-
rejected_subject.should_receive(:pending?).once
|
111
|
-
pending?(rejected_subject)
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'aliases Kernel#fulfilled? for #fulfilled?' do
|
115
|
-
fulfilled?(fulfilled_subject).should be_true
|
116
|
-
fulfilled?(pending_subject).should be_false
|
117
|
-
fulfilled?(rejected_subject).should be_false
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'aliases Kernel#realized? for #realized?' do
|
121
|
-
realized?(fulfilled_subject).should be_true
|
122
|
-
realized?(pending_subject).should be_false
|
123
|
-
realized?(rejected_subject).should be_false
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'aliases Kernel#rejected? for #rejected?' do
|
127
|
-
rejected?(rejected_subject).should be_true
|
128
|
-
rejected?(fulfilled_subject).should be_false
|
129
|
-
rejected?(pending_subject).should be_false
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
share_examples_for Concurrent::Obligation do
|
4
|
+
|
5
|
+
context '#state' do
|
6
|
+
|
7
|
+
it 'is :pending when first created' do
|
8
|
+
f = pending_subject
|
9
|
+
f.state.should == :pending
|
10
|
+
f.should be_pending
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is :fulfilled when the handler completes' do
|
14
|
+
f = fulfilled_subject
|
15
|
+
f.state.should == :fulfilled
|
16
|
+
f.should be_fulfilled
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is :rejected when the handler raises an exception' do
|
20
|
+
f = rejected_subject
|
21
|
+
f.state.should == :rejected
|
22
|
+
f.should be_rejected
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context '#value' do
|
27
|
+
|
28
|
+
it 'blocks the caller when :pending and timeout is nil' do
|
29
|
+
f = pending_subject
|
30
|
+
sleep(0.1)
|
31
|
+
f.value.should be_true
|
32
|
+
f.should be_fulfilled
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns nil when reaching the optional timeout value' do
|
36
|
+
f = pending_subject
|
37
|
+
sleep(0.1)
|
38
|
+
f.value(0).should be_nil
|
39
|
+
f.should be_pending
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns immediately when timeout is zero' do
|
43
|
+
Timeout.should_not_receive(:timeout).with(any_args())
|
44
|
+
f = pending_subject
|
45
|
+
sleep(0.1)
|
46
|
+
f.value(0).should be_nil
|
47
|
+
f.should be_pending
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns the value when fulfilled before timeout' do
|
51
|
+
f = pending_subject
|
52
|
+
sleep(0.1)
|
53
|
+
f.value(10).should be_true
|
54
|
+
f.should be_fulfilled
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns nil when timeout reached' do
|
58
|
+
f = pending_subject
|
59
|
+
sleep(0.1)
|
60
|
+
f.value(0.1).should be_nil
|
61
|
+
f.should be_pending
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'is nil when :pending' do
|
65
|
+
expected = pending_subject.value(0)
|
66
|
+
expected.should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'is nil when :rejected' do
|
70
|
+
expected = rejected_subject.value
|
71
|
+
expected.should be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'is set to the return value of the block when :fulfilled' do
|
75
|
+
expected = fulfilled_subject.value
|
76
|
+
expected.should eq fulfilled_value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context '#reason' do
|
81
|
+
|
82
|
+
it 'is nil when :pending' do
|
83
|
+
pending_subject.reason.should be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'is nil when :fulfilled' do
|
87
|
+
fulfilled_subject.reason.should be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'is set to error object of the exception when :rejected' do
|
91
|
+
rejected_subject.reason.should be_a(Exception)
|
92
|
+
rejected_subject.reason.to_s.should =~ /#{rejected_reason}/
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'Kernel aliases' do
|
97
|
+
|
98
|
+
it 'aliases Kernel#deref for #deref' do
|
99
|
+
deref(fulfilled_subject).should eq fulfilled_value
|
100
|
+
deref(fulfilled_subject, 0).should eq fulfilled_value
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'aliases Kernel#pending? for #pending?' do
|
104
|
+
#NOTE: was structured like others but was incorrectly failing
|
105
|
+
# on fulfilled_subject
|
106
|
+
fulfilled_subject.should_receive(:pending?).once
|
107
|
+
pending?(fulfilled_subject)
|
108
|
+
pending_subject.should_receive(:pending?).once
|
109
|
+
pending?(pending_subject)
|
110
|
+
rejected_subject.should_receive(:pending?).once
|
111
|
+
pending?(rejected_subject)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'aliases Kernel#fulfilled? for #fulfilled?' do
|
115
|
+
fulfilled?(fulfilled_subject).should be_true
|
116
|
+
fulfilled?(pending_subject).should be_false
|
117
|
+
fulfilled?(rejected_subject).should be_false
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'aliases Kernel#realized? for #realized?' do
|
121
|
+
realized?(fulfilled_subject).should be_true
|
122
|
+
realized?(pending_subject).should be_false
|
123
|
+
realized?(rejected_subject).should be_false
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'aliases Kernel#rejected? for #rejected?' do
|
127
|
+
rejected?(rejected_subject).should be_true
|
128
|
+
rejected?(fulfilled_subject).should be_false
|
129
|
+
rejected?(pending_subject).should be_false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -1,312 +1,312 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require_relative 'obligation_shared'
|
3
|
-
|
4
|
-
module Concurrent
|
5
|
-
|
6
|
-
describe Promise do
|
7
|
-
|
8
|
-
let!(:fulfilled_value) { 10 }
|
9
|
-
let!(:rejected_reason) { StandardError.new('mojo jojo') }
|
10
|
-
|
11
|
-
let(:pending_subject) do
|
12
|
-
Promise.new{ sleep(3); fulfilled_value }
|
13
|
-
end
|
14
|
-
|
15
|
-
let(:fulfilled_subject) do
|
16
|
-
Promise.new{ fulfilled_value }.tap(){ sleep(0.1) }
|
17
|
-
end
|
18
|
-
|
19
|
-
let(:rejected_subject) do
|
20
|
-
Promise.new{ raise rejected_reason }.
|
21
|
-
rescue{ nil }.tap(){ sleep(0.1) }
|
22
|
-
end
|
23
|
-
|
24
|
-
before(:each) do
|
25
|
-
Promise.thread_pool = FixedThreadPool.new(1)
|
26
|
-
end
|
27
|
-
|
28
|
-
it_should_behave_like Concurrent::Obligation
|
29
|
-
|
30
|
-
context 'behavior' do
|
31
|
-
|
32
|
-
it 'implements :promise behavior' do
|
33
|
-
lambda {
|
34
|
-
Promise.new{ nil }
|
35
|
-
}.should_not raise_error
|
36
|
-
|
37
|
-
Promise.new{ nil }.behaves_as?(:promise).should be_true
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'implements :future behavior' do
|
41
|
-
lambda {
|
42
|
-
Promise.new{ nil }
|
43
|
-
}.should_not raise_error
|
44
|
-
|
45
|
-
Promise.new{ nil }.behaves_as?(:future).should be_true
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
context '#then' do
|
50
|
-
|
51
|
-
it 'returns a new Promise when :pending' do
|
52
|
-
p1 = pending_subject
|
53
|
-
p2 = p1.then{}
|
54
|
-
p2.should be_a(Promise)
|
55
|
-
p1.should_not eq p2
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'returns a new Promise when :fulfilled' do
|
59
|
-
p1 = fulfilled_subject
|
60
|
-
p2 = p1.then{}
|
61
|
-
p2.should be_a(Promise)
|
62
|
-
p1.should_not eq p2
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'returns a new Promise when :rejected' do
|
66
|
-
p1 = rejected_subject
|
67
|
-
p2 = p1.then{}
|
68
|
-
p2.should be_a(Promise)
|
69
|
-
p1.should_not eq p2
|
70
|
-
end
|
71
|
-
|
72
|
-
it 'immediately rejects new promises when self has been rejected' do
|
73
|
-
p = rejected_subject
|
74
|
-
p.then.should be_rejected
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'accepts a nil block' do
|
78
|
-
lambda {
|
79
|
-
pending_subject.then
|
80
|
-
}.should_not raise_error
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'can be called more than once' do
|
84
|
-
p = pending_subject
|
85
|
-
p1 = p.then{}
|
86
|
-
p2 = p.then{}
|
87
|
-
p1.object_id.should_not eq p2.object_id
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
context '#rescue' do
|
92
|
-
|
93
|
-
it 'returns self when a block is given' do
|
94
|
-
p1 = pending_subject
|
95
|
-
p2 = p1.rescue{}
|
96
|
-
p1.object_id.should eq p2.object_id
|
97
|
-
end
|
98
|
-
|
99
|
-
it 'returns self when no block is given' do
|
100
|
-
p1 = pending_subject
|
101
|
-
p2 = p1.rescue
|
102
|
-
p1.object_id.should eq p2.object_id
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'accepts an exception class as the first parameter' do
|
106
|
-
lambda {
|
107
|
-
pending_subject.rescue(StandardError){}
|
108
|
-
}.should_not raise_error
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
context 'fulfillment' do
|
113
|
-
|
114
|
-
it 'passes all arguments to the first promise in the chain' do
|
115
|
-
@a = @b = @c = nil
|
116
|
-
p = Promise.new(1, 2, 3) do |a, b, c|
|
117
|
-
@a, @b, @c = a, b, c
|
118
|
-
end
|
119
|
-
sleep(0.1)
|
120
|
-
[@a, @b, @c].should eq [1, 2, 3]
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'passes the result of each block to all its children' do
|
124
|
-
@expected = nil
|
125
|
-
Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
|
126
|
-
sleep(0.1)
|
127
|
-
@expected.should eq 20
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'sets the promise value to the result if its block' do
|
131
|
-
p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
|
132
|
-
sleep(0.1)
|
133
|
-
p.value.should eq 40
|
134
|
-
end
|
135
|
-
|
136
|
-
it 'sets the promise state to :fulfilled if the block completes' do
|
137
|
-
p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
|
138
|
-
sleep(0.1)
|
139
|
-
p.should be_fulfilled
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'passes the last result through when a promise has no block' do
|
143
|
-
@expected = nil
|
144
|
-
Promise.new(10){|a| a * 2 }.then.then{|result| @expected = result}
|
145
|
-
sleep(0.1)
|
146
|
-
@expected.should eq 20
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
context 'rejection' do
|
151
|
-
|
152
|
-
it 'sets the promise reason the error object on exception' do
|
153
|
-
p = Promise.new{ raise StandardError.new('Boom!') }
|
154
|
-
sleep(0.1)
|
155
|
-
p.reason.should be_a(Exception)
|
156
|
-
p.reason.should.to_s =~ /Boom!/
|
157
|
-
end
|
158
|
-
|
159
|
-
it 'sets the promise state to :rejected on exception' do
|
160
|
-
p = Promise.new{ raise StandardError.new('Boom!') }
|
161
|
-
sleep(0.1)
|
162
|
-
p.should be_rejected
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'recursively rejects all children' do
|
166
|
-
p = Promise.new{ raise StandardError.new('Boom!') }
|
167
|
-
promises = 10.times.collect{ p.then{ true } }
|
168
|
-
sleep(0.1)
|
169
|
-
10.times.each{|i| promises[i].should be_rejected }
|
170
|
-
end
|
171
|
-
|
172
|
-
it 'skips processing rejected promises' do
|
173
|
-
p = Promise.new{ raise StandardError.new('Boom!') }
|
174
|
-
promises = 3.times.collect{ p.then{ true } }
|
175
|
-
sleep(0.1)
|
176
|
-
promises.each{|p| p.value.should_not be_true }
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'calls the first exception block with a matching class' do
|
180
|
-
@expected = nil
|
181
|
-
Promise.new{ raise StandardError }.
|
182
|
-
rescue(StandardError){|ex| @expected = 1 }.
|
183
|
-
rescue(StandardError){|ex| @expected = 2 }.
|
184
|
-
rescue(StandardError){|ex| @expected = 3 }
|
185
|
-
sleep(0.1)
|
186
|
-
@expected.should eq 1
|
187
|
-
end
|
188
|
-
|
189
|
-
it 'matches all with a rescue with no class given' do
|
190
|
-
@expected = nil
|
191
|
-
Promise.new{ raise NoMethodError }.
|
192
|
-
rescue(LoadError){|ex| @expected = 1 }.
|
193
|
-
rescue{|ex| @expected = 2 }.
|
194
|
-
rescue(StandardError){|ex| @expected = 3 }
|
195
|
-
sleep(0.1)
|
196
|
-
@expected.should eq 2
|
197
|
-
end
|
198
|
-
|
199
|
-
it 'searches associated rescue handlers in order' do
|
200
|
-
Promise.thread_pool = CachedThreadPool.new
|
201
|
-
|
202
|
-
@expected = nil
|
203
|
-
Promise.new{ raise ArgumentError }.
|
204
|
-
rescue(ArgumentError){|ex| @expected = 1 }.
|
205
|
-
rescue(LoadError){|ex| @expected = 2 }.
|
206
|
-
rescue(Exception){|ex| @expected = 3 }
|
207
|
-
sleep(0.1)
|
208
|
-
@expected.should eq 1
|
209
|
-
|
210
|
-
@expected = nil
|
211
|
-
Promise.new{ raise LoadError }.
|
212
|
-
rescue(ArgumentError){|ex| @expected = 1 }.
|
213
|
-
rescue(LoadError){|ex| @expected = 2 }.
|
214
|
-
rescue(Exception){|ex| @expected = 3 }
|
215
|
-
sleep(0.1)
|
216
|
-
@expected.should eq 2
|
217
|
-
|
218
|
-
@expected = nil
|
219
|
-
Promise.new{ raise StandardError }.
|
220
|
-
rescue(ArgumentError){|ex| @expected = 1 }.
|
221
|
-
rescue(LoadError){|ex| @expected = 2 }.
|
222
|
-
rescue(Exception){|ex| @expected = 3 }
|
223
|
-
sleep(0.1)
|
224
|
-
@expected.should eq 3
|
225
|
-
end
|
226
|
-
|
227
|
-
it 'passes the exception object to the matched block' do
|
228
|
-
@expected = nil
|
229
|
-
Promise.new{ raise StandardError }.
|
230
|
-
rescue(ArgumentError){|ex| @expected = ex }.
|
231
|
-
rescue(LoadError){|ex| @expected = ex }.
|
232
|
-
rescue(Exception){|ex| @expected = ex }
|
233
|
-
sleep(0.1)
|
234
|
-
@expected.should be_a(StandardError)
|
235
|
-
end
|
236
|
-
|
237
|
-
it 'ignores rescuers without a block' do
|
238
|
-
@expected = nil
|
239
|
-
Promise.new{ raise StandardError }.
|
240
|
-
rescue(StandardError).
|
241
|
-
rescue(StandardError){|ex| @expected = ex }.
|
242
|
-
rescue(Exception){|ex| @expected = ex }
|
243
|
-
sleep(0.1)
|
244
|
-
@expected.should be_a(StandardError)
|
245
|
-
end
|
246
|
-
|
247
|
-
it 'supresses the exception if no rescue matches' do
|
248
|
-
lambda {
|
249
|
-
Promise.new{ raise StandardError }.
|
250
|
-
rescue(ArgumentError){|ex| @expected = ex }.
|
251
|
-
rescue(StandardError){|ex| @expected = ex }.
|
252
|
-
rescue(Exception){|ex| @expected = ex }
|
253
|
-
sleep(0.1)
|
254
|
-
}.should_not raise_error
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'supresses exceptions thrown from rescue handlers' do
|
258
|
-
lambda {
|
259
|
-
Promise.new{ raise ArgumentError }.
|
260
|
-
rescue(Exception){ raise StandardError }
|
261
|
-
sleep(0.1)
|
262
|
-
}.should_not raise_error
|
263
|
-
end
|
264
|
-
|
265
|
-
it 'calls matching rescue handlers on all children' do
|
266
|
-
@expected = []
|
267
|
-
Promise.new{ raise StandardError }.
|
268
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
269
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
270
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
271
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
272
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }
|
273
|
-
sleep(0.1)
|
274
|
-
|
275
|
-
@expected.length.should eq 5
|
276
|
-
end
|
277
|
-
|
278
|
-
it 'matches a rescue handler added after rejection' do
|
279
|
-
@expected = false
|
280
|
-
p = Promise.new{ raise StandardError }
|
281
|
-
sleep(0.1)
|
282
|
-
p.rescue(StandardError){ @expected = true }
|
283
|
-
@expected.should be_true
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
context 'aliases' do
|
288
|
-
|
289
|
-
it 'aliases #realized? for #fulfilled?' do
|
290
|
-
fulfilled_subject.should be_realized
|
291
|
-
end
|
292
|
-
|
293
|
-
it 'aliases #deref for #value' do
|
294
|
-
fulfilled_subject.deref.should eq fulfilled_value
|
295
|
-
end
|
296
|
-
|
297
|
-
it 'aliases #catch for #rescue' do
|
298
|
-
@expected = nil
|
299
|
-
Promise.new{ raise StandardError }.catch{ @expected = true }
|
300
|
-
sleep(0.1)
|
301
|
-
@expected.should be_true
|
302
|
-
end
|
303
|
-
|
304
|
-
it 'aliases #on_error for #rescue' do
|
305
|
-
@expected = nil
|
306
|
-
Promise.new{ raise StandardError }.on_error{ @expected = true }
|
307
|
-
sleep(0.1)
|
308
|
-
@expected.should be_true
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
end
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'obligation_shared'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
describe Promise do
|
7
|
+
|
8
|
+
let!(:fulfilled_value) { 10 }
|
9
|
+
let!(:rejected_reason) { StandardError.new('mojo jojo') }
|
10
|
+
|
11
|
+
let(:pending_subject) do
|
12
|
+
Promise.new{ sleep(3); fulfilled_value }
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:fulfilled_subject) do
|
16
|
+
Promise.new{ fulfilled_value }.tap(){ sleep(0.1) }
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:rejected_subject) do
|
20
|
+
Promise.new{ raise rejected_reason }.
|
21
|
+
rescue{ nil }.tap(){ sleep(0.1) }
|
22
|
+
end
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
Promise.thread_pool = FixedThreadPool.new(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it_should_behave_like Concurrent::Obligation
|
29
|
+
|
30
|
+
context 'behavior' do
|
31
|
+
|
32
|
+
it 'implements :promise behavior' do
|
33
|
+
lambda {
|
34
|
+
Promise.new{ nil }
|
35
|
+
}.should_not raise_error
|
36
|
+
|
37
|
+
Promise.new{ nil }.behaves_as?(:promise).should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'implements :future behavior' do
|
41
|
+
lambda {
|
42
|
+
Promise.new{ nil }
|
43
|
+
}.should_not raise_error
|
44
|
+
|
45
|
+
Promise.new{ nil }.behaves_as?(:future).should be_true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context '#then' do
|
50
|
+
|
51
|
+
it 'returns a new Promise when :pending' do
|
52
|
+
p1 = pending_subject
|
53
|
+
p2 = p1.then{}
|
54
|
+
p2.should be_a(Promise)
|
55
|
+
p1.should_not eq p2
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns a new Promise when :fulfilled' do
|
59
|
+
p1 = fulfilled_subject
|
60
|
+
p2 = p1.then{}
|
61
|
+
p2.should be_a(Promise)
|
62
|
+
p1.should_not eq p2
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns a new Promise when :rejected' do
|
66
|
+
p1 = rejected_subject
|
67
|
+
p2 = p1.then{}
|
68
|
+
p2.should be_a(Promise)
|
69
|
+
p1.should_not eq p2
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'immediately rejects new promises when self has been rejected' do
|
73
|
+
p = rejected_subject
|
74
|
+
p.then.should be_rejected
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'accepts a nil block' do
|
78
|
+
lambda {
|
79
|
+
pending_subject.then
|
80
|
+
}.should_not raise_error
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'can be called more than once' do
|
84
|
+
p = pending_subject
|
85
|
+
p1 = p.then{}
|
86
|
+
p2 = p.then{}
|
87
|
+
p1.object_id.should_not eq p2.object_id
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context '#rescue' do
|
92
|
+
|
93
|
+
it 'returns self when a block is given' do
|
94
|
+
p1 = pending_subject
|
95
|
+
p2 = p1.rescue{}
|
96
|
+
p1.object_id.should eq p2.object_id
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'returns self when no block is given' do
|
100
|
+
p1 = pending_subject
|
101
|
+
p2 = p1.rescue
|
102
|
+
p1.object_id.should eq p2.object_id
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'accepts an exception class as the first parameter' do
|
106
|
+
lambda {
|
107
|
+
pending_subject.rescue(StandardError){}
|
108
|
+
}.should_not raise_error
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'fulfillment' do
|
113
|
+
|
114
|
+
it 'passes all arguments to the first promise in the chain' do
|
115
|
+
@a = @b = @c = nil
|
116
|
+
p = Promise.new(1, 2, 3) do |a, b, c|
|
117
|
+
@a, @b, @c = a, b, c
|
118
|
+
end
|
119
|
+
sleep(0.1)
|
120
|
+
[@a, @b, @c].should eq [1, 2, 3]
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'passes the result of each block to all its children' do
|
124
|
+
@expected = nil
|
125
|
+
Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
|
126
|
+
sleep(0.1)
|
127
|
+
@expected.should eq 20
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'sets the promise value to the result if its block' do
|
131
|
+
p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
|
132
|
+
sleep(0.1)
|
133
|
+
p.value.should eq 40
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'sets the promise state to :fulfilled if the block completes' do
|
137
|
+
p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
|
138
|
+
sleep(0.1)
|
139
|
+
p.should be_fulfilled
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'passes the last result through when a promise has no block' do
|
143
|
+
@expected = nil
|
144
|
+
Promise.new(10){|a| a * 2 }.then.then{|result| @expected = result}
|
145
|
+
sleep(0.1)
|
146
|
+
@expected.should eq 20
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'rejection' do
|
151
|
+
|
152
|
+
it 'sets the promise reason the error object on exception' do
|
153
|
+
p = Promise.new{ raise StandardError.new('Boom!') }
|
154
|
+
sleep(0.1)
|
155
|
+
p.reason.should be_a(Exception)
|
156
|
+
p.reason.should.to_s =~ /Boom!/
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'sets the promise state to :rejected on exception' do
|
160
|
+
p = Promise.new{ raise StandardError.new('Boom!') }
|
161
|
+
sleep(0.1)
|
162
|
+
p.should be_rejected
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'recursively rejects all children' do
|
166
|
+
p = Promise.new{ raise StandardError.new('Boom!') }
|
167
|
+
promises = 10.times.collect{ p.then{ true } }
|
168
|
+
sleep(0.1)
|
169
|
+
10.times.each{|i| promises[i].should be_rejected }
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'skips processing rejected promises' do
|
173
|
+
p = Promise.new{ raise StandardError.new('Boom!') }
|
174
|
+
promises = 3.times.collect{ p.then{ true } }
|
175
|
+
sleep(0.1)
|
176
|
+
promises.each{|p| p.value.should_not be_true }
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'calls the first exception block with a matching class' do
|
180
|
+
@expected = nil
|
181
|
+
Promise.new{ raise StandardError }.
|
182
|
+
rescue(StandardError){|ex| @expected = 1 }.
|
183
|
+
rescue(StandardError){|ex| @expected = 2 }.
|
184
|
+
rescue(StandardError){|ex| @expected = 3 }
|
185
|
+
sleep(0.1)
|
186
|
+
@expected.should eq 1
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'matches all with a rescue with no class given' do
|
190
|
+
@expected = nil
|
191
|
+
Promise.new{ raise NoMethodError }.
|
192
|
+
rescue(LoadError){|ex| @expected = 1 }.
|
193
|
+
rescue{|ex| @expected = 2 }.
|
194
|
+
rescue(StandardError){|ex| @expected = 3 }
|
195
|
+
sleep(0.1)
|
196
|
+
@expected.should eq 2
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'searches associated rescue handlers in order' do
|
200
|
+
Promise.thread_pool = CachedThreadPool.new
|
201
|
+
|
202
|
+
@expected = nil
|
203
|
+
Promise.new{ raise ArgumentError }.
|
204
|
+
rescue(ArgumentError){|ex| @expected = 1 }.
|
205
|
+
rescue(LoadError){|ex| @expected = 2 }.
|
206
|
+
rescue(Exception){|ex| @expected = 3 }
|
207
|
+
sleep(0.1)
|
208
|
+
@expected.should eq 1
|
209
|
+
|
210
|
+
@expected = nil
|
211
|
+
Promise.new{ raise LoadError }.
|
212
|
+
rescue(ArgumentError){|ex| @expected = 1 }.
|
213
|
+
rescue(LoadError){|ex| @expected = 2 }.
|
214
|
+
rescue(Exception){|ex| @expected = 3 }
|
215
|
+
sleep(0.1)
|
216
|
+
@expected.should eq 2
|
217
|
+
|
218
|
+
@expected = nil
|
219
|
+
Promise.new{ raise StandardError }.
|
220
|
+
rescue(ArgumentError){|ex| @expected = 1 }.
|
221
|
+
rescue(LoadError){|ex| @expected = 2 }.
|
222
|
+
rescue(Exception){|ex| @expected = 3 }
|
223
|
+
sleep(0.1)
|
224
|
+
@expected.should eq 3
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'passes the exception object to the matched block' do
|
228
|
+
@expected = nil
|
229
|
+
Promise.new{ raise StandardError }.
|
230
|
+
rescue(ArgumentError){|ex| @expected = ex }.
|
231
|
+
rescue(LoadError){|ex| @expected = ex }.
|
232
|
+
rescue(Exception){|ex| @expected = ex }
|
233
|
+
sleep(0.1)
|
234
|
+
@expected.should be_a(StandardError)
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'ignores rescuers without a block' do
|
238
|
+
@expected = nil
|
239
|
+
Promise.new{ raise StandardError }.
|
240
|
+
rescue(StandardError).
|
241
|
+
rescue(StandardError){|ex| @expected = ex }.
|
242
|
+
rescue(Exception){|ex| @expected = ex }
|
243
|
+
sleep(0.1)
|
244
|
+
@expected.should be_a(StandardError)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'supresses the exception if no rescue matches' do
|
248
|
+
lambda {
|
249
|
+
Promise.new{ raise StandardError }.
|
250
|
+
rescue(ArgumentError){|ex| @expected = ex }.
|
251
|
+
rescue(StandardError){|ex| @expected = ex }.
|
252
|
+
rescue(Exception){|ex| @expected = ex }
|
253
|
+
sleep(0.1)
|
254
|
+
}.should_not raise_error
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'supresses exceptions thrown from rescue handlers' do
|
258
|
+
lambda {
|
259
|
+
Promise.new{ raise ArgumentError }.
|
260
|
+
rescue(Exception){ raise StandardError }
|
261
|
+
sleep(0.1)
|
262
|
+
}.should_not raise_error
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'calls matching rescue handlers on all children' do
|
266
|
+
@expected = []
|
267
|
+
Promise.new{ raise StandardError }.
|
268
|
+
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
269
|
+
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
270
|
+
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
271
|
+
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
272
|
+
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }
|
273
|
+
sleep(0.1)
|
274
|
+
|
275
|
+
@expected.length.should eq 5
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'matches a rescue handler added after rejection' do
|
279
|
+
@expected = false
|
280
|
+
p = Promise.new{ raise StandardError }
|
281
|
+
sleep(0.1)
|
282
|
+
p.rescue(StandardError){ @expected = true }
|
283
|
+
@expected.should be_true
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context 'aliases' do
|
288
|
+
|
289
|
+
it 'aliases #realized? for #fulfilled?' do
|
290
|
+
fulfilled_subject.should be_realized
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'aliases #deref for #value' do
|
294
|
+
fulfilled_subject.deref.should eq fulfilled_value
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'aliases #catch for #rescue' do
|
298
|
+
@expected = nil
|
299
|
+
Promise.new{ raise StandardError }.catch{ @expected = true }
|
300
|
+
sleep(0.1)
|
301
|
+
@expected.should be_true
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'aliases #on_error for #rescue' do
|
305
|
+
@expected = nil
|
306
|
+
Promise.new{ raise StandardError }.on_error{ @expected = true }
|
307
|
+
sleep(0.1)
|
308
|
+
@expected.should be_true
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|