concurrent-ruby 0.2.1 → 0.2.2

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 (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