concurrent-ruby 0.1.1.pre.3 → 0.1.1.pre.4

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