concurrent-ruby 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +275 -275
  3. data/lib/concurrent.rb +28 -28
  4. data/lib/concurrent/agent.rb +114 -114
  5. data/lib/concurrent/cached_thread_pool.rb +131 -129
  6. data/lib/concurrent/defer.rb +65 -65
  7. data/lib/concurrent/event.rb +60 -60
  8. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  9. data/lib/concurrent/executor.rb +96 -95
  10. data/lib/concurrent/fixed_thread_pool.rb +99 -95
  11. data/lib/concurrent/functions.rb +120 -120
  12. data/lib/concurrent/future.rb +42 -42
  13. data/lib/concurrent/global_thread_pool.rb +16 -16
  14. data/lib/concurrent/goroutine.rb +29 -29
  15. data/lib/concurrent/null_thread_pool.rb +22 -22
  16. data/lib/concurrent/obligation.rb +67 -67
  17. data/lib/concurrent/promise.rb +174 -174
  18. data/lib/concurrent/reactor.rb +166 -166
  19. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  20. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  21. data/lib/concurrent/supervisor.rb +105 -100
  22. data/lib/concurrent/thread_pool.rb +76 -76
  23. data/lib/concurrent/utilities.rb +32 -32
  24. data/lib/concurrent/version.rb +3 -3
  25. data/lib/concurrent_ruby.rb +1 -1
  26. data/md/agent.md +123 -123
  27. data/md/defer.md +174 -174
  28. data/md/event.md +32 -32
  29. data/md/executor.md +187 -187
  30. data/md/future.md +83 -83
  31. data/md/goroutine.md +52 -52
  32. data/md/obligation.md +32 -32
  33. data/md/promise.md +227 -227
  34. data/md/thread_pool.md +224 -224
  35. data/spec/concurrent/agent_spec.rb +386 -386
  36. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  37. data/spec/concurrent/defer_spec.rb +195 -195
  38. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +200 -200
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  42. data/spec/concurrent/functions_spec.rb +217 -217
  43. data/spec/concurrent/future_spec.rb +108 -108
  44. data/spec/concurrent/global_thread_pool_spec.rb +38 -38
  45. data/spec/concurrent/goroutine_spec.rb +67 -67
  46. data/spec/concurrent/null_thread_pool_spec.rb +57 -54
  47. data/spec/concurrent/obligation_shared.rb +132 -132
  48. data/spec/concurrent/promise_spec.rb +312 -312
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  51. data/spec/concurrent/reactor_spec.rb +364 -364
  52. data/spec/concurrent/supervisor_spec.rb +269 -258
  53. data/spec/concurrent/thread_pool_shared.rb +204 -204
  54. data/spec/concurrent/utilities_spec.rb +74 -74
  55. data/spec/spec_helper.rb +32 -32
  56. metadata +20 -16
  57. 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