concurrent-ruby 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require_relative 'thread_pool_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe FixedThreadPool do
7
+
8
+ subject { FixedThreadPool.new(5) }
9
+
10
+ it_should_behave_like 'Thread Pool'
11
+
12
+ context '#initialize' do
13
+
14
+ it 'raises an exception when the pool size is less than one' do
15
+ lambda {
16
+ FixedThreadPool.new(0)
17
+ }.should raise_error(ArgumentError)
18
+ end
19
+
20
+ it 'raises an exception when the pool size is greater than 1024' do
21
+ lambda {
22
+ FixedThreadPool.new(1025)
23
+ }.should raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'creates a thread pool of the given size' do
27
+ thread = double('thread')
28
+ # add one for the garbage collector
29
+ Thread.should_receive(:new).exactly(5+1).times.and_return(thread)
30
+ pool = FixedThreadPool.new(5)
31
+ pool.size.should eq 5
32
+ end
33
+
34
+ it 'aliases Concurrent#new_fixed_thread_pool' do
35
+ pool = Concurrent.new_fixed_thread_pool(5)
36
+ pool.should be_a(FixedThreadPool)
37
+ pool.size.should eq 5
38
+ end
39
+ end
40
+
41
+ context '#kill' do
42
+
43
+ it 'kills all threads' do
44
+ Thread.should_receive(:kill).exactly(5).times
45
+ pool = FixedThreadPool.new(5)
46
+ pool.kill
47
+ sleep(0.1)
48
+ end
49
+ end
50
+
51
+ context '#size' do
52
+
53
+ let(:pool_size) { 3 }
54
+ subject { FixedThreadPool.new(pool_size) }
55
+
56
+ it 'returns the size of the subject when running' do
57
+ subject.size.should eq pool_size
58
+ end
59
+
60
+ it 'returns zero while shutting down' do
61
+ subject.post{ sleep(1) }
62
+ subject.shutdown
63
+ subject.size.should eq 0
64
+ end
65
+
66
+ it 'returns zero once shut down' do
67
+ subject.shutdown
68
+ subject.size.should eq 0
69
+ end
70
+ end
71
+
72
+ context 'exception handling' do
73
+
74
+ it 'restarts threads that experience exception' do
75
+ pool = FixedThreadPool.new(5)
76
+ 3.times{ pool << proc{ raise StandardError } }
77
+ sleep(2)
78
+ pool.size.should eq 5
79
+ pool.status.should_not include(nil)
80
+ #pool.status.include?(nil).should be_false
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+ require_relative 'obligation_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe Future do
7
+
8
+ let!(:fulfilled_value) { 10 }
9
+ let!(:rejected_reason) { StandardError.new('mojo jojo') }
10
+
11
+ let(:pending_subject) do
12
+ Future.new{ sleep(2) }
13
+ end
14
+
15
+ let(:fulfilled_subject) do
16
+ Future.new{ fulfilled_value }.tap(){ sleep(0.1) }
17
+ end
18
+
19
+ let(:rejected_subject) do
20
+ Future.new{ raise rejected_reason }.tap(){ sleep(0.1) }
21
+ end
22
+
23
+ before(:each) do
24
+ $GLOBAL_THREAD_POOL = CachedThreadPool.new
25
+ end
26
+
27
+ it_should_behave_like Obligation
28
+
29
+ context 'behavior' do
30
+
31
+ it 'implements :future behavior' do
32
+ lambda {
33
+ Future.new{ nil }
34
+ }.should_not raise_error
35
+
36
+ Future.new{ nil }.behaves_as?(:future).should be_true
37
+ end
38
+ end
39
+
40
+ context '#initialize' do
41
+
42
+ it 'spawns a new thread when a block is given' do
43
+ $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
44
+ Future.new{ nil }
45
+ end
46
+
47
+ it 'does not spawns a new thread when no block given' do
48
+ Thread.should_not_receive(:new).with(any_args())
49
+ Future.new
50
+ end
51
+
52
+ it 'immediately sets the state to :fulfilled when no block given' do
53
+ Future.new.should be_fulfilled
54
+ end
55
+
56
+ it 'immediately sets the value to nil when no block given' do
57
+ Future.new.value.should be_nil
58
+ end
59
+ end
60
+
61
+ context 'fulfillment' do
62
+
63
+ it 'passes all arguments to handler' do
64
+ @a = @b = @c = nil
65
+ f = Future.new(1, 2, 3) do |a, b, c|
66
+ @a, @b, @c = a, b, c
67
+ end
68
+ sleep(0.1)
69
+ [@a, @b, @c].should eq [1, 2, 3]
70
+ end
71
+
72
+ it 'sets the value to the result of the handler' do
73
+ f = Future.new(10){|a| a * 2 }
74
+ sleep(0.1)
75
+ f.value.should eq 20
76
+ end
77
+
78
+ it 'sets the state to :fulfilled when the block completes' do
79
+ f = Future.new(10){|a| a * 2 }
80
+ sleep(0.1)
81
+ f.should be_fulfilled
82
+ end
83
+
84
+ it 'sets the value to nil when the handler raises an exception' do
85
+ f = Future.new{ raise StandardError }
86
+ sleep(0.1)
87
+ f.value.should be_nil
88
+ end
89
+
90
+ it 'sets the state to :rejected when the handler raises an exception' do
91
+ f = Future.new{ raise StandardError }
92
+ sleep(0.1)
93
+ f.should be_rejected
94
+ end
95
+
96
+ context 'aliases' do
97
+
98
+ it 'aliases #realized? for #fulfilled?' do
99
+ fulfilled_subject.should be_realized
100
+ end
101
+
102
+ it 'aliases #deref for #value' do
103
+ fulfilled_subject.deref.should eq fulfilled_value
104
+ end
105
+
106
+ it 'aliases Kernel#future for Future.new' do
107
+ future().should be_a(Future)
108
+ future(){ nil }.should be_a(Future)
109
+ future(1, 2, 3).should be_a(Future)
110
+ future(1, 2, 3){ nil }.should be_a(Future)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe '#go' do
6
+
7
+ before(:each) do
8
+ $GLOBAL_THREAD_POOL = CachedThreadPool.new
9
+ end
10
+
11
+ it 'passes all arguments to the block' do
12
+ @expected = nil
13
+ go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
14
+ sleep(0.1)
15
+ @expected.should eq [3, 2, 1]
16
+ end
17
+
18
+ it 'returns true if the thread is successfully created' do
19
+ $GLOBAL_THREAD_POOL.should_receive(:post).and_return(true)
20
+ go{ nil }.should be_true
21
+ end
22
+
23
+ it 'returns false if the thread cannot be created' do
24
+ $GLOBAL_THREAD_POOL.should_receive(:post).and_return(false)
25
+ go{ nil }.should be_false
26
+ end
27
+
28
+ it 'immediately returns false if no block is given' do
29
+ go().should be_false
30
+ end
31
+
32
+ it 'does not create a thread if no block is given' do
33
+ $GLOBAL_THREAD_POOL.should_not_receive(:post)
34
+ go()
35
+ sleep(0.1)
36
+ end
37
+
38
+ it 'supresses exceptions on the thread' do
39
+ lambda{
40
+ go{ raise StandardError }
41
+ sleep(0.1)
42
+ }.should_not raise_error
43
+ end
44
+
45
+ it 'processes the block' do
46
+ @expected = false
47
+ go(1,2,3){|*args| @expected = args }
48
+ sleep(0.1)
49
+ @expected.should eq [1,2,3]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ share_examples_for Obligation do
6
+
7
+ context '#state' do
8
+
9
+ it 'is :pending when first created' do
10
+ f = pending_subject
11
+ f.state.should == :pending
12
+ f.should be_pending
13
+ end
14
+
15
+ it 'is :fulfilled when the handler completes' do
16
+ f = fulfilled_subject
17
+ f.state.should == :fulfilled
18
+ f.should be_fulfilled
19
+ end
20
+
21
+ it 'is :rejected when the handler raises an exception' do
22
+ f = rejected_subject
23
+ f.state.should == :rejected
24
+ f.should be_rejected
25
+ end
26
+ end
27
+
28
+ context '#value' do
29
+
30
+ it 'blocks the caller when :pending and timeout is nil' do
31
+ f = pending_subject
32
+ sleep(0.1)
33
+ f.value.should be_true
34
+ f.should be_fulfilled
35
+ end
36
+
37
+ it 'returns nil when reaching the optional timeout value' do
38
+ f = pending_subject
39
+ sleep(0.1)
40
+ f.value(0.1).should be_nil
41
+ f.should be_pending
42
+ end
43
+
44
+ it 'returns immediately when timeout is zero' do
45
+ Timeout.should_not_receive(:timeout).with(any_args())
46
+ f = pending_subject
47
+ sleep(0.1)
48
+ f.value(0).should be_nil
49
+ f.should be_pending
50
+ end
51
+
52
+ it 'is nil when :pending' do
53
+ expected = pending_subject.value
54
+ expected.should be_nil
55
+ end
56
+
57
+ it 'is nil when :rejected' do
58
+ expected = rejected_subject.value
59
+ expected.should be_nil
60
+ end
61
+
62
+ it 'is set to the return value of the block when :fulfilled' do
63
+ expected = fulfilled_subject.value
64
+ expected.should eq fulfilled_value
65
+ end
66
+ end
67
+
68
+ context '#reason' do
69
+
70
+ it 'is nil when :pending' do
71
+ pending_subject.reason.should be_nil
72
+ end
73
+
74
+ it 'is nil when :fulfilled' do
75
+ fulfilled_subject.reason.should be_nil
76
+ end
77
+
78
+ it 'is set to error object of the exception when :rejected' do
79
+ rejected_subject.reason.should be_a(Exception)
80
+ rejected_subject.reason.to_s.should =~ /#{rejected_reason}/
81
+ end
82
+ end
83
+
84
+ context 'Kernel aliases' do
85
+
86
+ it 'aliases Kernel#deref for #deref' do
87
+ deref(fulfilled_subject).should eq fulfilled_value
88
+ deref(fulfilled_subject, 0).should eq fulfilled_value
89
+ end
90
+
91
+ it 'aliases Kernel#pending? for #pending?' do
92
+ #NOTE: was structured like others but was incorrectly failing
93
+ # on fulfilled_subject
94
+ fulfilled_subject.should_receive(:pending?).once
95
+ pending?(fulfilled_subject)
96
+ pending_subject.should_receive(:pending?).once
97
+ pending?(pending_subject)
98
+ rejected_subject.should_receive(:pending?).once
99
+ pending?(rejected_subject)
100
+ end
101
+
102
+ it 'aliases Kernel#fulfilled? for #fulfilled?' do
103
+ fulfilled?(fulfilled_subject).should be_true
104
+ fulfilled?(pending_subject).should be_false
105
+ fulfilled?(rejected_subject).should be_false
106
+ end
107
+
108
+ it 'aliases Kernel#realized? for #realized?' do
109
+ realized?(fulfilled_subject).should be_true
110
+ realized?(pending_subject).should be_false
111
+ realized?(rejected_subject).should be_false
112
+ end
113
+
114
+ it 'aliases Kernel#rejected? for #rejected?' do
115
+ rejected?(rejected_subject).should be_true
116
+ rejected?(fulfilled_subject).should be_false
117
+ rejected?(pending_subject).should be_false
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,310 @@
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(1) }
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
+ $GLOBAL_THREAD_POOL = CachedThreadPool.new
26
+ end
27
+
28
+ it_should_behave_like 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{ Thread.pass; raise StandardError.new('Boom!') }
167
+ promises = 10.times.collect{ p.then{ true } }
168
+ sleep(0.1)
169
+
170
+ 10.times.each{|i| promises[i].should be_rejected }
171
+ end
172
+
173
+ it 'skips processing rejected promises' do
174
+ p = Promise.new{ raise StandardError.new('Boom!') }
175
+ promises = 3.times.collect{ p.then{ true } }
176
+ sleep(0.1)
177
+ promises.each{|p| p.value.should_not be_true }
178
+ end
179
+
180
+ it 'calls the first exception block with a matching class' do
181
+ @expected = nil
182
+ Promise.new{ raise StandardError }.
183
+ rescue(StandardError){|ex| @expected = 1 }.
184
+ rescue(StandardError){|ex| @expected = 2 }.
185
+ rescue(StandardError){|ex| @expected = 3 }
186
+ sleep(0.1)
187
+ @expected.should eq 1
188
+ end
189
+
190
+ it 'matches all with a rescue with no class given' do
191
+ @expected = nil
192
+ Promise.new{ raise NoMethodError }.
193
+ rescue(LoadError){|ex| @expected = 1 }.
194
+ rescue{|ex| @expected = 2 }.
195
+ rescue(StandardError){|ex| @expected = 3 }
196
+ sleep(0.1)
197
+ @expected.should eq 2
198
+ end
199
+
200
+ it 'searches associated rescue handlers in order' do
201
+ @expected = nil
202
+ Promise.new{ raise ArgumentError }.
203
+ rescue(ArgumentError){|ex| @expected = 1 }.
204
+ rescue(LoadError){|ex| @expected = 2 }.
205
+ rescue(Exception){|ex| @expected = 3 }
206
+ sleep(0.1)
207
+ @expected.should eq 1
208
+
209
+ @expected = nil
210
+ Promise.new{ raise LoadError }.
211
+ rescue(ArgumentError){|ex| @expected = 1 }.
212
+ rescue(LoadError){|ex| @expected = 2 }.
213
+ rescue(Exception){|ex| @expected = 3 }
214
+ sleep(0.1)
215
+ @expected.should eq 2
216
+
217
+ @expected = nil
218
+ Promise.new{ raise StandardError }.
219
+ rescue(ArgumentError){|ex| @expected = 1 }.
220
+ rescue(LoadError){|ex| @expected = 2 }.
221
+ rescue(Exception){|ex| @expected = 3 }
222
+ sleep(0.1)
223
+ @expected.should eq 3
224
+ end
225
+
226
+ it 'passes the exception object to the matched block' do
227
+ @expected = nil
228
+ Promise.new{ raise StandardError }.
229
+ rescue(ArgumentError){|ex| @expected = ex }.
230
+ rescue(LoadError){|ex| @expected = ex }.
231
+ rescue(Exception){|ex| @expected = ex }
232
+ sleep(0.1)
233
+ @expected.should be_a(StandardError)
234
+ end
235
+
236
+ it 'ignores rescuers without a block' do
237
+ @expected = nil
238
+ Promise.new{ raise StandardError }.
239
+ rescue(StandardError).
240
+ rescue(StandardError){|ex| @expected = ex }.
241
+ rescue(Exception){|ex| @expected = ex }
242
+ sleep(0.1)
243
+ @expected.should be_a(StandardError)
244
+ end
245
+
246
+ it 'supresses the exception if no rescue matches' do
247
+ lambda {
248
+ Promise.new{ raise StandardError }.
249
+ rescue(ArgumentError){|ex| @expected = ex }.
250
+ rescue(StandardError){|ex| @expected = ex }.
251
+ rescue(Exception){|ex| @expected = ex }
252
+ sleep(0.1)
253
+ }.should_not raise_error
254
+ end
255
+
256
+ it 'supresses exceptions thrown from rescue handlers' do
257
+ lambda {
258
+ Promise.new{ raise ArgumentError }.
259
+ rescue(Exception){ raise StandardError }
260
+ sleep(0.1)
261
+ }.should_not raise_error
262
+ end
263
+
264
+ it 'calls matching rescue handlers on all children' do
265
+ @expected = []
266
+ Promise.new{ Thread.pass; raise StandardError }.
267
+ then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
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
+ sleep(0.1)
273
+
274
+ @expected.length.should eq 5
275
+ end
276
+ end
277
+
278
+ context 'aliases' do
279
+
280
+ it 'aliases #realized? for #fulfilled?' do
281
+ fulfilled_subject.should be_realized
282
+ end
283
+
284
+ it 'aliases #deref for #value' do
285
+ fulfilled_subject.deref.should eq fulfilled_value
286
+ end
287
+
288
+ it 'aliases #catch for #rescue' do
289
+ @expected = nil
290
+ Promise.new{ raise StandardError }.catch{ @expected = true }
291
+ sleep(0.1)
292
+ @expected.should be_true
293
+ end
294
+
295
+ it 'aliases #on_error for #rescue' do
296
+ @expected = nil
297
+ Promise.new{ raise StandardError }.on_error{ @expected = true }
298
+ sleep(0.1)
299
+ @expected.should be_true
300
+ end
301
+
302
+ it 'aliases Kernel#promise for Promise.new' do
303
+ promise().should be_a(Promise)
304
+ promise(){ nil }.should be_a(Promise)
305
+ promise(1, 2, 3).should be_a(Promise)
306
+ promise(1, 2, 3){ nil }.should be_a(Promise)
307
+ end
308
+ end
309
+ end
310
+ end