concurrent-ruby 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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