concurrent-ruby 0.4.1 → 0.5.0.pre.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.
- checksums.yaml +4 -4
- data/README.md +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe Obligation do
|
6
|
+
|
7
|
+
let (:obligation_class) do
|
8
|
+
|
9
|
+
Class.new do
|
10
|
+
include Obligation
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
init_mutex
|
14
|
+
end
|
15
|
+
|
16
|
+
public :state=, :compare_and_set_state, :if_state, :mutex
|
17
|
+
attr_writer :value, :reason
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let (:obligation) { obligation_class.new }
|
22
|
+
let (:event) { double 'event' }
|
23
|
+
|
24
|
+
share_examples_for :incomplete do
|
25
|
+
it 'should be not completed' do
|
26
|
+
obligation.should_not be_completed
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should be incomplete' do
|
30
|
+
obligation.should be_incomplete
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#value' do
|
34
|
+
|
35
|
+
it 'should return immediately if timeout is zero' do
|
36
|
+
obligation.value(0).should be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should block on the event if timeout is not set' do
|
40
|
+
obligation.stub(:event).and_return(event)
|
41
|
+
event.should_receive(:wait).with(nil)
|
42
|
+
|
43
|
+
obligation.value
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should block on the event if timeout is not zero' do
|
47
|
+
obligation.stub(:event).and_return(event)
|
48
|
+
event.should_receive(:wait).with(5)
|
49
|
+
|
50
|
+
obligation.value(5)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'unscheduled' do
|
56
|
+
before(:each) { obligation.state = :unscheduled }
|
57
|
+
it_should_behave_like :incomplete
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'pending' do
|
61
|
+
before(:each) { obligation.state = :pending }
|
62
|
+
it_should_behave_like :incomplete
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'fulfilled' do
|
66
|
+
|
67
|
+
before(:each) do
|
68
|
+
obligation.state = :fulfilled
|
69
|
+
obligation.send(:value=, 42)
|
70
|
+
obligation.stub(:event).and_return(event)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should be completed' do
|
74
|
+
obligation.should be_completed
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should be not incomplete' do
|
78
|
+
obligation.should_not be_incomplete
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#value' do
|
82
|
+
|
83
|
+
it 'should return immediately if timeout is zero' do
|
84
|
+
obligation.value(0).should eq 42
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should return immediately if timeout is not set' do
|
88
|
+
event.should_not_receive(:wait)
|
89
|
+
|
90
|
+
obligation.value.should eq 42
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should return immediately if timeout is not zero' do
|
94
|
+
event.should_not_receive(:wait)
|
95
|
+
|
96
|
+
obligation.value(5).should eq 42
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'rejected' do
|
105
|
+
|
106
|
+
before(:each) do
|
107
|
+
obligation.state = :rejected
|
108
|
+
obligation.stub(:event).and_return(event)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should be completed' do
|
112
|
+
obligation.should be_completed
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should be not incomplete' do
|
116
|
+
obligation.should_not be_incomplete
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#value' do
|
121
|
+
|
122
|
+
it 'should return immediately if timeout is zero' do
|
123
|
+
event.should_not_receive(:wait)
|
124
|
+
|
125
|
+
obligation.value(0).should be_nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should return immediately if timeout is not set' do
|
129
|
+
event.should_not_receive(:wait)
|
130
|
+
|
131
|
+
obligation.value.should be_nil
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should return immediately if timeout is not zero' do
|
135
|
+
event.should_not_receive(:wait)
|
136
|
+
|
137
|
+
obligation.value(5).should be_nil
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#compare_and_set_state' do
|
143
|
+
|
144
|
+
before(:each) { obligation.state = :unscheduled }
|
145
|
+
|
146
|
+
context 'unexpected state' do
|
147
|
+
it 'should return false if state is not the expected one' do
|
148
|
+
obligation.compare_and_set_state(:pending, :rejected).should be_false
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should not change the state if current is not the expected one' do
|
152
|
+
obligation.compare_and_set_state(:pending, :rejected)
|
153
|
+
obligation.state.should eq :unscheduled
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'expected state' do
|
158
|
+
it 'should return true if state is the expected one' do
|
159
|
+
obligation.compare_and_set_state(:pending, :unscheduled).should be_true
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should not change the state if current is not the expected one' do
|
163
|
+
obligation.compare_and_set_state(:pending, :unscheduled)
|
164
|
+
obligation.state.should eq :pending
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
describe '#if_state' do
|
171
|
+
|
172
|
+
before(:each) { obligation.state = :unscheduled }
|
173
|
+
|
174
|
+
it 'should raise without block' do
|
175
|
+
expect { obligation.if_state(:pending) }.to raise_error(ArgumentError)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should return false if state is not expected' do
|
179
|
+
obligation.if_state(:pending, :rejected) { 42 }.should be_false
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should the block value if state is expected' do
|
183
|
+
obligation.if_state(:rejected, :unscheduled) { 42 }.should eq 42
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should execute the block within the mutex' do
|
187
|
+
obligation.if_state(:unscheduled) { obligation.mutex.should be_locked }
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples "an observer set" do
|
4
|
+
|
5
|
+
let (:observer_set) { described_class.new }
|
6
|
+
let (:observer) { double('observer') }
|
7
|
+
let (:another_observer) { double('another observer') }
|
8
|
+
|
9
|
+
describe '#add_observer' do
|
10
|
+
|
11
|
+
context 'with argument' do
|
12
|
+
it 'should return the passed function' do
|
13
|
+
observer_set.add_observer(observer, :a_method).should eq(:a_method)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'without arguments' do
|
18
|
+
it 'should return the default function' do
|
19
|
+
observer_set.add_observer(observer).should eq(:update)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#notify_observers' do
|
25
|
+
it 'should return the observer set' do
|
26
|
+
observer_set.notify_observers.should be(observer_set)
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with a single observer' do
|
30
|
+
it 'should update a registered observer without arguments' do
|
31
|
+
expect(observer).to receive(:update).with(no_args)
|
32
|
+
|
33
|
+
observer_set.add_observer(observer)
|
34
|
+
|
35
|
+
observer_set.notify_observers
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should update a registered observer with arguments' do
|
39
|
+
expect(observer).to receive(:update).with(1, 2, 3)
|
40
|
+
|
41
|
+
observer_set.add_observer(observer)
|
42
|
+
|
43
|
+
observer_set.notify_observers(1, 2, 3)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should notify an observer using the chosen method' do
|
47
|
+
expect(observer).to receive(:another_method).with('a string arg')
|
48
|
+
|
49
|
+
observer_set.add_observer(observer, :another_method)
|
50
|
+
|
51
|
+
observer_set.notify_observers('a string arg')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should notify an observer once using the last added method' do
|
55
|
+
expect(observer).to receive(:another_method).with(any_args).never
|
56
|
+
expect(observer).to receive(:yet_another_method).with('a string arg')
|
57
|
+
|
58
|
+
observer_set.add_observer(observer, :another_method)
|
59
|
+
observer_set.add_observer(observer, :yet_another_method)
|
60
|
+
|
61
|
+
observer_set.notify_observers('a string arg')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can be called many times' do
|
65
|
+
expect(observer).to receive(:update).with(:an_arg).twice
|
66
|
+
expect(observer).to receive(:update).with(no_args).once
|
67
|
+
|
68
|
+
observer_set.add_observer(observer)
|
69
|
+
|
70
|
+
observer_set.notify_observers(:an_arg)
|
71
|
+
observer_set.notify_observers
|
72
|
+
observer_set.notify_observers(:an_arg)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with many observers' do
|
77
|
+
it 'should notify all observer using the chosen method' do
|
78
|
+
expect(observer).to receive(:a_method).with(4, 'a')
|
79
|
+
expect(another_observer).to receive(:update).with(4, 'a')
|
80
|
+
|
81
|
+
observer_set.add_observer(observer, :a_method)
|
82
|
+
observer_set.add_observer(another_observer)
|
83
|
+
|
84
|
+
observer_set.notify_observers(4, 'a')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with a block' do
|
89
|
+
|
90
|
+
before(:each) do
|
91
|
+
observer.stub(:update).with(any_args)
|
92
|
+
another_observer.stub(:update).with(any_args)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'calls the block once for every observer' do
|
96
|
+
|
97
|
+
counter = double('block call counter')
|
98
|
+
expect(counter).to receive(:called).with(no_args).exactly(2).times
|
99
|
+
|
100
|
+
observer_set.add_observer(observer)
|
101
|
+
observer_set.add_observer(another_observer)
|
102
|
+
|
103
|
+
observer_set.notify_observers{ counter.called }
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'passes the block return value to the update method' do
|
107
|
+
|
108
|
+
expect(observer).to receive(:update).with(1, 2, 3, 4)
|
109
|
+
observer_set.add_observer(observer)
|
110
|
+
observer_set.notify_observers{ [1, 2, 3, 4] }
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'accepts blocks returning a single value' do
|
114
|
+
|
115
|
+
expect(observer).to receive(:update).with(:foo)
|
116
|
+
observer_set.add_observer(observer)
|
117
|
+
observer_set.notify_observers{ :foo }
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'accepts block return values that include arrays' do
|
121
|
+
|
122
|
+
expect(observer).to receive(:update).with(1, [2, 3], 4)
|
123
|
+
observer_set.add_observer(observer)
|
124
|
+
observer_set.notify_observers{ [1, [2, 3], 4] }
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'raises an exception if given both arguments and a block' do
|
128
|
+
|
129
|
+
observer_set.add_observer(observer)
|
130
|
+
|
131
|
+
expect {
|
132
|
+
observer_set.notify_observers(1, 2, 3, 4){ nil }
|
133
|
+
}.to raise_error(ArgumentError)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context '#count_observers' do
|
139
|
+
it 'should be zero after initialization' do
|
140
|
+
observer_set.count_observers.should eq 0
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should be 1 after the first observer is added' do
|
144
|
+
observer_set.add_observer(observer)
|
145
|
+
observer_set.count_observers.should eq 1
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should be 1 if the same observer is added many times' do
|
149
|
+
observer_set.add_observer(observer)
|
150
|
+
observer_set.add_observer(observer, :another_method)
|
151
|
+
observer_set.add_observer(observer, :yet_another_method)
|
152
|
+
|
153
|
+
observer_set.count_observers.should eq 1
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should be equal to the number of unique observers' do
|
157
|
+
observer_set.add_observer(observer)
|
158
|
+
observer_set.add_observer(another_observer)
|
159
|
+
observer_set.add_observer(double('observer 3'))
|
160
|
+
observer_set.add_observer(double('observer 4'))
|
161
|
+
|
162
|
+
observer_set.count_observers.should eq 4
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#delete_observer' do
|
167
|
+
it 'should not notify a deleted observer' do
|
168
|
+
expect(observer).to receive(:update).never
|
169
|
+
|
170
|
+
observer_set.add_observer(observer)
|
171
|
+
observer_set.delete_observer(observer)
|
172
|
+
|
173
|
+
observer_set.notify_observers
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'can delete a non added observer' do
|
177
|
+
observer_set.delete_observer(observer)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should return the observer' do
|
181
|
+
observer_set.delete_observer(observer).should be(observer)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#delete_observers' do
|
186
|
+
it 'should remove all observers' do
|
187
|
+
expect(observer).to receive(:update).never
|
188
|
+
expect(another_observer).to receive(:update).never
|
189
|
+
|
190
|
+
observer_set.add_observer(observer)
|
191
|
+
observer_set.add_observer(another_observer)
|
192
|
+
|
193
|
+
observer_set.delete_observers
|
194
|
+
|
195
|
+
observer_set.notify_observers
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should return the observer set' do
|
199
|
+
observer_set.delete_observers.should be(observer_set)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe '#notify_and_delete_observers' do
|
204
|
+
before(:each) do
|
205
|
+
observer_set.add_observer(observer, :a_method)
|
206
|
+
observer_set.add_observer(another_observer)
|
207
|
+
|
208
|
+
expect(observer).to receive(:a_method).with('args').once
|
209
|
+
expect(another_observer).to receive(:update).with('args').once
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should notify all observers' do
|
213
|
+
observer_set.notify_and_delete_observers('args')
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should clear observers' do
|
217
|
+
observer_set.notify_and_delete_observers('args')
|
218
|
+
|
219
|
+
observer_set.count_observers.should eq(0)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'can be called many times without any other notification' do
|
223
|
+
observer_set.notify_and_delete_observers('args')
|
224
|
+
observer_set.notify_and_delete_observers('args')
|
225
|
+
observer_set.notify_and_delete_observers('args')
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'should return the observer set' do
|
229
|
+
observer_set.notify_and_delete_observers('args').should be(observer_set)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
@@ -31,15 +31,11 @@ share_examples_for :postable do
|
|
31
31
|
postable.stop
|
32
32
|
end
|
33
33
|
|
34
|
-
it 'returns
|
34
|
+
it 'returns true on success' do
|
35
35
|
postable = postable_class.new{|msg| sleep }
|
36
36
|
@thread = Thread.new{ postable.run }
|
37
|
-
@thread.join(0.1)
|
38
|
-
postable.post(true).should
|
39
|
-
@thread.join(0.1)
|
40
|
-
postable.post(true).should == 1
|
41
|
-
@thread.join(0.1)
|
42
|
-
postable.post(true).should == 2
|
37
|
+
5.times{ @thread.join(0.1); break if postable.running? }
|
38
|
+
postable.post(true).should be_true
|
43
39
|
postable.stop
|
44
40
|
end
|
45
41
|
|
@@ -9,20 +9,20 @@ module Concurrent
|
|
9
9
|
let!(:thread_pool_user){ Promise }
|
10
10
|
it_should_behave_like Concurrent::UsesGlobalThreadPool
|
11
11
|
|
12
|
+
let(:empty_root) { Promise.new { nil } }
|
12
13
|
let!(:fulfilled_value) { 10 }
|
13
14
|
let!(:rejected_reason) { StandardError.new('mojo jojo') }
|
14
15
|
|
15
16
|
let(:pending_subject) do
|
16
|
-
Promise.new{ sleep(3); fulfilled_value }
|
17
|
+
Promise.new{ sleep(0.3); fulfilled_value }.execute
|
17
18
|
end
|
18
19
|
|
19
20
|
let(:fulfilled_subject) do
|
20
|
-
Promise.
|
21
|
+
Promise.fulfill(fulfilled_value)
|
21
22
|
end
|
22
23
|
|
23
24
|
let(:rejected_subject) do
|
24
|
-
Promise.
|
25
|
-
rescue{ nil }.tap(){ sleep(0.1) }
|
25
|
+
Promise.reject( rejected_reason )
|
26
26
|
end
|
27
27
|
|
28
28
|
before(:each) do
|
@@ -36,241 +36,323 @@ module Concurrent
|
|
36
36
|
promise.should be_a(Dereferenceable)
|
37
37
|
end
|
38
38
|
|
39
|
-
context '
|
39
|
+
context 'initializers' do
|
40
|
+
describe '.fulfill' do
|
41
|
+
|
42
|
+
subject { Promise.fulfill(10) }
|
43
|
+
|
44
|
+
it 'should return a Promise' do
|
45
|
+
subject.should be_a Promise
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should return a fulfilled Promise' do
|
49
|
+
subject.should be_fulfilled
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should return a Promise with set value' do
|
53
|
+
subject.value.should eq 10
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '.reject' do
|
58
|
+
|
59
|
+
let(:reason) { ArgumentError.new }
|
60
|
+
subject { Promise.reject(reason) }
|
61
|
+
|
62
|
+
it 'should return a Promise' do
|
63
|
+
subject.should be_a Promise
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should return a rejected Promise' do
|
67
|
+
subject.should be_rejected
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should return a Promise with set reason' do
|
71
|
+
subject.reason.should be reason
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '.new' do
|
76
|
+
it 'should return an unscheduled Promise' do
|
77
|
+
p = Promise.new { nil }
|
78
|
+
p.should be_unscheduled
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '.execute' do
|
83
|
+
it 'creates a new Promise' do
|
84
|
+
p = Promise.execute{ nil }
|
85
|
+
p.should be_a(Promise)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'passes the block to the new Promise' do
|
89
|
+
p = Promise.execute { 20 }
|
90
|
+
sleep(0.1)
|
91
|
+
p.value.should eq 20
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'calls #execute on the new Promise' do
|
95
|
+
p = double('promise')
|
96
|
+
Promise.stub(:new).with(any_args).and_return(p)
|
97
|
+
p.should_receive(:execute).with(no_args)
|
98
|
+
Promise.execute{ nil }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context '#execute' do
|
104
|
+
|
105
|
+
context 'unscheduled' do
|
106
|
+
|
107
|
+
it 'sets the promise to :pending' do
|
108
|
+
p = Promise.new { sleep(0.1) }.execute
|
109
|
+
p.should be_pending
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'posts the block given in construction' do
|
113
|
+
Promise.thread_pool.should_receive(:post).with(any_args)
|
114
|
+
Promise.new { nil }.execute
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'pending' do
|
120
|
+
|
121
|
+
it 'sets the promise to :pending' do
|
122
|
+
p = pending_subject.execute
|
123
|
+
p.should be_pending
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'does not posts again' do
|
127
|
+
Promise.thread_pool.should_receive(:post).with(any_args).once
|
128
|
+
pending_subject.execute
|
129
|
+
end
|
40
130
|
|
41
|
-
it 'returns a new Promise when :pending' do
|
42
|
-
p1 = pending_subject
|
43
|
-
p2 = p1.then{}
|
44
|
-
p2.should be_a(Promise)
|
45
|
-
p1.should_not eq p2
|
46
131
|
end
|
47
132
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
133
|
+
|
134
|
+
describe 'with children' do
|
135
|
+
|
136
|
+
let(:root) { Promise.new { sleep(0.1); nil } }
|
137
|
+
let(:c1) { root.then { nil } }
|
138
|
+
let(:c2) { root.then { nil } }
|
139
|
+
let(:c2_1) { c2.then { nil } }
|
140
|
+
|
141
|
+
context 'when called on the root' do
|
142
|
+
it 'should set all promises to :pending' do
|
143
|
+
root.execute
|
144
|
+
|
145
|
+
c1.should be_pending
|
146
|
+
c2.should be_pending
|
147
|
+
c2_1.should be_pending
|
148
|
+
[root, c1, c2, c2_1].each { |p| p.should be_pending }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when called on a child' do
|
153
|
+
it 'should set all promises to :pending' do
|
154
|
+
c2_1.execute
|
155
|
+
|
156
|
+
[root, c1, c2, c2_1].each { |p| p.should be_pending }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe '#then' do
|
164
|
+
|
165
|
+
it 'returns a new promise when a block is passed' do
|
166
|
+
child = empty_root.then { nil }
|
167
|
+
child.should be_a Promise
|
168
|
+
child.should_not be empty_root
|
53
169
|
end
|
54
170
|
|
55
|
-
it 'returns a new
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
p1.should_not eq p2
|
171
|
+
it 'returns a new promise when a rescuer is passed' do
|
172
|
+
child = empty_root.then(Proc.new{})
|
173
|
+
child.should be_a Promise
|
174
|
+
child.should_not be empty_root
|
60
175
|
end
|
61
176
|
|
62
|
-
it '
|
63
|
-
|
64
|
-
|
177
|
+
it 'returns a new promise when a block and rescuer are passed' do
|
178
|
+
child = empty_root.then(Proc.new{}) { nil }
|
179
|
+
child.should be_a Promise
|
180
|
+
child.should_not be empty_root
|
65
181
|
end
|
66
182
|
|
67
|
-
it '
|
68
|
-
|
69
|
-
|
70
|
-
|
183
|
+
it 'should have block or rescuers' do
|
184
|
+
expect { empty_root.then }.to raise_error(ArgumentError)
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'unscheduled' do
|
188
|
+
|
189
|
+
let(:p1) { Promise.new {nil} }
|
190
|
+
let(:child) { p1.then{} }
|
191
|
+
|
192
|
+
it 'returns a new promise' do
|
193
|
+
child.should be_a Promise
|
194
|
+
p1.should_not be child
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'returns an unscheduled promise' do
|
198
|
+
child.should be_unscheduled
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'pending' do
|
203
|
+
|
204
|
+
let(:child) { pending_subject.then{} }
|
205
|
+
|
206
|
+
it 'returns a new promise' do
|
207
|
+
child.should be_a Promise
|
208
|
+
pending_subject.should_not be child
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'returns a pending promise' do
|
212
|
+
child.should be_pending
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'fulfilled' do
|
217
|
+
it 'returns a new Promise' do
|
218
|
+
p1 = fulfilled_subject
|
219
|
+
p2 = p1.then{}
|
220
|
+
p2.should be_a(Promise)
|
221
|
+
p1.should_not eq p2
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'notifies fulfillment to new child' do
|
225
|
+
child = fulfilled_subject.then(Proc.new{ 7 }) { |v| v + 5 }
|
226
|
+
child.value.should eq fulfilled_value + 5
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'rejected' do
|
232
|
+
it 'returns a new Promise when :rejected' do
|
233
|
+
p1 = rejected_subject
|
234
|
+
p2 = p1.then{}
|
235
|
+
p2.should be_a(Promise)
|
236
|
+
p1.should_not eq p2
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'notifies rejection to new child' do
|
240
|
+
child = rejected_subject.then(Proc.new{ 7 }) { |v| v + 5 }
|
241
|
+
child.value.should eq 7
|
242
|
+
end
|
243
|
+
|
71
244
|
end
|
72
245
|
|
73
246
|
it 'can be called more than once' do
|
74
247
|
p = pending_subject
|
75
248
|
p1 = p.then{}
|
76
249
|
p2 = p.then{}
|
77
|
-
p1.
|
250
|
+
p1.should_not be p2
|
78
251
|
end
|
79
252
|
end
|
80
253
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
p1 = pending_subject
|
85
|
-
p2 = p1.rescue{}
|
86
|
-
p1.object_id.should eq p2.object_id
|
254
|
+
describe 'on_success' do
|
255
|
+
it 'should have a block' do
|
256
|
+
expect { empty_root.on_success }.to raise_error(ArgumentError)
|
87
257
|
end
|
88
258
|
|
89
|
-
it 'returns
|
90
|
-
|
91
|
-
|
92
|
-
|
259
|
+
it 'returns a new promise' do
|
260
|
+
child = empty_root.on_success { nil }
|
261
|
+
child.should be_a Promise
|
262
|
+
child.should_not be empty_root
|
93
263
|
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context '#rescue' do
|
94
267
|
|
95
|
-
it '
|
96
|
-
|
97
|
-
|
98
|
-
|
268
|
+
it 'returns a new promise' do
|
269
|
+
child = empty_root.rescue { nil }
|
270
|
+
child.should be_a Promise
|
271
|
+
child.should_not be empty_root
|
99
272
|
end
|
100
273
|
end
|
101
274
|
|
102
275
|
context 'fulfillment' do
|
103
276
|
|
104
|
-
it 'passes all arguments to the first promise in the chain' do
|
105
|
-
@a = @b = @c = nil
|
106
|
-
p = Promise.new(1, 2, 3) do |a, b, c|
|
107
|
-
@a, @b, @c = a, b, c
|
108
|
-
end
|
109
|
-
sleep(0.1)
|
110
|
-
[@a, @b, @c].should eq [1, 2, 3]
|
111
|
-
end
|
112
|
-
|
113
277
|
it 'passes the result of each block to all its children' do
|
114
|
-
|
115
|
-
Promise.new
|
278
|
+
expected = nil
|
279
|
+
Promise.new{ 20 }.then{ |result| expected = result }.execute
|
116
280
|
sleep(0.1)
|
117
|
-
|
281
|
+
expected.should eq 20
|
118
282
|
end
|
119
283
|
|
120
284
|
it 'sets the promise value to the result if its block' do
|
121
|
-
|
285
|
+
root = Promise.new{ 20 }
|
286
|
+
p = root.then{ |result| result * 2}.execute
|
122
287
|
sleep(0.1)
|
288
|
+
root.value.should eq 20
|
123
289
|
p.value.should eq 40
|
124
290
|
end
|
125
291
|
|
126
292
|
it 'sets the promise state to :fulfilled if the block completes' do
|
127
|
-
p = Promise.new
|
293
|
+
p = Promise.new{ 10 * 2 }.then{|result| result * 2}.execute
|
128
294
|
sleep(0.1)
|
129
295
|
p.should be_fulfilled
|
130
296
|
end
|
131
297
|
|
132
298
|
it 'passes the last result through when a promise has no block' do
|
133
|
-
|
134
|
-
Promise.new
|
299
|
+
expected = nil
|
300
|
+
Promise.new{ 20 }.then(Proc.new{}).then{|result| expected = result}.execute
|
301
|
+
sleep(0.1)
|
302
|
+
expected.should eq 20
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'uses result as fulfillment value when a promise has no block' do
|
306
|
+
p = Promise.new{ 20 }.then(Proc.new{}).execute
|
307
|
+
sleep(0.1)
|
308
|
+
p.value.should eq 20
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'can manage long chain' do
|
312
|
+
root = Promise.new { 20 }
|
313
|
+
p1 = root.then { |b| b * 3 }
|
314
|
+
p2 = root.then { |c| c + 2 }
|
315
|
+
p3 = p1.then { |d| d + 7 }
|
316
|
+
|
317
|
+
root.execute
|
135
318
|
sleep(0.1)
|
136
|
-
|
319
|
+
|
320
|
+
root.value.should eq 20
|
321
|
+
p1.value.should eq 60
|
322
|
+
p2.value.should eq 22
|
323
|
+
p3.value.should eq 67
|
137
324
|
end
|
138
325
|
end
|
139
326
|
|
140
327
|
context 'rejection' do
|
141
328
|
|
142
|
-
it '
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
329
|
+
it 'passes the reason to all its children' do
|
330
|
+
expected = nil
|
331
|
+
Promise.new{ raise ArgumentError }.then(Proc.new{ |reason| expected = reason }).execute
|
332
|
+
sleep(0.1)
|
333
|
+
expected.should be_a ArgumentError
|
147
334
|
end
|
148
335
|
|
149
|
-
it 'sets the promise
|
150
|
-
|
336
|
+
it 'sets the promise value to the result if its block' do
|
337
|
+
root = Promise.new{ raise ArgumentError }
|
338
|
+
p = root.then(Proc.new{ |reason| 42 }).execute
|
339
|
+
sleep(0.1)
|
340
|
+
p.value.should eq 42
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'sets the promise state to :rejected if the block completes' do
|
344
|
+
p = Promise.new{ raise ArgumentError }.execute
|
151
345
|
sleep(0.1)
|
152
346
|
p.should be_rejected
|
153
347
|
end
|
154
348
|
|
155
|
-
it '
|
156
|
-
p = Promise.new{
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
it 'skips processing rejected promises' do
|
163
|
-
p = Promise.new{ sleep(0.1); raise StandardError.new('Boom!') }
|
164
|
-
promises = 3.times.collect{ p.then{ true } }
|
165
|
-
sleep(0.5)
|
166
|
-
promises.each{|p| p.value.should_not be_true }
|
167
|
-
end
|
168
|
-
|
169
|
-
it 'calls the first exception block with a matching class' do
|
170
|
-
@expected = nil
|
171
|
-
Promise.new{ sleep(0.1); raise StandardError }.
|
172
|
-
rescue(StandardError){|ex| @expected = 1 }.
|
173
|
-
rescue(StandardError){|ex| @expected = 2 }.
|
174
|
-
rescue(StandardError){|ex| @expected = 3 }
|
175
|
-
sleep(0.2)
|
176
|
-
@expected.should eq 1
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'matches all with a rescue with no class given' do
|
180
|
-
@expected = nil
|
181
|
-
Promise.new{ sleep(0.1); raise NoMethodError }.
|
182
|
-
rescue(LoadError){|ex| @expected = 1 }.
|
183
|
-
rescue{|ex| @expected = 2 }.
|
184
|
-
rescue(StandardError){|ex| @expected = 3 }
|
185
|
-
sleep(0.2)
|
186
|
-
@expected.should eq 2
|
187
|
-
end
|
188
|
-
|
189
|
-
it 'searches associated rescue handlers in order' do
|
190
|
-
Promise.thread_pool = CachedThreadPool.new
|
191
|
-
|
192
|
-
@expected = nil
|
193
|
-
Promise.new{ sleep(0.1); raise ArgumentError }.
|
194
|
-
rescue(ArgumentError){|ex| @expected = 1 }.
|
195
|
-
rescue(LoadError){|ex| @expected = 2 }.
|
196
|
-
rescue(StandardError){|ex| @expected = 3 }
|
197
|
-
sleep(0.2)
|
198
|
-
@expected.should eq 1
|
199
|
-
|
200
|
-
@expected = nil
|
201
|
-
Promise.new{ sleep(0.1); raise LoadError }.
|
202
|
-
rescue(ArgumentError){|ex| @expected = 1 }.
|
203
|
-
rescue(LoadError){|ex| @expected = 2 }.
|
204
|
-
rescue(StandardError){|ex| @expected = 3 }
|
205
|
-
sleep(0.2)
|
206
|
-
@expected.should eq 2
|
207
|
-
|
208
|
-
@expected = nil
|
209
|
-
Promise.new{ sleep(0.1); raise StandardError }.
|
210
|
-
rescue(ArgumentError){|ex| @expected = 1 }.
|
211
|
-
rescue(LoadError){|ex| @expected = 2 }.
|
212
|
-
rescue(StandardError){|ex| @expected = 3 }
|
213
|
-
sleep(0.2)
|
214
|
-
@expected.should eq 3
|
215
|
-
end
|
216
|
-
|
217
|
-
it 'passes the exception object to the matched block' do
|
218
|
-
@expected = nil
|
219
|
-
Promise.new{ sleep(0.1); raise StandardError }.
|
220
|
-
rescue(ArgumentError){|ex| @expected = ex }.
|
221
|
-
rescue(LoadError){|ex| @expected = ex }.
|
222
|
-
rescue(StandardError){|ex| @expected = ex }
|
223
|
-
sleep(0.2)
|
224
|
-
@expected.should be_a(StandardError)
|
225
|
-
end
|
226
|
-
|
227
|
-
it 'ignores rescuers without a block' do
|
228
|
-
@expected = nil
|
229
|
-
Promise.new{ sleep(0.1); raise StandardError }.
|
230
|
-
rescue(StandardError).
|
231
|
-
rescue(StandardError){|ex| @expected = ex }
|
232
|
-
sleep(0.2)
|
233
|
-
@expected.should be_a(StandardError)
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'supresses the exception if no rescue matches' do
|
237
|
-
lambda {
|
238
|
-
Promise.new{ sleep(0.1); raise StandardError }.
|
239
|
-
rescue(ArgumentError){|ex| @expected = ex }.
|
240
|
-
rescue(NotImplementedError){|ex| @expected = ex }.
|
241
|
-
rescue(NoMethodError){|ex| @expected = ex }
|
242
|
-
sleep(0.2)
|
243
|
-
}.should_not raise_error
|
244
|
-
end
|
245
|
-
|
246
|
-
it 'supresses exceptions thrown from rescue handlers' do
|
247
|
-
lambda {
|
248
|
-
Promise.new{ sleep(0.1); raise ArgumentError }.
|
249
|
-
rescue(StandardError){ raise StandardError }
|
250
|
-
sleep(0.2)
|
251
|
-
}.should_not raise_error
|
252
|
-
end
|
253
|
-
|
254
|
-
it 'calls matching rescue handlers on all children' do
|
255
|
-
@expected = []
|
256
|
-
Promise.new{ sleep(0.1); raise StandardError }.
|
257
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
258
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
259
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
260
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
261
|
-
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }
|
262
|
-
sleep(1)
|
263
|
-
|
264
|
-
@expected.length.should eq 5
|
265
|
-
end
|
266
|
-
|
267
|
-
it 'matches a rescue handler added after rejection' do
|
268
|
-
@expected = false
|
269
|
-
p = Promise.new{ sleep(0.1); raise StandardError }
|
270
|
-
sleep(0.2)
|
271
|
-
p.rescue(StandardError){ @expected = true }
|
272
|
-
@expected.should be_true
|
349
|
+
it 'uses reason as rejection reason when a promise has no rescue callable' do
|
350
|
+
p = Promise.new{ raise ArgumentError }.then { |val| val }.execute
|
351
|
+
sleep(0.1)
|
352
|
+
p.should be_rejected
|
353
|
+
p.reason.should be_a ArgumentError
|
273
354
|
end
|
355
|
+
|
274
356
|
end
|
275
357
|
|
276
358
|
context 'aliases' do
|
@@ -284,17 +366,13 @@ module Concurrent
|
|
284
366
|
end
|
285
367
|
|
286
368
|
it 'aliases #catch for #rescue' do
|
287
|
-
|
288
|
-
|
289
|
-
sleep(0.1)
|
290
|
-
@expected.should be_true
|
369
|
+
child = rejected_subject.catch { 7 }
|
370
|
+
child.value.should eq 7
|
291
371
|
end
|
292
372
|
|
293
373
|
it 'aliases #on_error for #rescue' do
|
294
|
-
|
295
|
-
|
296
|
-
sleep(0.1)
|
297
|
-
@expected.should be_true
|
374
|
+
child = rejected_subject.on_error { 7 }
|
375
|
+
child.value.should eq 7
|
298
376
|
end
|
299
377
|
end
|
300
378
|
end
|