concurrent-ruby 0.2.1 → 0.2.2

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